├── .gitignore
├── .idea
├── encodings.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── itan
│ │ └── com
│ │ └── bluetoothle
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── itan
│ │ │ └── com
│ │ │ └── bluetoothle
│ │ │ ├── BluetoothActivity.java
│ │ │ ├── CentralRoleActivity.java
│ │ │ ├── CentralService.java
│ │ │ ├── Constants.java
│ │ │ ├── DeviceConnectActivity.java
│ │ │ ├── DevicesAdapter.java
│ │ │ ├── MainActivity.java
│ │ │ ├── PeripheralAdvertiseService.java
│ │ │ └── PeripheralRoleActivity.java
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ic_arrow_back_white_24dp.png
│ │ ├── drawable-mdpi
│ │ └── ic_arrow_back_white_24dp.png
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xhdpi
│ │ └── ic_arrow_back_white_24dp.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_arrow_back_white_24dp.png
│ │ ├── drawable-xxxhdpi
│ │ └── ic_arrow_back_white_24dp.png
│ │ ├── drawable
│ │ ├── color_option_1.xml
│ │ ├── color_option_2.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_central_role.xml
│ │ ├── activity_device_connect.xml
│ │ ├── activity_main.xml
│ │ ├── activity_peripheral_role.xml
│ │ └── list_item_device_list.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── itan
│ └── com
│ └── bluetoothle
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── 1.png
├── 2.png
├── 3.png
├── 4.png
├── Screenshot_20180515-1521351_LI.jpg
└── submarine_polygon_LI.jpg
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Android Bluetooth Low Energy communication Sample
2 | ===================================
3 | Bla Bla
4 |
5 | Getting Started
6 | ------------
7 | Bla Bla
8 |
9 | Screenshots
10 | -------------
11 |
12 |
13 |
14 | Acknowledgements
15 | ------------
16 | [android-BluetoothLeGatt][1]
17 |
18 | [android-BluetoothAdvertisements][2]
19 |
20 | [ble-test-peripheral-android][3]
21 |
22 | [1]:https://github.com/googlesamples/android-BluetoothLeGatt/
23 | [2]:https://github.com/googlesamples/android-BluetoothAdvertisements/
24 | [3]:https://github.com/WebBluetoothCG/ble-test-peripheral-android
25 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | applicationId "itan.com.bluetoothle"
7 | /*
8 | In Android 5.0, an Android device can now act as a Bluetooth LE peripheral device.
9 | Android 4.3 introduced platform support for Bluetooth Low Energy (Bluetooth LE)
10 | in the central role
11 | */
12 | minSdkVersion 21
13 | targetSdkVersion 26
14 | versionCode 1
15 | versionName "1.0"
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | compile fileTree(dir: 'libs', include: ['*.jar'])
28 | compile 'com.android.support:appcompat-v7:26.1.0'
29 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
30 | compile 'junit:junit:4.12'
31 | compile 'com.android.support.test:runner:1.0.1'
32 | compile 'com.android.support.test.espresso:espresso-core:3.0.1'
33 | compile 'com.android.support:design:26.1.0'
34 | }
35 |
--------------------------------------------------------------------------------
/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
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/itan/com/bluetoothle/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("itan.com.bluetoothle", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
40 |
44 |
45 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/BluetoothActivity.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.bluetooth.BluetoothAdapter;
4 | import android.bluetooth.BluetoothManager;
5 | import android.content.Context;
6 | import android.graphics.Color;
7 | import android.os.Bundle;
8 | import android.support.design.widget.Snackbar;
9 | import android.support.v7.app.ActionBar;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.Toolbar;
12 | import android.view.View;
13 |
14 | /**
15 | * Created by itanbarpeled on 28/01/2018.
16 | */
17 |
18 | public abstract class BluetoothActivity extends AppCompatActivity {
19 |
20 |
21 | private Toolbar mToolbar;
22 |
23 |
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(getLayoutId());
29 |
30 | mToolbar = (Toolbar) findViewById(R.id.toolbar);
31 |
32 | setToolbar();
33 | }
34 |
35 |
36 | protected abstract int getLayoutId();
37 |
38 | protected abstract int getTitleString();
39 |
40 | protected void onBackButtonClicked() {
41 | onBackPressed();
42 | }
43 |
44 | protected void showMsgText(int stringId) {
45 | showMsgText(getString(stringId));
46 | }
47 |
48 | protected void showMsgText(String string) {
49 | if (mToolbar != null) {
50 | Snackbar snackbar = Snackbar.make(mToolbar, string, Snackbar.LENGTH_LONG);
51 | snackbar.show();
52 | }
53 | }
54 |
55 | protected BluetoothAdapter getBluetoothAdapter() {
56 |
57 | BluetoothAdapter bluetoothAdapter;
58 | BluetoothManager bluetoothService = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE));
59 |
60 | if (bluetoothService != null) {
61 |
62 | bluetoothAdapter = bluetoothService.getAdapter();
63 |
64 | // Is Bluetooth supported on this device?
65 | if (bluetoothAdapter != null) {
66 |
67 | // Is Bluetooth turned on?
68 | if (bluetoothAdapter.isEnabled()) {
69 | /*
70 | all the other Bluetooth initial checks already verified in MainActivity
71 | */
72 | return bluetoothAdapter;
73 | }
74 | }
75 | }
76 |
77 | return null;
78 | }
79 |
80 |
81 | private void setToolbar() {
82 | setSupportActionBar(mToolbar);
83 |
84 | ActionBar actionBar = getSupportActionBar();
85 | if (actionBar != null) {
86 | actionBar.setDisplayShowTitleEnabled(false);
87 | actionBar.setDisplayHomeAsUpEnabled(true);
88 | actionBar.setDisplayShowHomeEnabled(true);
89 | }
90 |
91 | mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
92 | @Override
93 | public void onClick(View view) {
94 | onBackButtonClicked();
95 | }
96 | });
97 | mToolbar.setTitle(getTitleString());
98 | mToolbar.setTitleTextColor(Color.WHITE);
99 | }
100 |
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/CentralRoleActivity.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.bluetooth.BluetoothAdapter;
4 | import android.bluetooth.BluetoothClass;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.bluetooth.le.BluetoothLeScanner;
7 | import android.bluetooth.le.ScanCallback;
8 | import android.bluetooth.le.ScanFilter;
9 | import android.bluetooth.le.ScanResult;
10 | import android.bluetooth.le.ScanSettings;
11 | import android.content.Intent;
12 | import android.os.Bundle;
13 | import android.os.Handler;
14 | import android.os.Looper;
15 | import android.support.v7.widget.LinearLayoutManager;
16 | import android.support.v7.widget.RecyclerView;
17 | import android.util.Log;
18 | import android.view.View;
19 | import android.widget.Button;
20 | import android.widget.Toast;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.concurrent.TimeUnit;
25 |
26 |
27 | /**
28 | This activity represents the Central/Client role.
29 | Bluetooth communication flow:
30 | 1. advertise [peripheral]
31 | 2. scan [central]
32 | 3. connect [central]
33 | 4. notify [peripheral]
34 | 5. receive [central]
35 | */
36 | public class CentralRoleActivity extends BluetoothActivity implements View.OnClickListener, DevicesAdapter.DevicesAdapterListener {
37 |
38 |
39 | /**
40 | * Stops scanning after 30 seconds.
41 | */
42 | private static final long SCAN_PERIOD = 30000;
43 |
44 | private RecyclerView mDevicesRecycler;
45 | private DevicesAdapter mDevicesAdapter;
46 | private Button mScanButton;
47 |
48 | private ScanCallback mScanCallback;
49 |
50 | private Handler mHandler;
51 |
52 |
53 | @Override
54 | protected void onCreate(Bundle savedInstanceState) {
55 | super.onCreate(savedInstanceState);
56 |
57 | mScanButton = (Button) findViewById(R.id.button_scan);
58 | mScanButton.setOnClickListener(this);
59 |
60 | mDevicesRecycler = (RecyclerView) findViewById(R.id.devices_recycler_view);
61 | mDevicesRecycler.setHasFixedSize(true);
62 | mDevicesRecycler.setLayoutManager(new LinearLayoutManager(this));
63 |
64 | mDevicesAdapter = new DevicesAdapter(this);
65 | mDevicesRecycler.setAdapter(mDevicesAdapter);
66 |
67 | mHandler = new Handler(Looper.getMainLooper());
68 |
69 | }
70 |
71 |
72 | @Override
73 | protected int getLayoutId() {
74 | return R.layout.activity_central_role;
75 | }
76 |
77 |
78 | @Override
79 | public void onClick(View view) {
80 |
81 | switch(view.getId()) {
82 |
83 | case R.id.button_scan:
84 | startBLEScan();
85 | break;
86 |
87 | }
88 | }
89 |
90 |
91 | @Override
92 | protected int getTitleString() {
93 | return R.string.central_screen;
94 | }
95 |
96 |
97 | /*
98 | start Bluetooth Low Energy scan
99 | */
100 | private void startBLEScan() {
101 |
102 | BluetoothAdapter bluetoothAdapter = getBluetoothAdapter();
103 |
104 | /*
105 | better to request each time as BluetoothAdapter state might change (connection lost, etc...)
106 | */
107 | if (bluetoothAdapter != null) {
108 |
109 | BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
110 |
111 | if (bluetoothLeScanner != null) {
112 |
113 | if (mScanCallback == null) {
114 | Log.d(MainActivity.TAG, "Starting Scanning");
115 |
116 | // Will stop the scanning after a set time.
117 | mHandler.postDelayed(new Runnable() {
118 | @Override
119 | public void run() {
120 | stopScanning();
121 | }
122 | }, SCAN_PERIOD);
123 |
124 | // Kick off a new scan.
125 | mScanCallback = new SampleScanCallback();
126 | bluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
127 |
128 | String toastText =
129 | getString(R.string.scan_start_toast) + " "
130 | + TimeUnit.SECONDS.convert(SCAN_PERIOD, TimeUnit.MILLISECONDS) + " "
131 | + getString(R.string.seconds);
132 |
133 | showMsgText(toastText);
134 |
135 | } else {
136 | showMsgText(R.string.already_scanning);
137 | }
138 |
139 | return;
140 | }
141 | }
142 |
143 | showMsgText(R.string.error_unknown);
144 | }
145 |
146 | /**
147 | * Return a List of {@link ScanFilter} objects to filter by Service UUID.
148 | */
149 | private List buildScanFilters() {
150 |
151 | List scanFilters = new ArrayList<>();
152 |
153 | ScanFilter.Builder builder = new ScanFilter.Builder();
154 | // Comment out the below line to see all BLE devices around you
155 | //builder.setServiceUuid(Constants.SERVICE_UUID);
156 | scanFilters.add(builder.build());
157 |
158 | return scanFilters;
159 | }
160 |
161 | /**
162 | * Return a {@link ScanSettings} object set to use low power (to preserve battery life).
163 | */
164 | private ScanSettings buildScanSettings() {
165 | ScanSettings.Builder builder = new ScanSettings.Builder();
166 | builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
167 | return builder.build();
168 | }
169 |
170 |
171 | /**
172 | * Stop scanning for BLE Advertisements.
173 | */
174 | public void stopScanning() {
175 |
176 | Log.d(MainActivity.TAG, "Stopping Scanning");
177 |
178 | /*
179 | better to request each time as BluetoothAdapter state might change (connection lost, etc...)
180 | */
181 | BluetoothAdapter bluetoothAdapter = getBluetoothAdapter();
182 |
183 | if (bluetoothAdapter != null) {
184 |
185 | BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
186 |
187 | if (bluetoothLeScanner != null) {
188 |
189 | // Stop the scan, wipe the callback.
190 | bluetoothLeScanner.stopScan(mScanCallback);
191 | mScanCallback = null;
192 |
193 | // Even if no new results, update 'last seen' times.
194 | mDevicesAdapter.notifyDataSetChanged();
195 |
196 | return;
197 | }
198 | }
199 |
200 | showMsgText(R.string.error_unknown);
201 | }
202 |
203 |
204 | @Override
205 | public void onDeviceItemClick(String deviceName, String deviceAddress) {
206 |
207 | //stopScanning();
208 |
209 | Intent intent = new Intent(this, DeviceConnectActivity.class);
210 | intent.putExtra(DeviceConnectActivity.EXTRAS_DEVICE_NAME, deviceName);
211 | intent.putExtra(DeviceConnectActivity.EXTRAS_DEVICE_ADDRESS, deviceAddress);
212 | startActivity(intent);
213 | }
214 |
215 |
216 | /**
217 | * Custom ScanCallback object - adds to adapter on success, displays error on failure.
218 | */
219 | private class SampleScanCallback extends ScanCallback {
220 |
221 | @Override
222 | public void onBatchScanResults(List results) {
223 | super.onBatchScanResults(results);
224 | mDevicesAdapter.add(results);
225 | logResults(results);
226 | }
227 |
228 | @Override
229 | public void onScanResult(int callbackType, ScanResult result) {
230 | super.onScanResult(callbackType, result);
231 | mDevicesAdapter.add(result);
232 | logResults(result);
233 | }
234 |
235 | @Override
236 | public void onScanFailed(int errorCode) {
237 | super.onScanFailed(errorCode);
238 | showMsgText("Scan failed with error: " + errorCode);
239 | }
240 |
241 |
242 | private void logResults(List results) {
243 | if (results != null) {
244 | for (ScanResult result : results) {
245 | logResults(result);
246 | }
247 | }
248 | }
249 |
250 | private void logResults(ScanResult result) {
251 | if (result != null) {
252 | BluetoothDevice device = result.getDevice();
253 | if (device != null) {
254 | Log.v(MainActivity.TAG, device.getName() + " " + device.getAddress());
255 | return;
256 | }
257 | }
258 | Log.e(MainActivity.TAG, "error SampleScanCallback");
259 | }
260 | }
261 |
262 |
263 | }
264 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/CentralService.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.app.Service;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.bluetooth.BluetoothGatt;
7 | import android.bluetooth.BluetoothGattCallback;
8 | import android.bluetooth.BluetoothGattCharacteristic;
9 | import android.bluetooth.BluetoothGattDescriptor;
10 | import android.bluetooth.BluetoothGattService;
11 | import android.bluetooth.BluetoothManager;
12 | import android.bluetooth.BluetoothProfile;
13 | import android.content.Context;
14 | import android.content.Intent;
15 | import android.os.Binder;
16 | import android.os.IBinder;
17 | import android.util.Log;
18 | import java.util.List;
19 |
20 | import static itan.com.bluetoothle.Constants.BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID;
21 | import static itan.com.bluetoothle.Constants.HEART_RATE_SERVICE_UUID;
22 |
23 | /**
24 | * Created by itanbarpeled on 28/01/2018.
25 | */
26 |
27 | public class CentralService extends Service {
28 |
29 | private static final int STATE_DISCONNECTED = 0;
30 | private static final int STATE_CONNECTING = 1;
31 | private static final int STATE_CONNECTED = 2;
32 |
33 | public final static String ACTION_GATT_CONNECTED = "ACTION_GATT_CONNECTED";
34 | public final static String ACTION_GATT_DISCONNECTED = "ACTION_GATT_DISCONNECTED";
35 | public final static String ACTION_GATT_SERVICES_DISCOVERED = "ACTION_GATT_SERVICES_DISCOVERED";
36 | public final static String ACTION_DATA_AVAILABLE = "ACTION_DATA_AVAILABLE";
37 | public final static String EXTRA_DATA = "EXTRA_DATA";
38 |
39 |
40 | private BluetoothManager mBluetoothManager;
41 | private BluetoothAdapter mBluetoothAdapter;
42 | private String mBluetoothDeviceAddress;
43 | private BluetoothGatt mBluetoothGatt;
44 |
45 | private final IBinder mBinder = new LocalBinder();
46 | private int mConnectionState = STATE_DISCONNECTED;
47 |
48 |
49 | /*
50 | Implements callback methods for GATT events that the app cares about. For example,
51 | connection change and services discovered.
52 | */
53 | private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
54 |
55 | @Override
56 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
57 |
58 | String intentAction;
59 |
60 | if (newState == BluetoothProfile.STATE_CONNECTED) {
61 | intentAction = ACTION_GATT_CONNECTED;
62 | mConnectionState = STATE_CONNECTED;
63 | broadcastUpdate(intentAction);
64 | Log.i(MainActivity.TAG, "Connected to GATT server.");
65 | // Attempts to discover services after successful connection.
66 | Log.i(MainActivity.TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
67 |
68 | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
69 | intentAction = ACTION_GATT_DISCONNECTED;
70 | mConnectionState = STATE_DISCONNECTED;
71 | Log.i(MainActivity.TAG, "Disconnected from GATT server.");
72 | broadcastUpdate(intentAction);
73 | }
74 | }
75 |
76 | @Override
77 | public void onServicesDiscovered(BluetoothGatt gatt, int status) {
78 | if (status == BluetoothGatt.GATT_SUCCESS) {
79 | broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
80 | } else {
81 | Log.w(MainActivity.TAG, "onServicesDiscovered received: " + status);
82 | }
83 | }
84 |
85 | @Override
86 | public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
87 | if (status == BluetoothGatt.GATT_SUCCESS) {
88 | broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
89 | } else {
90 | Log.w(MainActivity.TAG, "onCharacteristicRead GATT_FAILURE");
91 | }
92 | }
93 |
94 | @Override
95 | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
96 | broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
97 | }
98 | };
99 |
100 |
101 | private void broadcastUpdate(final String action) {
102 | final Intent intent = new Intent(action);
103 | sendBroadcast(intent);
104 | }
105 |
106 |
107 | private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) {
108 |
109 | Intent intent = new Intent(action);
110 |
111 | /*
112 | This is special handling for the Heart Rate Measurement profile. Data parsing is
113 | carried out as per profile specifications:
114 | http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
115 | */
116 | if (BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {
117 |
118 | int flag = characteristic.getProperties();
119 | int format = -1;
120 |
121 | if ((flag & 0x01) != 0) {
122 | format = BluetoothGattCharacteristic.FORMAT_UINT16;
123 | Log.d(MainActivity.TAG, "data format UINT16.");
124 | } else {
125 | format = BluetoothGattCharacteristic.FORMAT_UINT8;
126 | Log.d(MainActivity.TAG, "data format UINT16.");
127 | }
128 |
129 | int msg = characteristic.getIntValue(format, 0);
130 | Log.d(MainActivity.TAG, String.format("message: %d", msg));
131 | intent.putExtra(EXTRA_DATA, msg);
132 |
133 | } else {
134 |
135 | /*
136 | for all other profiles, writes the data formatted in HEX.
137 | this code isn't relevant for this project.
138 | */
139 | final byte[] data = characteristic.getValue();
140 |
141 | if (data != null && data.length > 0) {
142 | final StringBuilder stringBuilder = new StringBuilder(data.length);
143 | for (byte byteChar : data) {
144 | stringBuilder.append(String.format("%02X ", byteChar));
145 | }
146 |
147 | Log.w(MainActivity.TAG, "broadcastUpdate. general profile");
148 | intent.putExtra(EXTRA_DATA, -1);
149 | //intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
150 | }
151 | }
152 |
153 |
154 | sendBroadcast(intent);
155 | }
156 |
157 | public class LocalBinder extends Binder {
158 | CentralService getService() {
159 | return CentralService.this;
160 | }
161 | }
162 |
163 | @Override
164 | public IBinder onBind(Intent intent) {
165 | return mBinder;
166 | }
167 |
168 | @Override
169 | public boolean onUnbind(Intent intent) {
170 | // After using a given device, you should make sure that BluetoothGatt.close() is called
171 | // such that resources are cleaned up properly. In this particular example, close() is
172 | // invoked when the UI is disconnected from the Service.
173 | close();
174 | return super.onUnbind(intent);
175 | }
176 |
177 |
178 | /**
179 | * Initializes a reference to the local Bluetooth adapter.
180 | *
181 | * @return Return true if the initialization is successful.
182 | */
183 | public boolean initialize() {
184 |
185 | // For API level 18 and above, get a reference to BluetoothAdapter through BluetoothManager.
186 | if (mBluetoothManager == null) {
187 |
188 | mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
189 |
190 | if (mBluetoothManager == null) {
191 | Log.e(MainActivity.TAG, "Unable to initialize BluetoothManager.");
192 | return false;
193 | }
194 | }
195 |
196 | mBluetoothAdapter = mBluetoothManager.getAdapter();
197 |
198 | if (mBluetoothAdapter == null) {
199 | Log.e(MainActivity.TAG, "Unable to obtain a BluetoothAdapter.");
200 | return false;
201 | }
202 |
203 | return true;
204 | }
205 |
206 | /**
207 | * Connects to the GATT server hosted on the Bluetooth LE device.
208 | *
209 | * @param address The device address of the destination device.
210 | *
211 | * @return Return true if the connection is initiated successfully. The connection result
212 | * is reported asynchronously through the
213 | * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
214 | * callback.
215 | */
216 | public boolean connect(final String address) {
217 |
218 | if (mBluetoothAdapter == null || address == null) {
219 | Log.w(MainActivity.TAG, "BluetoothAdapter not initialized or unspecified address.");
220 | return false;
221 | }
222 |
223 | // Previously connected device. Try to reconnect.
224 | if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) {
225 | Log.d(MainActivity.TAG, "Trying to use an existing mBluetoothGatt for connection.");
226 | if (mBluetoothGatt.connect()) {
227 | mConnectionState = STATE_CONNECTING;
228 | return true;
229 | } else {
230 | return false;
231 | }
232 | }
233 |
234 | BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
235 | if (device == null) {
236 | Log.w(MainActivity.TAG, "Device not found. Unable to connect.");
237 | return false;
238 | }
239 |
240 | // We want to directly connect to the device, so we are setting the autoConnect
241 | // parameter to false.
242 | mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
243 | mBluetoothDeviceAddress = address;
244 | mConnectionState = STATE_CONNECTING;
245 |
246 | Log.d(MainActivity.TAG, "Trying to create a new connection.");
247 |
248 | return true;
249 | }
250 |
251 |
252 | // TODO bluetooth - call this method when needed
253 | /**
254 | * Disconnects an existing connection or cancel a pending connection. The disconnection result
255 | * is reported asynchronously through the
256 | * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
257 | * callback.
258 | */
259 | public void disconnect() {
260 |
261 | if (mBluetoothAdapter == null || mBluetoothGatt == null) {
262 | Log.w(MainActivity.TAG, "BluetoothAdapter not initialized");
263 | return;
264 | }
265 |
266 | mBluetoothGatt.disconnect();
267 | }
268 |
269 | /**
270 | * After using a given BLE device, the app must call this method to ensure resources are
271 | * released properly.
272 | */
273 | public void close() {
274 |
275 | if (mBluetoothGatt == null) {
276 | return;
277 | }
278 |
279 | mBluetoothGatt.close();
280 | mBluetoothGatt = null;
281 | }
282 |
283 |
284 |
285 | /**
286 | * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
287 | * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
288 | * callback.
289 | *
290 | * @param characteristic The characteristic to read from.
291 | */
292 | public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
293 |
294 | if (mBluetoothAdapter == null || mBluetoothGatt == null) {
295 | Log.w(MainActivity.TAG, "BluetoothAdapter not initialized");
296 | return;
297 | }
298 |
299 | mBluetoothGatt.readCharacteristic(characteristic);
300 | }
301 |
302 | /**
303 | * Enables or disables notification on a give characteristic.
304 | *
305 | * @param characteristic Characteristic to act on.
306 | * @param enabled If true, enable notification. False otherwise.
307 | */
308 | public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
309 |
310 | if (mBluetoothAdapter == null || mBluetoothGatt == null) {
311 | Log.w(MainActivity.TAG, "BluetoothAdapter not initialized");
312 | return;
313 | }
314 |
315 | mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
316 |
317 | // This is specific to Heart Rate Measurement.
318 | /*
319 | if (HEART_RATE_MEASUREMENT_UUID.toString().equals(characteristic.getUuid().toString())) {
320 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
321 | descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
322 | mBluetoothGatt.writeDescriptor(descriptor);
323 | }
324 | */
325 |
326 | }
327 |
328 | /**
329 | * Retrieves a list of supported GATT services on the connected device. This should be
330 | * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
331 | *
332 | * @return A {@code List} of supported services.
333 | */
334 | public List getSupportedGattServices() {
335 | if (mBluetoothGatt == null) {
336 | return null;
337 | }
338 |
339 | return mBluetoothGatt.getServices();
340 | }
341 | }
342 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/Constants.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.bluetooth.BluetoothGattCharacteristic;
4 | import android.os.ParcelUuid;
5 |
6 | import java.util.UUID;
7 |
8 | /**
9 | * Created by itanbarpeled on 28/01/2018.
10 | */
11 |
12 | public class Constants {
13 |
14 |
15 | public static final int SERVER_MSG_FIRST_STATE = 1;
16 | public static final int SERVER_MSG_SECOND_STATE = 2;
17 |
18 | /*
19 | TODO bluetooth
20 | better to use different Bluetooth Service,
21 | instead of Heart Rate Service:
22 | https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml.
23 |
24 | maybe Object Transfer Service is more suitable:
25 | https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.object_transfer.xml
26 | */
27 | public static final UUID HEART_RATE_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb");
28 | public static final UUID BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb");
29 |
30 |
31 |
32 |
33 | private static UUID convertFromInteger(int i) {
34 | final long MSB = 0x0000000000001000L;
35 | final long LSB = 0x800000805f9b34fbL;
36 | long value = i & 0xFFFFFFFF;
37 | return new UUID(MSB | (value << 32), LSB);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/DeviceConnectActivity.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.bluetooth.BluetoothGattCharacteristic;
4 | import android.bluetooth.BluetoothGattService;
5 | import android.content.BroadcastReceiver;
6 | import android.content.ComponentName;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.IntentFilter;
10 | import android.content.ServiceConnection;
11 | import android.graphics.Color;
12 | import android.os.IBinder;
13 | import android.os.Bundle;
14 | import android.text.TextUtils;
15 | import android.util.Log;
16 | import android.view.View;
17 | import android.widget.Button;
18 | import android.widget.ImageView;
19 | import android.widget.TextView;
20 | import java.util.ArrayList;
21 | import java.util.List;
22 | import static itan.com.bluetoothle.Constants.BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID;
23 | import static itan.com.bluetoothle.Constants.HEART_RATE_SERVICE_UUID;
24 | import static itan.com.bluetoothle.Constants.SERVER_MSG_FIRST_STATE;
25 | import static itan.com.bluetoothle.Constants.SERVER_MSG_SECOND_STATE;
26 |
27 |
28 |
29 | public class DeviceConnectActivity extends BluetoothActivity implements View.OnClickListener {
30 |
31 | public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME";
32 | public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS";
33 | private static final String LIST_NAME = "NAME";
34 | private static final String LIST_UUID = "UUID";
35 |
36 |
37 | private CentralService mBluetoothLeService;
38 | private ArrayList> mDeviceServices;
39 | private BluetoothGattCharacteristic mCharacteristic;
40 |
41 | private String mDeviceName;
42 | private String mDeviceAddress;
43 |
44 | private TextView mConnectionStatus;
45 | private TextView mConnectedDeviceName;
46 | private ImageView mServerCharacteristic;
47 | private Button mRequestReadCharacteristic;
48 |
49 |
50 | @Override
51 | protected void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 |
54 | mDeviceServices = new ArrayList<>();
55 | mCharacteristic = null;
56 |
57 | Intent intent = getIntent();
58 | if (intent != null) {
59 | mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME);
60 | mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS);
61 | }
62 |
63 |
64 | mConnectionStatus = (TextView) findViewById(R.id.connection_status);
65 | mConnectedDeviceName = (TextView) findViewById(R.id.connected_device_name);
66 | mServerCharacteristic = (ImageView) findViewById(R.id.server_characteristic_value);
67 | mRequestReadCharacteristic = (Button) findViewById(R.id.request_read_characteristic);
68 | mRequestReadCharacteristic.setOnClickListener(this);
69 |
70 |
71 | if (TextUtils.isEmpty(mDeviceName)) {
72 | mConnectedDeviceName.setText("");
73 | } else {
74 | mConnectedDeviceName.setText(mDeviceName);
75 | }
76 |
77 |
78 | Intent gattServiceIntent = new Intent(this, CentralService.class);
79 | bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
80 |
81 | /*
82 | updateConnectionState(R.string.connected);
83 | mRequestReadCharacteristic.setEnabled(true);
84 | updateInputFromServer(SERVER_MSG_SECOND_STATE);
85 | */
86 | }
87 |
88 |
89 | @Override
90 | protected void onResume() {
91 | super.onResume();
92 | registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
93 | }
94 |
95 |
96 | @Override
97 | protected void onPause() {
98 | super.onPause();
99 | unregisterReceiver(mGattUpdateReceiver);
100 | }
101 |
102 |
103 | @Override
104 | protected void onDestroy() {
105 | super.onDestroy();
106 | unbindService(mServiceConnection);
107 | mBluetoothLeService = null;
108 | }
109 |
110 | @Override
111 | protected int getLayoutId() {
112 | return R.layout.activity_device_connect;
113 | }
114 |
115 | @Override
116 | protected int getTitleString() {
117 | return R.string.central_connection_screen;
118 | }
119 |
120 |
121 | @Override
122 | public void onClick(View view) {
123 |
124 | switch (view.getId()) {
125 |
126 | case R.id.request_read_characteristic:
127 | requestReadCharacteristic();
128 | break;
129 |
130 | }
131 | }
132 |
133 |
134 | /*
135 | request from the Server the value of the Characteristic.
136 | this request is asynchronous.
137 | */
138 | private void requestReadCharacteristic() {
139 | if (mBluetoothLeService != null && mCharacteristic != null) {
140 | mBluetoothLeService.readCharacteristic(mCharacteristic);
141 | } else {
142 | showMsgText(R.string.error_unknown);
143 | }
144 | }
145 |
146 |
147 | // Code to manage Service lifecycle.
148 | private final ServiceConnection mServiceConnection = new ServiceConnection() {
149 |
150 | @Override
151 | public void onServiceConnected(ComponentName componentName, IBinder service) {
152 |
153 | mBluetoothLeService = ((CentralService.LocalBinder) service).getService();
154 |
155 | if (!mBluetoothLeService.initialize()) {
156 | Log.e(MainActivity.TAG, "Unable to initialize Bluetooth");
157 | finish();
158 | }
159 |
160 | // Automatically connects to the device upon successful start-up initialization.
161 | mBluetoothLeService.connect(mDeviceAddress);
162 | }
163 |
164 | @Override
165 | public void onServiceDisconnected(ComponentName componentName) {
166 | mBluetoothLeService = null;
167 | }
168 | };
169 |
170 |
171 | /*
172 | Handles various events fired by the Service.
173 | ACTION_GATT_CONNECTED: connected to a GATT server.
174 | ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
175 | ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
176 | ACTION_DATA_AVAILABLE: received data from the device. This can be a result of read or notification operations.
177 | */
178 | private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
179 | @Override
180 | public void onReceive(Context context, Intent intent) {
181 |
182 | String action = intent.getAction();
183 |
184 | if (action == null) {
185 | return;
186 | }
187 |
188 | switch (intent.getAction()) {
189 |
190 | case CentralService.ACTION_GATT_CONNECTED:
191 | updateConnectionState(R.string.connected);
192 | mRequestReadCharacteristic.setEnabled(true);
193 | break;
194 |
195 | case CentralService.ACTION_GATT_DISCONNECTED:
196 | updateConnectionState(R.string.disconnected);
197 | mRequestReadCharacteristic.setEnabled(false);
198 | break;
199 |
200 |
201 | case CentralService.ACTION_GATT_SERVICES_DISCOVERED:
202 | // set all the supported services and characteristics on the user interface.
203 | setGattServices(mBluetoothLeService.getSupportedGattServices());
204 | registerCharacteristic();
205 | break;
206 |
207 | case CentralService.ACTION_DATA_AVAILABLE:
208 | int msg = intent.getIntExtra(CentralService.EXTRA_DATA, -1);
209 | Log.v(MainActivity.TAG, "ACTION_DATA_AVAILABLE " + msg);
210 | updateInputFromServer(msg);
211 | break;
212 |
213 | }
214 | }
215 | };
216 |
217 |
218 | /*
219 | This sample demonstrates 'Read' and 'Notify' features.
220 | See http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
221 | list of supported characteristic features.
222 | */
223 | private void registerCharacteristic() {
224 |
225 | BluetoothGattCharacteristic characteristic = null;
226 |
227 | if (mDeviceServices != null) {
228 |
229 | /* iterate all the Services the connected device offer.
230 | a Service is a collection of Characteristic.
231 | */
232 | for (ArrayList service : mDeviceServices) {
233 |
234 | // iterate all the Characteristic of the Service
235 | for (BluetoothGattCharacteristic serviceCharacteristic : service) {
236 |
237 | /* check this characteristic belongs to the Service defined in
238 | PeripheralAdvertiseService.buildAdvertiseData() method
239 | */
240 | if (serviceCharacteristic.getService().getUuid().equals(HEART_RATE_SERVICE_UUID)) {
241 |
242 | if (serviceCharacteristic.getUuid().equals(BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID)) {
243 | characteristic = serviceCharacteristic;
244 | mCharacteristic = characteristic;
245 | }
246 | }
247 | }
248 | }
249 |
250 | /*
251 | int charaProp = characteristic.getProperties();
252 | if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
253 | */
254 |
255 | if (characteristic != null) {
256 | mBluetoothLeService.readCharacteristic(characteristic);
257 | mBluetoothLeService.setCharacteristicNotification(characteristic, true);
258 | }
259 | }
260 | }
261 |
262 |
263 | /*
264 | Demonstrates how to iterate through the supported GATT Services/Characteristics.
265 | */
266 | private void setGattServices(List gattServices) {
267 |
268 | if (gattServices == null) {
269 | return;
270 | }
271 |
272 | mDeviceServices = new ArrayList<>();
273 |
274 | // Loops through available GATT Services from the connected device
275 | for (BluetoothGattService gattService : gattServices) {
276 | ArrayList characteristic = new ArrayList<>();
277 | characteristic.addAll(gattService.getCharacteristics()); // each GATT Service can have multiple characteristic
278 | mDeviceServices.add(characteristic);
279 | }
280 |
281 | }
282 |
283 |
284 | private void updateConnectionState(final int resourceId) {
285 | runOnUiThread(new Runnable() {
286 | @Override
287 | public void run() {
288 | mConnectionStatus.setText(resourceId);
289 | }
290 | });
291 | }
292 |
293 |
294 | private static IntentFilter makeGattUpdateIntentFilter() {
295 | final IntentFilter intentFilter = new IntentFilter();
296 | intentFilter.addAction(CentralService.ACTION_GATT_CONNECTED);
297 | intentFilter.addAction(CentralService.ACTION_GATT_DISCONNECTED);
298 | intentFilter.addAction(CentralService.ACTION_GATT_SERVICES_DISCOVERED);
299 | intentFilter.addAction(CentralService.ACTION_DATA_AVAILABLE);
300 | return intentFilter;
301 | }
302 |
303 |
304 | private void updateInputFromServer(int msg) {
305 |
306 | String color;
307 |
308 | switch (msg) {
309 | case SERVER_MSG_FIRST_STATE:
310 | color = "#AD1457";
311 | break;
312 |
313 | case SERVER_MSG_SECOND_STATE:
314 | color = "#6A1B9A";
315 | break;
316 |
317 | default:
318 | color = "#FFFFFF";
319 | break;
320 |
321 | }
322 |
323 | mServerCharacteristic.setBackgroundColor(Color.parseColor(color));
324 | showMsgText(String.format(getString(R.string.characteristic_value_received), msg));
325 | }
326 |
327 |
328 |
329 |
330 |
331 |
332 | }
333 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/DevicesAdapter.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 | import android.bluetooth.le.ScanResult;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.text.TextUtils;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * Created by itanbarpeled on 28/01/2018.
17 | */
18 |
19 | public class DevicesAdapter extends RecyclerView.Adapter {
20 |
21 |
22 | public interface DevicesAdapterListener {
23 | void onDeviceItemClick(String deviceName, String deviceAddress);
24 | }
25 |
26 | /*
27 | class Temp {
28 | String name;
29 | String address;
30 |
31 | public Temp(String name, String address) {
32 | this.name = name;
33 | this.address = address;
34 | }
35 | }
36 | */
37 |
38 | static class ViewHolder extends RecyclerView.ViewHolder {
39 |
40 | TextView mDeviceNameView;
41 | TextView mDeviceNameAddressView;
42 |
43 | ViewHolder(View view) {
44 |
45 | super(view);
46 | mDeviceNameView = (TextView) view.findViewById(R.id.device_name);
47 | mDeviceNameAddressView = (TextView) view.findViewById(R.id.device_address);
48 | }
49 |
50 | }
51 |
52 | //private ArrayList mArrayList;
53 | private ArrayList mArrayList;
54 | private DevicesAdapterListener mListener;
55 |
56 |
57 | public DevicesAdapter(DevicesAdapterListener listener) {
58 | mArrayList = new ArrayList<>();
59 | mListener = listener;
60 |
61 | /*
62 | Temp device = new Temp("Pixel", "AA:12:AA:12:AA:12");
63 | mArrayList.add(device);
64 | device = new Temp("Galaxy S8", "BB:23:BB:23:BB:23");
65 | mArrayList.add(device);
66 | notifyDataSetChanged();
67 | */
68 | }
69 |
70 | public DevicesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
71 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_device_list, parent, false);
72 | return new ViewHolder(view);
73 | }
74 |
75 | @Override
76 | public void onBindViewHolder(ViewHolder holder, int position) {
77 |
78 | ScanResult scanResult = mArrayList.get(position);
79 | final String deviceName = scanResult.getDevice().getName();
80 | final String deviceAddress = scanResult.getDevice().getAddress();
81 |
82 | /*
83 | Temp scanResult = mArrayList.get(position);
84 | final String deviceName = scanResult.name;
85 | final String deviceAddress = scanResult.address;
86 | */
87 |
88 | if (TextUtils.isEmpty(deviceName)) {
89 | holder.mDeviceNameView.setText("");
90 | } else {
91 | holder.mDeviceNameView.setText(deviceName);
92 | }
93 |
94 | if (TextUtils.isEmpty(deviceAddress)) {
95 | holder.mDeviceNameAddressView.setText("");
96 | } else {
97 | holder.mDeviceNameAddressView.setText(deviceAddress);
98 | }
99 |
100 | holder.itemView.setOnClickListener(new View.OnClickListener() {
101 | @Override
102 | public void onClick(View view) {
103 | if (!TextUtils.isEmpty(deviceName) && !TextUtils.isEmpty(deviceAddress)) {
104 | if (mListener != null) {
105 | mListener.onDeviceItemClick(deviceName, deviceAddress);
106 | }
107 | }
108 | }
109 | });
110 | }
111 |
112 | @Override
113 | public int getItemCount() {
114 | return mArrayList.size();
115 | }
116 |
117 | public void add(ScanResult scanResult) {
118 | add(scanResult, true);
119 | }
120 |
121 | /**
122 | * Add a ScanResult item to the adapter if a result from that device isn't already present.
123 | * Otherwise updates the existing position with the new ScanResult.
124 | */
125 | public void add(ScanResult scanResult, boolean notify) {
126 |
127 | if (scanResult == null) {
128 | return;
129 | }
130 |
131 | int existingPosition = getPosition(scanResult.getDevice().getAddress());
132 |
133 | if (existingPosition >= 0) {
134 | // Device is already in list, update its record.
135 | mArrayList.set(existingPosition, scanResult);
136 | } else {
137 | // Add new Device's ScanResult to list.
138 | mArrayList.add(scanResult);
139 | }
140 |
141 | if (notify) {
142 | notifyDataSetChanged();
143 | }
144 |
145 | }
146 |
147 | public void add(List scanResults) {
148 | if (scanResults != null) {
149 | for (ScanResult scanResult : scanResults) {
150 | add(scanResult, false);
151 | }
152 | notifyDataSetChanged();
153 | }
154 | }
155 |
156 |
157 | /**
158 | * Search the adapter for an existing device address and return it, otherwise return -1.
159 | */
160 | private int getPosition(String address) {
161 | int position = -1;
162 | for (int i = 0; i < mArrayList.size(); i++) {
163 | if (mArrayList.get(i).getDevice().getAddress().equals(address)) {
164 | position = i;
165 | break;
166 | }
167 | }
168 | return position;
169 | }
170 |
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/MainActivity.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.Manifest;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothManager;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.pm.PackageManager;
9 | import android.os.Build;
10 | import android.support.annotation.NonNull;
11 | import android.support.design.widget.Snackbar;
12 | import android.support.v4.app.ActivityCompat;
13 | import android.support.v7.app.AppCompatActivity;
14 | import android.os.Bundle;
15 | import android.view.View;
16 | import android.widget.Button;
17 | import android.widget.Toast;
18 |
19 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
20 |
21 | public static final String TAG = "BluetoothLE";
22 | private static final int REQUEST_ENABLE_BT = 1;
23 | private static final int PERMISSION_REQUEST_COARSE_LOCATION = 2;
24 |
25 |
26 | private Button mPeripheralButton;
27 | private Button mCentralButton;
28 |
29 | private BluetoothAdapter mBluetoothAdapter;
30 |
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 |
37 | mPeripheralButton = (Button) findViewById(R.id.button_role_peripheral);
38 | mCentralButton = (Button) findViewById(R.id.button_role_central);
39 |
40 | mPeripheralButton.setOnClickListener(this);
41 | mCentralButton.setOnClickListener(this);
42 |
43 | if (savedInstanceState == null) {
44 | initBT();
45 | }
46 |
47 | }
48 |
49 |
50 | @Override
51 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
52 |
53 | super.onActivityResult(requestCode, resultCode, data);
54 |
55 | switch (requestCode) {
56 | case REQUEST_ENABLE_BT:
57 |
58 | if (resultCode == RESULT_OK) {
59 |
60 | initBT();
61 |
62 | } else {
63 |
64 | // User declined to enable Bluetooth, exit the app.
65 | Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
66 | }
67 | break;
68 |
69 | default:
70 | super.onActivityResult(requestCode, resultCode, data);
71 | break;
72 | }
73 | }
74 |
75 |
76 | @Override
77 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
78 |
79 | switch (requestCode) {
80 |
81 | case PERMISSION_REQUEST_COARSE_LOCATION:
82 | if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
83 | showErrorText(R.string.bt_not_permit_coarse);
84 | } else {
85 | // Everything is supported and enabled.
86 | enableNavigation();
87 | }
88 | break;
89 |
90 | }
91 | }
92 |
93 |
94 | private void initBT() {
95 |
96 | BluetoothManager bluetoothService = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE));
97 |
98 | if (bluetoothService != null) {
99 |
100 | mBluetoothAdapter = bluetoothService.getAdapter();
101 |
102 | // Is Bluetooth supported on this device?
103 | if (mBluetoothAdapter != null) {
104 |
105 | // Is Bluetooth turned on?
106 | if (mBluetoothAdapter.isEnabled()) {
107 |
108 | // Are Bluetooth Advertisements supported on this device?
109 | if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {
110 |
111 | // see https://stackoverflow.com/a/37015725/1869297
112 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
113 |
114 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
115 | requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
116 | } else {
117 | // Everything is supported and enabled.
118 | enableNavigation();
119 | }
120 |
121 | } else {
122 | // Everything is supported and enabled.
123 | enableNavigation();
124 | }
125 |
126 |
127 | } else {
128 |
129 | // Bluetooth Advertisements are not supported.
130 | showErrorText(R.string.bt_ads_not_supported);
131 | }
132 | } else {
133 |
134 | // Prompt user to turn on Bluetooth (logic continues in onActivityResult()).
135 | Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
136 | startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
137 | }
138 | } else {
139 |
140 | // Bluetooth is not supported.
141 | showErrorText(R.string.bt_not_supported);
142 | }
143 |
144 | }
145 | }
146 |
147 |
148 | @Override
149 | public void onClick(View view) {
150 |
151 | Intent intent = null;
152 |
153 | switch(view.getId()) {
154 |
155 | case R.id.button_role_peripheral:
156 | intent = new Intent(this, PeripheralRoleActivity.class);
157 | break;
158 |
159 | case R.id.button_role_central:
160 | intent = new Intent(this, CentralRoleActivity.class);
161 | break;
162 |
163 | }
164 |
165 | if (intent != null) {
166 | startActivity(intent);
167 | }
168 | }
169 |
170 |
171 | private void enableNavigation() {
172 | mPeripheralButton.setEnabled(true);
173 | mCentralButton.setEnabled(true);
174 | }
175 |
176 |
177 | private void showErrorText(int string) {
178 | Snackbar snackbar = Snackbar.make(mPeripheralButton, string, Snackbar.LENGTH_LONG);
179 | snackbar.show();
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/PeripheralAdvertiseService.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.app.Service;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothManager;
6 | import android.bluetooth.le.AdvertiseCallback;
7 | import android.bluetooth.le.AdvertiseData;
8 | import android.bluetooth.le.AdvertiseSettings;
9 | import android.bluetooth.le.BluetoothLeAdvertiser;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.os.Handler;
13 | import android.os.IBinder;
14 | import android.os.ParcelUuid;
15 | import android.util.Log;
16 |
17 | import java.util.concurrent.TimeUnit;
18 |
19 | /**
20 | * Created by itanbarpeled on 28/01/2018.
21 | */
22 |
23 | public class PeripheralAdvertiseService extends Service {
24 |
25 |
26 | /**
27 | * A global variable to let AdvertiserFragment check if the Service is running without needing
28 | * to start or bind to it.
29 | * This is the best practice method as defined here:
30 | * https://groups.google.com/forum/#!topic/android-developers/jEvXMWgbgzE
31 | */
32 | public static boolean running = false;
33 |
34 | private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
35 | private AdvertiseCallback mAdvertiseCallback;
36 | private Handler mHandler;
37 | private Runnable timeoutRunnable;
38 |
39 | /**
40 | * Length of time to allow advertising before automatically shutting off. (10 minutes)
41 | */
42 | private long TIMEOUT = TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES);
43 |
44 | @Override
45 | public void onCreate() {
46 | running = true;
47 | initialize();
48 | startAdvertising();
49 | setTimeout();
50 | super.onCreate();
51 | }
52 |
53 | @Override
54 | public void onDestroy() {
55 | /**
56 | * Note that onDestroy is not guaranteed to be called quickly or at all. Services exist at
57 | * the whim of the system, and onDestroy can be delayed or skipped entirely if memory need
58 | * is critical.
59 | */
60 | running = false;
61 | stopAdvertising();
62 | mHandler.removeCallbacks(timeoutRunnable);
63 | stopForeground(true);
64 | super.onDestroy();
65 | }
66 |
67 | /**
68 | * Required for extending service, but this will be a Started Service only, so no need for
69 | * binding.
70 | */
71 | @Override
72 | public IBinder onBind(Intent intent) {
73 | return null;
74 | }
75 |
76 | /**
77 | * Get references to system Bluetooth objects if we don't have them already.
78 | */
79 | private void initialize() {
80 | if (mBluetoothLeAdvertiser == null) {
81 | BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
82 | if (bluetoothManager != null) {
83 | BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
84 | if (bluetoothAdapter != null) {
85 | mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
86 | }
87 | }
88 | }
89 | }
90 |
91 | /**
92 | * Starts a delayed Runnable that will cause the BLE Advertising to timeout and stop after a
93 | * set amount of time.
94 | */
95 | private void setTimeout(){
96 | mHandler = new Handler();
97 | timeoutRunnable = new Runnable() {
98 | @Override
99 | public void run() {
100 | stopSelf();
101 | }
102 | };
103 | mHandler.postDelayed(timeoutRunnable, TIMEOUT);
104 | }
105 |
106 | /**
107 | * Starts BLE Advertising.
108 | */
109 | private void startAdvertising() {
110 |
111 | Log.d(MainActivity.TAG, "Service: Starting Advertising");
112 |
113 | if (mAdvertiseCallback == null) {
114 | AdvertiseSettings settings = buildAdvertiseSettings();
115 | AdvertiseData data = buildAdvertiseData();
116 | mAdvertiseCallback = new SampleAdvertiseCallback();
117 |
118 | if (mBluetoothLeAdvertiser != null) {
119 | mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);
120 | }
121 | }
122 | }
123 |
124 |
125 | /**
126 | * Stops BLE Advertising.
127 | */
128 | private void stopAdvertising() {
129 | if (mBluetoothLeAdvertiser != null) {
130 | mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
131 | mAdvertiseCallback = null;
132 | }
133 | }
134 |
135 | /**
136 | * Returns an AdvertiseData object which includes the Service UUID and Device Name.
137 | */
138 | private AdvertiseData buildAdvertiseData() {
139 |
140 | /**
141 | * Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements.
142 | * This includes everything put into AdvertiseData including UUIDs, device info, &
143 | * arbitrary service or manufacturer data.
144 | * Attempting to send packets over this limit will result in a failure with error code
145 | * AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the
146 | * onStartFailure() method of an AdvertiseCallback implementation.
147 | */
148 |
149 | AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
150 | //dataBuilder.addServiceUuid(Constants.SERVICE_UUID);
151 | dataBuilder.addServiceUuid(ParcelUuid.fromString(Constants.HEART_RATE_SERVICE_UUID.toString()));
152 | dataBuilder.setIncludeDeviceName(true);
153 |
154 | /* For example - this will cause advertising to fail (exceeds size limit) */
155 | //String failureData = "asdghkajsghalkxcjhfa;sghtalksjcfhalskfjhasldkjfhdskf";
156 | //dataBuilder.addServiceData(Constants.SERVICE_UUID, failureData.getBytes());
157 |
158 | return dataBuilder.build();
159 | }
160 |
161 | /**
162 | * Returns an AdvertiseSettings object set to use low power (to help preserve battery life)
163 | * and disable the built-in timeout since this code uses its own timeout runnable.
164 | */
165 | private AdvertiseSettings buildAdvertiseSettings() {
166 | AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
167 | settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);
168 | settingsBuilder.setTimeout(0);
169 | return settingsBuilder.build();
170 | }
171 |
172 | /**
173 | * Custom callback after Advertising succeeds or fails to start. Broadcasts the error code
174 | * in an Intent to be picked up by AdvertiserFragment and stops this Service.
175 | */
176 | private class SampleAdvertiseCallback extends AdvertiseCallback {
177 |
178 | @Override
179 | public void onStartFailure(int errorCode) {
180 | super.onStartFailure(errorCode);
181 | Log.d(MainActivity.TAG, "Advertising failed");
182 | stopSelf();
183 | }
184 |
185 | @Override
186 | public void onStartSuccess(AdvertiseSettings settingsInEffect) {
187 | super.onStartSuccess(settingsInEffect);
188 | Log.d(MainActivity.TAG, "Advertising successfully started");
189 | }
190 | }
191 |
192 |
193 | }
194 |
195 |
--------------------------------------------------------------------------------
/app/src/main/java/itan/com/bluetoothle/PeripheralRoleActivity.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 | import android.bluetooth.BluetoothGatt;
5 | import android.bluetooth.BluetoothGattCharacteristic;
6 | import android.bluetooth.BluetoothGattDescriptor;
7 | import android.bluetooth.BluetoothGattServer;
8 | import android.bluetooth.BluetoothGattServerCallback;
9 | import android.bluetooth.BluetoothGattService;
10 | import android.bluetooth.BluetoothManager;
11 | import android.content.Context;
12 | import android.content.Intent;
13 | import android.os.Bundle;
14 | import android.util.Log;
15 | import android.view.View;
16 | import android.widget.Button;
17 | import android.widget.RadioGroup;
18 | import android.widget.Switch;
19 |
20 | import java.util.Arrays;
21 | import java.util.HashSet;
22 |
23 | import static itan.com.bluetoothle.Constants.BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID;
24 | import static itan.com.bluetoothle.Constants.HEART_RATE_SERVICE_UUID;
25 | import static itan.com.bluetoothle.Constants.SERVER_MSG_FIRST_STATE;
26 | import static itan.com.bluetoothle.Constants.SERVER_MSG_SECOND_STATE;
27 |
28 |
29 | /**
30 | This activity represents the Peripheral/Server role.
31 | Bluetooth communication flow:
32 | 1. advertise [peripheral]
33 | 2. scan [central]
34 | 3. connect [central]
35 | 4. notify [peripheral]
36 | 5. receive [central]
37 | */
38 | public class PeripheralRoleActivity extends BluetoothActivity implements View.OnClickListener {
39 |
40 | private BluetoothGattService mSampleService;
41 | private BluetoothGattCharacteristic mSampleCharacteristic;
42 |
43 | private BluetoothManager mBluetoothManager;
44 | private BluetoothGattServer mGattServer;
45 | private HashSet mBluetoothDevices;
46 |
47 | private Button mNotifyButton;
48 | private Switch mEnableAdvertisementSwitch;
49 | private RadioGroup mCharacteristicValueSwitch;
50 |
51 |
52 |
53 | @Override
54 | protected void onCreate(Bundle savedInstanceState) {
55 | super.onCreate(savedInstanceState);
56 |
57 | mNotifyButton = (Button) findViewById(R.id.button_notify);
58 | mEnableAdvertisementSwitch = (Switch) findViewById(R.id.advertise_switch);
59 | mCharacteristicValueSwitch = (RadioGroup) findViewById(R.id.color_switch);
60 |
61 |
62 | mNotifyButton.setOnClickListener(this);
63 | mEnableAdvertisementSwitch.setOnClickListener(this);
64 | mCharacteristicValueSwitch.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
65 | @Override
66 | public void onCheckedChanged(RadioGroup group, int checkedId) {
67 | setCharacteristic(checkedId);
68 | }
69 | });
70 |
71 | setGattServer();
72 | setBluetoothService();
73 | }
74 |
75 | @Override
76 | protected int getLayoutId() {
77 | return R.layout.activity_peripheral_role;
78 | }
79 |
80 |
81 | @Override
82 | public void onClick(View view) {
83 |
84 | switch(view.getId()) {
85 |
86 | case R.id.advertise_switch:
87 | Switch switchToggle = (Switch) view;
88 | if (switchToggle.isChecked()) {
89 | startAdvertising();
90 | } else {
91 | stopAdvertising();
92 | }
93 | break;
94 |
95 |
96 | case R.id.button_notify:
97 | notifyCharacteristicChanged();
98 | break;
99 |
100 | }
101 | }
102 |
103 |
104 | @Override
105 | protected int getTitleString() {
106 | return R.string.peripheral_screen;
107 | }
108 |
109 |
110 | /**
111 | * Starts BLE Advertising by starting {@code PeripheralAdvertiseService}.
112 | */
113 | private void startAdvertising() {
114 | // TODO bluetooth - maybe bindService? what happens when closing app?
115 | startService(getServiceIntent(this));
116 | }
117 |
118 |
119 | /**
120 | * Stops BLE Advertising by stopping {@code PeripheralAdvertiseService}.
121 | */
122 | private void stopAdvertising() {
123 | stopService(getServiceIntent(this));
124 | mEnableAdvertisementSwitch.setChecked(false);
125 | }
126 |
127 | private void setGattServer() {
128 |
129 | mBluetoothDevices = new HashSet<>();
130 | mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
131 |
132 | if (mBluetoothManager != null) {
133 | mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
134 | } else {
135 | showMsgText(R.string.error_unknown);
136 | }
137 | }
138 |
139 | private void setBluetoothService() {
140 |
141 | // create the Service
142 | mSampleService = new BluetoothGattService(HEART_RATE_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
143 |
144 | /*
145 | create the Characteristic.
146 | we need to grant to the Client permission to read (for when the user clicks the "Request Characteristic" button).
147 | no need for notify permission as this is an action the Server initiate.
148 | */
149 | mSampleCharacteristic = new BluetoothGattCharacteristic(BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
150 | setCharacteristic(); // set initial state
151 |
152 | // add the Characteristic to the Service
153 | mSampleService.addCharacteristic(mSampleCharacteristic);
154 |
155 | // add the Service to the Server/Peripheral
156 | if (mGattServer != null) {
157 | mGattServer.addService(mSampleService);
158 | }
159 | }
160 |
161 |
162 | private void setCharacteristic() {
163 | setCharacteristic(R.id.color_option_1);
164 | }
165 |
166 | /*
167 | update the value of Characteristic.
168 | the client will receive the Characteristic value when:
169 | 1. the Client user clicks the "Request Characteristic" button
170 | 2. teh Server user clicks the "Notify Client" button
171 |
172 | value - can be between 0-255 according to:
173 | https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml
174 | */
175 | private void setCharacteristic(int checkedId) {
176 | /*
177 | done each time the user changes a value of a Characteristic
178 | */
179 | int value = checkedId == R.id.color_option_1 ? SERVER_MSG_FIRST_STATE : SERVER_MSG_SECOND_STATE;
180 | mSampleCharacteristic.setValue(getValue(value));
181 | }
182 |
183 | private byte[] getValue(int value) {
184 | return new byte[]{(byte) value};
185 | }
186 |
187 | /*
188 | send to the client the value of the Characteristic,
189 | as the user requested to notify.
190 | */
191 | private void notifyCharacteristicChanged() {
192 | /*
193 | done when the user clicks the notify button in the app.
194 | indicate - true for indication (acknowledge) and false for notification (un-acknowledge).
195 | */
196 | boolean indicate = (mSampleCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE;
197 |
198 | for (BluetoothDevice device : mBluetoothDevices) {
199 | if (mGattServer != null) {
200 | mGattServer.notifyCharacteristicChanged(device, mSampleCharacteristic, indicate);
201 | }
202 | }
203 | }
204 |
205 | /**
206 | * Returns Intent addressed to the {@code PeripheralAdvertiseService} class.
207 | */
208 | private Intent getServiceIntent(Context context) {
209 | return new Intent(context, PeripheralAdvertiseService.class);
210 | }
211 |
212 |
213 | private final BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
214 |
215 | @Override
216 | public void onConnectionStateChange(BluetoothDevice device, final int status, int newState) {
217 |
218 | super.onConnectionStateChange(device, status, newState);
219 |
220 | String msg;
221 |
222 | if (status == BluetoothGatt.GATT_SUCCESS) {
223 |
224 | if (newState == BluetoothGatt.STATE_CONNECTED) {
225 |
226 | mBluetoothDevices.add(device);
227 |
228 | msg = "Connected to device: " + device.getAddress();
229 | Log.v(MainActivity.TAG, msg);
230 | showMsgText(msg);
231 |
232 | } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
233 |
234 | mBluetoothDevices.remove(device);
235 |
236 | msg = "Disconnected from device";
237 | Log.v(MainActivity.TAG, msg);
238 | showMsgText(msg);
239 | }
240 |
241 | } else {
242 | mBluetoothDevices.remove(device);
243 |
244 | msg = getString(R.string.status_error_when_connecting) + ": " + status;
245 | Log.e(MainActivity.TAG, msg);
246 | showMsgText(msg);
247 |
248 | }
249 | }
250 |
251 |
252 | @Override
253 | public void onNotificationSent(BluetoothDevice device, int status) {
254 | super.onNotificationSent(device, status);
255 | Log.v(MainActivity.TAG, "Notification sent. Status: " + status);
256 | }
257 |
258 |
259 | @Override
260 | public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
261 |
262 | super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
263 |
264 | if (mGattServer == null) {
265 | return;
266 | }
267 |
268 | Log.d(MainActivity.TAG, "Device tried to read characteristic: " + characteristic.getUuid());
269 | Log.d(MainActivity.TAG, "Value: " + Arrays.toString(characteristic.getValue()));
270 |
271 | mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
272 | }
273 |
274 |
275 | @Override
276 | public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
277 |
278 | super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
279 |
280 | Log.v(MainActivity.TAG, "Characteristic Write request: " + Arrays.toString(value));
281 |
282 | mSampleCharacteristic.setValue(value);
283 |
284 | if (responseNeeded) {
285 | mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
286 | }
287 |
288 | }
289 |
290 | @Override
291 | public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
292 |
293 | super.onDescriptorReadRequest(device, requestId, offset, descriptor);
294 |
295 | if (mGattServer == null) {
296 | return;
297 | }
298 |
299 | Log.d(MainActivity.TAG, "Device tried to read descriptor: " + descriptor.getUuid());
300 | Log.d(MainActivity.TAG, "Value: " + Arrays.toString(descriptor.getValue()));
301 |
302 | mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, descriptor.getValue());
303 | }
304 |
305 | @Override
306 | public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
307 | BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded,
308 | int offset,
309 | byte[] value) {
310 |
311 | super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
312 |
313 | Log.v(MainActivity.TAG, "Descriptor Write Request " + descriptor.getUuid() + " " + Arrays.toString(value));
314 |
315 | // int status = BluetoothGatt.GATT_SUCCESS;
316 | // if (descriptor.getUuid() == CLIENT_CHARACTERISTIC_CONFIGURATION_UUID) {
317 | // BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
318 | // boolean supportsNotifications = (characteristic.getProperties() &
319 | // BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0;
320 | // boolean supportsIndications = (characteristic.getProperties() &
321 | // BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0;
322 | //
323 | // if (!(supportsNotifications || supportsIndications)) {
324 | // status = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
325 | // } else if (value.length != 2) {
326 | // status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
327 | // } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
328 | // status = BluetoothGatt.GATT_SUCCESS;
329 | // mCurrentServiceFragment.notificationsDisabled(characteristic);
330 | // descriptor.setValue(value);
331 | // } else if (supportsNotifications &&
332 | // Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
333 | // status = BluetoothGatt.GATT_SUCCESS;
334 | // mCurrentServiceFragment.notificationsEnabled(characteristic, false /* indicate */);
335 | // descriptor.setValue(value);
336 | // } else if (supportsIndications &&
337 | // Arrays.equals(value, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)) {
338 | // status = BluetoothGatt.GATT_SUCCESS;
339 | // mCurrentServiceFragment.notificationsEnabled(characteristic, true /* indicate */);
340 | // descriptor.setValue(value);
341 | // } else {
342 | // status = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
343 | // }
344 | // } else {
345 | // status = BluetoothGatt.GATT_SUCCESS;
346 | // descriptor.setValue(value);
347 | // }
348 | // if (responseNeeded) {
349 | // mGattServer.sendResponse(device, requestId, status,
350 | // /* No need to respond with offset */ 0,
351 | // /* No need to respond with a value */ null);
352 | // }
353 |
354 | }
355 | };
356 |
357 |
358 | }
359 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_arrow_back_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/drawable-mdpi/ic_arrow_back_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_arrow_back_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/drawable-xhdpi/ic_arrow_back_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_arrow_back_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/drawable-xxhdpi/ic_arrow_back_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/color_option_1.xml:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
4 |
5 |
6 |
7 |
10 |
11 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/color_option_2.xml:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
4 |
5 |
6 |
7 |
10 |
11 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_central_role.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
45 |
46 |
47 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_device_connect.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
20 |
25 |
26 |
27 |
32 |
33 |
34 |
39 |
40 |
41 |
47 |
48 |
49 |
54 |
55 |
56 |
61 |
62 |
63 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
20 |
25 |
26 |
27 |
38 |
39 |
40 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_peripheral_role.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
24 |
25 |
31 |
32 |
37 |
38 |
43 |
44 |
50 |
51 |
56 |
57 |
58 |
59 |
60 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_device_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
27 |
28 |
29 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BluetoothLE
3 | Choose the device role:
4 | Peripheral/Server
5 | Central/Client
6 | scan
7 | Advertise Device
8 | Notify Client
9 | Peripheral/Server screen
10 | Central/Client screen
11 | Bluetooth is not supported on this device.
12 | Bluetooth Advertisements are not supported on this device.
13 | User declined to enable Bluetooth.
14 | User declined to permit ACCESS_COARSE_LOCATION.
15 | Scanning already started.
16 | Scanning for
17 | seconds.
18 | No devices found - refresh to try again.
19 | Advertising stopped due to timeout.
20 | Unknown error.
21 | connect
22 | Connected
23 | Disconnected
24 | Central Connection screen
25 | Connection Status
26 | Connected Device Name
27 | Server Characteristic value
28 | An error occurred when connecting to a device. Error Code
29 | Choose color
30 | Request Characteristic
31 | Characteristic value received: %1$d
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/test/java/itan/com/bluetoothle/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package itan.com.bluetoothle;
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() throws Exception {
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 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.1'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jan 28 13:15:28 IST 2018
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-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/screenshots/1.png
--------------------------------------------------------------------------------
/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/screenshots/2.png
--------------------------------------------------------------------------------
/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/screenshots/3.png
--------------------------------------------------------------------------------
/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/screenshots/4.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_20180515-1521351_LI.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/screenshots/Screenshot_20180515-1521351_LI.jpg
--------------------------------------------------------------------------------
/screenshots/submarine_polygon_LI.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itanbp/android-ble-peripheral-central/7be1191deaaec28f6301a4e573a5cf06b7f98426/screenshots/submarine_polygon_LI.jpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------