I want to send BLE data in the image format given below.Advertising Data packet.
I am just sending test data1 in byte format but as shown in image I have to send data 0x22 length of first advertisement data type at 0x11 i have send length of second data type and so on. So how i should set it according to the image.
public class MainActivity extends AppCompatActivity {
private Button mAdvertiseButton,stopAdvertiseButton;
private static final String TAG = "BLEApp";
private BluetoothAdapter mBluetoothAdapter;
public static final int REQUEST_ENABLE_BT = 1;
private String Device_Name="Abc";
private String Device_Id ="0x34Abc";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdvertiseButton=(Button) findViewById(R.id.adv);
stopAdvertiseButton=(Button) findViewById(R.id.stopadv);
mAdvertiseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (savedInstanceState == null)
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
.getAdapter();
// Is Bluetooth supported on this device?
if (mBluetoothAdapter != null) {
// Is Bluetooth turned on?
if (mBluetoothAdapter.isEnabled()) {
// Are Bluetooth Advertisements supported on this device?
if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {
// Everything is supported and enabled, load the method
advertise();
} else {
// Bluetooth Advertisements are not supported.
showErrorText(R.string.bt_ads_not_supported);
}
} else {
// Prompt user to turn on Bluetooth (logic continues in onActivityResult()).
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
} else {
// Bluetooth is not supported.
showErrorText(R.string.bt_not_supported);
}
}
});
stopAdvertiseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// stopAdv();
}
});
}
#RequiresApi(api = Build.VERSION_CODES.O)
private void stopAdv() {
AdvertiseCallback callback= null;
BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
advertiser.stopAdvertising(null);
}
private void advertise() {
BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
AdvertisingSetParameters parameters = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
parameters = (new AdvertisingSetParameters.Builder())
.setLegacyMode(true) // True by default, but set here as a reminder.
.setConnectable(false)
.setInterval(AdvertisingSetParameters.INTERVAL_HIGH)
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM)
.build();
}
byte[] manufacturerData = new byte[] {
// 0x12, 0x34,
// 0x56, 0x66,
0x41,0x4d,0x4f,0x4c
};
String testData = "abcdefghij";
byte[] testData1=testData.getBytes();
ParcelUuid pUuid = new ParcelUuid( UUID.fromString("CDB7950D-73F1-4D4D-8E47-C090502DBD63"));
AdvertiseData data = (new AdvertiseData.Builder())
.addManufacturerData(1, testData1)
.setIncludeDeviceName(true)
.build();
//.addServiceData( pUuid, "Data".getBytes(Charset.forName("UTF-8") ) )
AdvertisingSetCallback callback= null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
callback = new AdvertisingSetCallback() {
#Override
public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {
super.onAdvertisingSetStarted(advertisingSet, txPower, status);
Log.d(TAG, "onAdvertisingSetStarted(): txPower:" + txPower + " , status: "
+ status);
AdvertisingSet currentAdvertisingSet = advertisingSet;
// After onAdvertisingSetStarted callback is called, you can modify the
// advertising data and scan response data:
currentAdvertisingSet.setAdvertisingData(new AdvertiseData.Builder().
addManufacturerData(67, testData1)
.setIncludeDeviceName(true)
.setIncludeTxPowerLevel(true).build());
// Wait for onAdvertisingDataSet callback...
ParcelUuid pUuid = new ParcelUuid(UUID.randomUUID());
currentAdvertisingSet.setScanResponseData(new
AdvertiseData.Builder().addServiceUuid(pUuid).build());
Log.d(TAG,"UUID"+pUuid);
//
// Wait for onScanResponseDataSet callback...
Log.d(TAG, data.toString() + status);
// currentAdvertisingSet.setScanResponseData(new AdvertiseData.Builder());
}
#Override
public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
super.onAdvertisingSetStopped(advertisingSet);
Log.d(TAG, "onAdvertisingSetStopped():");
}
#Override
public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) {
super.onAdvertisingEnabled(advertisingSet, enable, status);
}
#Override
public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
super.onAdvertisingDataSet(advertisingSet, status);
Log.d(TAG, "onAdvertisingDataSet() :status:" + status);
}
#Override
public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {
super.onScanResponseDataSet(advertisingSet, status);
Log.d(TAG, "onScanResponseDataSet(): status:" + status);
}
};
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
advertiser.startAdvertisingSet(parameters, data, null, null, null, callback);
Toast.makeText(MainActivity.this, "Data"+data, Toast.LENGTH_LONG).show();
}
// When done with the advertising:
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// advertiser.stopAdvertisingSet(callback);
// }
//
}
Android provides high-level API for building your BLE advertise message payload, so you only need to add the values of each handles (an handle is a triplet of length-dataType-data) of the message in the image.
For example to add a service (second handle of the message) you don't have to set the length (0x11) and the data type (0x07) but only use addServiceUuid of the AdvertiseData.Builder, the SDK will set the length and type for you. This is valid also for the last handle (the manufacturer data), you have to use the addManufacturerData.
For the first handle (the flags) you can't set with the AdvertiseData.Builder but the AdvertisingSetParameters.Builder(), here. But according this answer the limited discoverable mode is no longer supported
Related
I'm new here. Anyways I've been scratching my head at this issue I have. I created a service that runs WiFiP2pHelper to discover nearby devices and retrieve WifiP2pDnsSdServiceInfo. I tested it on multiple phones, and they seem to work fine. If it works fine, it should return logs like these:
2020-08-12 13:32:23.449 24345-24345/com.example.acts D/WiFi P2P Helper: Service Request Added
2020-08-12 13:32:23.449 24345-24345/com.example.acts D/WiFi P2P Helper: Discover Services Successful
2020-08-12 13:32:23.473 24345-24345/com.example.acts D/WiFi P2P Helper: Local Service Added
Now I made the service to run in the background (even when the application is killed). It seems to work fine on the devices I've tested except any that runs on Android 10. I suspect it's something with Android 10 that causes WiFiP2pManager or WiFi P2P itself not to run in the background. But I'm not entirely sure if it's Android 10 causing it (I only got 2 Android 10 devices that were tested on). It returns the following logs:
2020-08-12 13:33:23.517 24345-24345/com.example.acts D/WiFi P2P Helper: Service Request Added
2020-08-12 13:33:23.517 24345-24345/com.example.acts D/WiFi P2P Helper: Error with P2P 0
2020-08-12 13:33:23.518 24345-24345/com.example.acts D/WiFi P2P Helper: Failed to Add Local Service
2020-08-12 13:33:23.518 24345-24345/com.example.acts E/WiFi P2P Helper: 0
As you can see, "Error with P2P 0" and "Failed to Add Local Service" points to the discoverServices and addLocalService. Below is the code. Sorry to dump it all here. Any help would really be appreciated! Thanks!
public class WiFiP2PHelper extends Service {
WifiP2pManager manager;
WifiP2pManager.Channel channel;
WifiP2pDnsSdServiceRequest serviceRequest;
IntentFilter intentFilter;
String iid;
private String baseServiceName = "ACTS";
private String serviceName = "";
final HashMap<String, String> buddies = new HashMap<String, String>();
final String TAG = "WiFi P2P Helper";
Handler handler = new Handler();
Runnable runnable;
int delay = 15000;
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onCreate() {
super.onCreate();
}
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
channel = manager.initialize(this, getMainLooper(), null);
intentFilter = new IntentFilter();
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
getUser();
startRegistration();
discoverService();
startMyOwnForeground();
return START_STICKY;
}
#RequiresApi(api = Build.VERSION_CODES.O)
private void startMyOwnForeground() {
String NOTIFICATION_CHANNEL_ID = "com.example.acts";
String channelName = "My Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("Hello World")
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(1333, notification);
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void getUser() {
List<User> users = User.getUser();
int count = users.size();
if (count > 0) {
User loggedInUser = users.get(0);
iid = loggedInUser.Id;
serviceName = baseServiceName + "_" + loggedInUser.Id;
Log.e("User", iid);
} else {
//uId = "User_Unregistered";
serviceName = baseServiceName + "_User_Unregistered";
Log.e("User in else", serviceName);
}
}
public String getWFDMacAddress() {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ntwInterface : interfaces) {
if (ntwInterface.getName().equalsIgnoreCase("p2p0")) {
byte[] byteMac = ntwInterface.getHardwareAddress();
if (byteMac == null) {
return null;
}
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < byteMac.length; i++) {
strBuilder.append(String.format("%02X:", byteMac[i]));
}
if (strBuilder.length() > 0) {
strBuilder.deleteCharAt(strBuilder.length() - 1);
}
return strBuilder.toString().toLowerCase();
}
}
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
return null;
}
public int findOpenSocket() throws IOException {
// Initialize a server socket on the next available port.
ServerSocket serverSocket = new ServerSocket(0);
// Store the chosen port.
int port = serverSocket.getLocalPort();
serverSocket.close();
return port;
}
private void startRegistration() {
manager.clearLocalServices(channel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.e(TAG, "onSuccess: Sucessss");
int port = 3030;
try {
port = findOpenSocket();
} catch (IOException e) {
e.printStackTrace();
}
Map record = new HashMap();
record.put("listenport", String.valueOf(port));
record.put("buddyname", iid);
record.put("available", "visible");
WifiP2pDnsSdServiceInfo serviceInfo =
WifiP2pDnsSdServiceInfo.newInstance(serviceName, "_presence._tcp", record);
manager.addLocalService(channel, serviceInfo, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.d(TAG, "Local Service Added");
}
#Override
public void onFailure(int arg0) {
Log.d(TAG, "Failed to Add Local Service");
Log.e(TAG, String.valueOf(arg0));
}
});
}
#Override
public void onFailure(int arg0) {
Log.d(TAG, "P2P Unsupported");
}
});
}
private void discoverService() {
WifiP2pManager.DnsSdTxtRecordListener txtListener = new WifiP2pManager.DnsSdTxtRecordListener() {
#Override
public void onDnsSdTxtRecordAvailable(
String fullDomain, Map record, WifiP2pDevice device) {
Log.d(TAG, "DnsSdTxtRecord available - " + record.toString());
buddies.put(device.deviceAddress, (String) record.get("buddyname"));
}
};
WifiP2pManager.DnsSdServiceResponseListener servListener = new WifiP2pManager.DnsSdServiceResponseListener() {
#Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType,
WifiP2pDevice resourceType) {
resourceType.deviceName = buddies
.containsKey(resourceType.deviceAddress) ? buddies
.get(resourceType.deviceAddress) : resourceType.deviceName;
String uId = instanceName.replace(baseServiceName + "_", "");
Log.d(TAG, "Received " + uId);
/*Discovered device1 = new Discovered();
device1.discoveredthreeID = uId;
Discovered.insertTransactionToSQLite(device1);*/
}
};
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
manager.addServiceRequest(channel,
serviceRequest,
new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.d(TAG, "Service Request Added");
}
#Override
public void onFailure(int code) {
Log.d(TAG, "Failed to Add Service Request");
}
});
manager.setDnsSdResponseListeners(channel, servListener, txtListener);
manager.discoverServices(channel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.d(TAG, "Discover Services Successful");
}
#Override
public void onFailure(int code) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
if (code == WifiP2pManager.P2P_UNSUPPORTED) {
Log.d(TAG, "P2P isn't supported on this device.");
} else if (code == WifiP2pManager.ERROR) {
Log.d(TAG, "Error with P2P " + code);
} else if (code == WifiP2pManager.BUSY) {
Log.d(TAG, "P2P is Busy " + code);
}
}
});
}
}
Hi everyone who has the same issue as me. I found the solution to this. Basically, Android 10 (API 29) separates location permissions to the normal location permission and background location permission. You'll need to get your application to prompt for background permission (since it's not allowed by default).
Add these to your AndroidManifest.xml file
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
and in my MainActivity onCreate method I added a check to see if the device is on Android 10 (API 29) and if the permissions have already been granted
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) ||
(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 1);
}
} else {
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
}
If anyone else has a better way of implementing permission checks, please share it here. I'd like to learn as well. Thanks!
I have 2 Android applications. One of them advertises a UUID via bluetooth, and provides write characteristics. The other one scans for a device advertising this UUID, connects to it, and then writes to its 2 characteristics.
Basically, I need the device/app that is advertising the UUID (I'll call it the "server app" now) to always know when the other device/app is present, as well as its UUID that I am writing to. I have to have 2 write characteristics for the UUID, because it's too long to fit into 1, so I write the first half of the UUID in the first characteristic, and the second half in the second characteristic.
So the flow of the apps are as such:
The "server app" advertises a UUID
The other app scans for the advertising UUID
The devices come within Bluetooth range
The app that is scanning finds the advertising UUID, and connects to the device.
The app then writes its own UUID to the 2 characteristics.
The "server app" receives the UUID and displays it on the screen.
The app continuously writes its UUID to the 2 characteristics while within Bluetooth range, even after being separated out of range and then brought back within range.
What I have working is the first 6 steps. I can't get step 7 to work.
The problem that I have, is after the UUID is written to the 2 characteristics, it won't write to it anymore. So the "server app" won't know that the other device/app is present anymore or not. I have to terminate the app (that does the scanning) and restart it so that it can connect to the other device/app and write to the characteristics again.
I need to continuously write to the characteristics when they are near each other (with some delay in between is fine). Even after the devices are separated in distance greater than the Bluetooth range, and then brought back together. I wrote these apps in Xamarin, but I think it applies to Android in general. Here is my code for the app that advertises the UUID:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
private static Java.Util.UUID AdvertiseUUID = Java.Util.UUID.FromString("59ed0f55-e984-4fc2-9403-b1e64269ec5e");
private static Java.Util.UUID ServiceUUID = Java.Util.UUID.FromString("89c9a1f2-5c38-492d-b8e9-67830268682e");
private static Java.Util.UUID WriteCharacteristic1UUID = Java.Util.UUID.FromString("92515af8-8d70-40a7-b5b1-5a5bd624e5a0");
private static Java.Util.UUID WriteCharacteristic2UUID = Java.Util.UUID.FromString("71d640cb-bb78-45bd-ae26-614fead76efe");
BluetoothLeAdvertiser advertiser;
BluetoothGattServer gattServer;
BluetoothGattService service;
MyAdvertiseCallback callback = new MyAdvertiseCallback();
private static string writeText1;
private static string writeText2;
private static List<PassengerDevice> passengerDevices = new List<PassengerDevice>();
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
AndroidBluetoothServer();
}
private async Task AndroidBluetoothServer()
{
try
{
CreateGattServer();
advertiser = BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser;
var advertiseBuilder = new AdvertiseSettings.Builder();
var parameters = advertiseBuilder.SetConnectable(true)
.SetAdvertiseMode(AdvertiseMode.LowLatency)
.SetTxPowerLevel(AdvertiseTx.PowerHigh)
.Build();
AdvertiseData data = (new AdvertiseData.Builder()).AddServiceUuid(new ParcelUuid(AdvertiseUUID)).Build();
advertiser.StartAdvertising(parameters, data, callback);
}
catch(Exception e)
{
}
}
private void CreateGattServer()
{
service = new BluetoothGattService(ServiceUUID, GattServiceType.Primary);
BluetoothGattCharacteristic writeCharacteristic1 = new BluetoothGattCharacteristic(WriteCharacteristic1UUID, GattProperty.WriteNoResponse, GattPermission.Write);
BluetoothGattCharacteristic writeCharacteristic2 = new BluetoothGattCharacteristic(WriteCharacteristic2UUID, GattProperty.WriteNoResponse, GattPermission.Write);
service.AddCharacteristic(writeCharacteristic1);
service.AddCharacteristic(writeCharacteristic2);
var bluetoothManager = GetSystemService(BluetoothService) as BluetoothManager;
gattServer = bluetoothManager.OpenGattServer(BaseContext, new MyGattServerCallback());
gattServer.AddService(service);
}
public class MyGattServerCallback : BluetoothGattServerCallback
{
public override void OnCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, bool preparedWrite, bool responseNeeded, int offset, byte[] value)
{
base.OnCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
var str = System.Text.Encoding.Default.GetString(value);
var passengerDevice = passengerDevices.FirstOrDefault(d => d.Address == device.Address);
if(passengerDevice != null)
{
if (characteristic.Uuid == WriteCharacteristic1UUID)
{
passengerDevice.Text1 = str;
}
else if (characteristic.Uuid == WriteCharacteristic2UUID)
{
passengerDevice.Text2 = str;
passengerDevice.RecievedTimestamp = DateTime.Now;
}
}
else
{
var newDevice = new PassengerDevice();
newDevice.Address = device.Address;
if (characteristic.Uuid == WriteCharacteristic1UUID)
{
newDevice.Text1 = str;
}
else if (characteristic.Uuid == WriteCharacteristic2UUID)
{
newDevice.Text2 = str;
}
passengerDevices.Add(newDevice);
}
App2.Class1.SetText(passengerDevices);
}
}
public class MyAdvertiseCallback : AdvertiseCallback
{
public override void OnStartFailure([GeneratedEnum] AdvertiseFailure errorCode)
{
base.OnStartFailure(errorCode);
}
public override void OnStartSuccess(AdvertiseSettings settingsInEffect)
{
base.OnStartSuccess(settingsInEffect);
}
}
protected override void OnDestroy()
{
try
{
if(advertiser != null)
{
advertiser.StopAdvertising(callback);
advertiser.Dispose();
advertiser = null;
}
if (service != null)
{
service.Dispose();
service = null;
}
if (gattServer != null)
{
gattServer.Close();
gattServer.Dispose();
gattServer = null;
}
}
catch(Exception e)
{
}
finally
{
base.OnDestroy();
}
}
}
And here is my code for the app that scans for the UUID and writes to the characteristics:
public class AndroidBluetooth
{
private static Java.Util.UUID AdvertiseUUID = Java.Util.UUID.FromString("59ed0f55-e984-4fc2-9403-b1e64269ec5e");
private static Java.Util.UUID ServiceUUID = Java.Util.UUID.FromString("89c9a1f2-5c38-492d-b8e9-67830268682e");
private static Java.Util.UUID WriteCharacteristic1UUID = Java.Util.UUID.FromString("92515af8-8d70-40a7-b5b1-5a5bd624e5a0");
private static Java.Util.UUID WriteCharacteristic2UUID = Java.Util.UUID.FromString("71d640cb-bb78-45bd-ae26-614fead76efe");
private ScanCallback _scanCallback;
private ScanSettings _scanSettings;
private ScanFilter _scanFilter;
private MyBluetoothGattCallback _gattCallback;
private static ParcelUuid _parcelUuid;
public static bool IsStarted { get; private set; }
public void StartAndroidBluetoothClient()
{
IsStarted = true;
_parcelUuid = new ParcelUuid(AdvertiseUUID);
_gattCallback = new MyBluetoothGattCallback();
_scanCallback = new MyScanCallback() { GattCallback = _gattCallback };
_scanFilter = new ScanFilter.Builder().SetServiceUuid(new ParcelUuid(AdvertiseUUID))
.Build();
_scanSettings = new ScanSettings.Builder().SetScanMode(Android.Bluetooth.LE.ScanMode.LowLatency)
.Build();
BluetoothAdapter.DefaultAdapter.BluetoothLeScanner.StartScan(new List<ScanFilter>() { _scanFilter }, _scanSettings, _scanCallback);
}
public class MyScanCallback : ScanCallback
{
public MyBluetoothGattCallback GattCallback { get; set; }
public override void OnScanResult([GeneratedEnum] ScanCallbackType callbackType, ScanResult result)
{
base.OnScanResult(callbackType, result);
result.Device.ConnectGatt(Android.App.Application.Context, true, GattCallback);
}
public override void OnScanFailed([GeneratedEnum] ScanFailure errorCode)
{
base.OnScanFailed(errorCode);
}
}
public class MyBluetoothGattCallback : BluetoothGattCallback
{
public BluetoothGatt Gatt { get; set; }
public override void OnConnectionStateChange(BluetoothGatt gatt, [GeneratedEnum] GattStatus status, [GeneratedEnum] ProfileState newState)
{
base.OnConnectionStateChange(gatt, status, newState);
// It is Success the first time, but then never again until I kill the app and restart it
if (status == GattStatus.Success)
{
Gatt = gatt;
gatt.DiscoverServices();
}
}
public override void OnServicesDiscovered(BluetoothGatt gatt, [GeneratedEnum] GattStatus status)
{
base.OnServicesDiscovered(gatt, status);
if (status == GattStatus.Success)
{
var service = gatt.GetService(ServiceUUID);
if (service != null)
{
var wc1 = service.GetCharacteristic(WriteCharacteristic1UUID);
if (wc1 != null)
{
wc1.SetValue("71d640cb-bb78-45bd");
gatt.WriteCharacteristic(wc1);
}
}
}
}
public override void OnCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, [GeneratedEnum] GattStatus status)
{
base.OnCharacteristicWrite(gatt, characteristic, status);
if (status != GattStatus.Success)
{
// try again
if (characteristic.Uuid.Equals(WriteCharacteristic1UUID))
{
var service = gatt.GetService(ServiceUUID);
var wc1 = service.GetCharacteristic(WriteCharacteristic1UUID);
if (wc1 != null)
{
wc1.SetValue("71d640cb-bb78-45bd");
gatt.WriteCharacteristic(wc1);
}
}
else if (characteristic.Uuid.Equals(WriteCharacteristic2UUID))
{
var service = gatt.GetService(ServiceUUID);
var wc2 = service.GetCharacteristic(WriteCharacteristic2UUID);
if (wc2 != null)
{
wc2.SetValue("-ae26-614fead76efe");
gatt.WriteCharacteristic(wc2);
}
}
return;
}
if (characteristic.Uuid.Equals(WriteCharacteristic1UUID))
{
// now send the second text
var service = gatt.GetService(ServiceUUID);
var wc2 = service.GetCharacteristic(WriteCharacteristic2UUID);
if (wc2 != null)
{
wc2.SetValue("-ae26-614fead76efe");
gatt.WriteCharacteristic(wc2);
}
}
}
}
public void Close()
{
if(_gattCallback.Gatt != null)
{
// Some devices (or Android versions) will disconnect automatically when the app closes, but some won't and then you'll have to power off the phone to make sure the hardware is disconnected, and you won't be able to connect again until you do.
_gattCallback.Gatt.Close();
_gattCallback.Gatt.Disconnect();
_gattCallback.Gatt = null;
}
IsStarted = false;
}
}
I write to the 2 characteristics, one after the other, and then it never writes to them again until I kill and restart the app...how can I make it continuously write to the characteristics when the devices are within range?
You have some big question there, but I read through it all and I'll give you some pointers on where to go. For a full app most of those stuff would be IMHO in separate classes, taking care of just 1 responsibility, but I'll try my best:
" I need the (...) "server app" (...) to always know when the other device/app is present,"
for that you can/should use callback from BluetoothGattServerCallback.onConnectionStateChange() . With that you can know when a device connects and disconnects without needing this convoluted "always write" logic.
from what I understood on the client app you want:
while(true) {
while(not found) {
scan()
}
connect()
while(is connected) {
write characteristic 1
write characteristic 2
}
}
but from your code it seems you're missing the while parts. You're certainly missing inside OnCharacteristicWrite the part to keep repeating. Something like this:
// if I just wrote the first
if (characteristic.Uuid.Equals(WriteCharacteristic1UUID))
{
// write the sencond
var service = gatt.GetService(ServiceUUID);
var wc2 = service.GetCharacteristic(WriteCharacteristic2UUID);
if (wc2 != null)
{
wc2.SetValue("-ae26-614fead76efe");
gatt.WriteCharacteristic(wc2);
}
}
// if we just wrote the second
else if (characteristic.Uuid.Equals(WriteCharacteristic2UUID))
{
//write the 1st again
... insert here code that writes the 1st characteristic
}
It seems to it's missing some disconnection handling code. This should goinside the OnConnectionStateChange.
You have to do something like this:
if (status != GattStatus.Success || newState != CONNECTED)
{
// we're not connected, close the gatt
try{ gatt.disconnect() } catch(exception) { }
gatt.close()
}
else
{
Gatt = gatt;
gatt.DiscoverServices();
}
Android have some limitations on this gatt, so it's very important to disconnect and close it, or else you'll run into some system limitations.
I've never used this auto-connect option true, but it seems to me that you never stopped scanning. That means that you'll keep receiving callbacks in OnScanResult, so you keep asking to connect again and again.
You should certainly make some flag that device is already found and there's no need to connect again. Like:
onScanResul(...
if(!connectedOrConnectin) {
connectedOrConnectin = true
-> connect
// make sure to clear the flag after disconnection.
}
Also, you might want to not use the "auto-connect" flag to True (https://issuetracker.google.com/issues/37071781). It seems that you will get a much faster connection if trying to connect by the other method.
I hope it helps.
I have an android app to connect to a BLE device and write to it. I can successfully connect, read and write to it. As a part of testing, we are trying different disconnection scenarios.
Sometimes, if BLE device disconnects the connection, I get the connection change as disconnect with status value as 19. Also if there is any bond error, status equals 22. If I programmatically disconnect the connection, this status gives me 0. But none of these states except 0 are specified in android documentation.
Posting a sample BluetoothGattCallback
private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(TAG, "onConnectionStateChange status: "+status+", newState: "+newState);
/*i need to know the possible values for this status variable*/
if(newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else {
gatt.close();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "onServicesDiscovered service discovered");
}
};
Does anyone face this same problem and sorted out the list of statuses. I need to know the possible values for status variable in onConnectionStateChange method
Here is the list of codes i have
Programmatically disconnected - 0
Device went out of range - 8
Disconnected by device - 19
Issue with bond - 22
Device not found - 133(some phone it gives 62)
I have tested disconnect scenario's in 5.0.2, 5.1, 6.0 and 6.0.1. But only found this bond issue code in 6.0.1 android version.
Sorry to bring up an old question, but here is the solution for many of the problems i've had with Bluetooth (BLE) 4.0. Sorry again for the big classes below but be sure they are needed and no method there is irrelevant or unused.
public abstract class AbstractBluetoothBroadcaster extends BroadcastReceiver {
protected static final String LOG_TAG = BluetoothLowEnergy.LOG_TAG;
protected BluetoothLowEnergy bluetoothLowEnergy;
public AbstractBluetoothBroadcaster(BluetoothLowEnergy bluetoothLowEnergy, String action){
super();
this.bluetoothLowEnergy = bluetoothLowEnergy;
IntentFilter intentFilterStateChange = new IntentFilter(action);
intentFilterStateChange.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
this.bluetoothLowEnergy.getActivity().registerReceiver(this, intentFilterStateChange);
}
public void onDestroy(){
this.bluetoothLowEnergy.getActivity().unregisterReceiver(this);
}
}
public class BluetoothBondStateBroadcaster extends AbstractBluetoothBroadcaster {
private BluetoothLowEnergy bluetoothLowEnergy;
private boolean deviceBonded;
public BluetoothBondStateBroadcaster(BluetoothLowEnergy bluetoothLowEnergy) {
super(bluetoothLowEnergy, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
this.bluetoothLowEnergy = bluetoothLowEnergy;
this.deviceBonded = false;
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID())) {
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
switch (state) {
case BluetoothDevice.BOND_NONE:
Log.d(LOG_TAG, " NOT BONDED - dev " + bluetoothDevice.getAddress());
this.deviceBonded = false;
break;
case BluetoothDevice.BOND_BONDING:
Log.d(LOG_TAG, " BONDING ... - dev " + bluetoothDevice.getAddress());
break;
case BluetoothDevice.BOND_BONDED:
Log.d(LOG_TAG, " BONDED - dev " + bluetoothDevice.getAddress());
deviceBonded = true;
bluetoothLowEnergy.onBluetoothBonded();
break;
default:
break;
}
}
}
public void resetDeviceBonded(){
this.deviceBonded = false;
}
public boolean isDeviceBonded() {
return deviceBonded;
}
}
public class BluetoothPairingBroadcaster extends AbstractBluetoothBroadcaster {
private String devicePIN;
public BluetoothPairingBroadcaster(BluetoothLowEnergy bluetoothLowEnergy){
super(bluetoothLowEnergy, BluetoothDevice.ACTION_PAIRING_REQUEST);
this.devicePIN = "";
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int pairingType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID()) &&
!getDevicePIN().isEmpty()) {
if (pairingType == BluetoothDevice.PAIRING_VARIANT_PIN){
bluetoothDevice.setPin(getDevicePIN().getBytes());
Log.d(LOG_TAG," Auto-entering pin - " + getDevicePIN());
bluetoothDevice.createBond();
Log.d(LOG_TAG," pin entered and request sent...");
abortBroadcast();
}
}
}
public void setDevicePIN(String pin){
this.devicePIN = pin;
}
public String getDevicePIN(){
return this.devicePIN ;
}
}
public class BluetoothLowEnergy extends BluetoothGattCallback {
// listener that has the methods that the application (activity)
// will use to send / receive data, or to reflect the system state
// in the UI
public interface BluetoothListener {
/**
* Triggered when the scanning has started successfully
*/
void onBluetoothStartScan();
/**
* Triggered when the scanning stops
* #param scanResults results of the scanning
*/
void onBluetoothStopScan(Collection<BluetoothDevice> scanResults);
/**
* Triggered when the device is ready to send/receive data
*/
void onBluetoothConnectionReady();
/**
* Triggered when a bluetooth msg is received
* #param msg message received
*/
void onBluetoothReceiveMsg(String msg);
/**
* Triggered whenever data is send
* #param success true means data was sent fine to the remote device, false otherwise
*/
void onBluetoothSend(String data, boolean success);
/**
* Triggered if no bluetooth is connected, and we need a connection
* to send / receive / discover services
*/
void onBluetoothNotConnected();
}
// custom exceptions
public class BluetoothNotEnabledException extends Exception { }
public class BluetoothLowEnergyNotSupported extends Exception { }
public class BluetoothDeviceNotFound extends Exception { }
// service and characteristic uuids that are going to be used to
// send / receive data between central and peripheral GATTs
private static final String SERVICE_UUID = "FFE0-";
private static final String CHARACTERISTIC_UUID = "FFE1-";
// timeout for bluetooth scan (in ms)
public static final int SCAN_TIMEOUT = 5000;
// BLE LOG TAG
public static final String LOG_TAG = "BLUETOOTH_BLE";
// model
private boolean bluetoothScanning;
private boolean bluetoothConnected;
private Map<String, BluetoothDevice> bluetoothScanResults;
// gui
private Activity activity;
// bluetooth
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bluetoothLeScanner;
private ScanCallback bluetoothScanCallback;
private BluetoothGatt bluetoothGatt;
private BluetoothGattCharacteristic characteristic;
public BluetoothLowEnergy(Activity activity, BluetoothListener bluetoothListener){
this.activity = activity;
this.bluetoothListener = bluetoothListener;
// this keeps track of the scanning and connection states
this.bluetoothScanning = this.bluetoothConnected = false;
// keeps track of the scanning results
this.bluetoothScanResults = new HashMap<>();
// set bluetooth pairing request and bonded callback
// these broadcasters will be responsible to detect and validate
// the bonded state of your device
this.pairingRequestBroadcaster = new BluetoothPairingBroadcaster(this);
this.bondedBroadcaster = new BluetoothBondStateBroadcaster(this);
// set the scan callback methods that will add results to
// this.bluetoothScanResults map
this.bluetoothScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
addScanResult(result);
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result: results) {
addScanResult(result);
}
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(LOG_TAG, "Scan Failed with code " + errorCode);
}
private void addScanResult(ScanResult result) {
BluetoothDevice device = result.getDevice();
String deviceAddress = device.getAddress();
bluetoothScanResults.put(deviceAddress, device);
Log.d(LOG_TAG, "Found device " + deviceAddress);
}
};
// Use this to determine whether BLE is supported on the device.
if (!this.activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
throw new BluetoothLowEnergyNotSupported();
}
}
/**
* This method should be called when the activity is destroyed
*/
public void onDestroy(){
this.bondedBroadcaster.onDestroy();
this.pairingRequestBroadcaster.onDestroy();
this.disconnect();
}
/**
* This method is called when we finish pairing/bonding to the device
*/
public void onBluetoothBonded(){
// if we have the services already discovered, then we can
// send/receive data, to do so we call the bluetooth listener below
if (servicesDiscovered){
this.bluetoothListener.onBluetoothConnectionReady();
// if we know we have a connection established, then we can
// discover services
} else if (bluetoothConnected){
bluetoothGatt.discoverServices();
}
}
/**
* This method is called whenever a connection is established or a disconnection happens
*/
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
BluetoothDevice bluetoothDevice = gatt.getDevice();
// if these conditions == true, then we have a disconnect
if ( status == BluetoothGatt.GATT_FAILURE ||
status != BluetoothGatt.GATT_SUCCESS ||
newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Disconnected from %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
this.disconnect();
// if these conditions == true, then we have a successful connection
} else if (newState == BluetoothProfile.STATE_CONNECTED) {
bluetoothConnected = true;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Connected to %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
// this sleep is here to avoid TONS of problems in BLE, that occur whenever we start
// service discovery immediately after the connection is established
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
gatt.discoverServices();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
// BEGIN - find the service and characteristic that we want (defined as a static attribute
// of the BluetoothLowEnergy class)
Log.d(LOG_TAG, "Discovering services ...");
BluetoothGattService service = null;
for (BluetoothGattService serv: gatt.getServices()){
Log.d(LOG_TAG, "Found service " + serv.getUuid().toString());
if (serv.getUuid().toString().toUpperCase().contains(SERVICE_UUID)){
service = serv;
Log.d(LOG_TAG, "---> Selected service " + serv.getUuid().toString());
break;
}
}
if (service == null){
return;
}
for (BluetoothGattCharacteristic charac: service.getCharacteristics()){
Log.d(LOG_TAG, "Found characteristic " + charac.getUuid().toString());
if (charac.getUuid().toString().toUpperCase().contains(CHARACTERISTIC_UUID)){
this.characteristic = charac;
Log.d(LOG_TAG, "---> Selected characteristic " + charac.getUuid().toString());
break;
}
}
if (this.characteristic == null){
return;
}
Log.d(LOG_TAG, "Setting write and notification to the characteristic ...");
bluetoothAdapter.cancelDiscovery();
// END - find the service and characteristic
// set that we want to write to the selected characteristic and be notified if
// it changes (the remote GATT peripheral sends data to the Android's GATT Center)
this.characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
gatt.setCharacteristicNotification(this.characteristic, true);
// we finished service discovery
this.servicesDiscovered = true;
// if we have paired/bonded then we are ready to send/receive data
if (pairingRequestBroadcaster.getDevicePIN().isEmpty() || bondedBroadcaster.isDeviceBonded()) {
this.bluetoothListener.onBluetoothConnectionReady();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicRead(gatt, charac, status);
restartDisconnectTimeout();
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
try {
String characValue = new String(charac.getValue(), CHARSET)
.replaceAll(DATA_FILTER_REGEX,"");
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Read - %s",
characValue
));
if (charac.getUuid().equals(this.characteristic.getUuid())) {
this.bluetoothListener.onBluetoothReceiveMsg(characValue);
}
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Read - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicWrite(gatt, charac, status);
restartDisconnectTimeout();
try {
String characValue = new String(charac.getValue(), CHARSET);
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Write - SUCCESS - %s",
characValue
));
bluetoothListener.onBluetoothSend( characValue, (status == BluetoothGatt.GATT_SUCCESS) );
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Write - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic charac) {
super.onCharacteristicChanged(gatt, charac);
Log.d(LOG_TAG,"Characteristic Changed");
onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS);
}
/**
* Remove pairing/bonding of the device
* #param device Device to remove bonding
*/
public static void removeBond(BluetoothDevice device){
try {
if (device == null){
throw new Exception();
}
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
method.invoke(device, (Object[]) null);
Log.d(LOG_TAG, "removeBond() called");
Thread.sleep(600);
Log.d(LOG_TAG, "removeBond() - finished method");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Clears the GATT services cache, so that new services can be discovered
* #param bluetoothGatt GATT Client to clear service's discovery cache
*/
public static void refresh(BluetoothGatt bluetoothGatt){
try {
Method method = bluetoothGatt.getClass().getMethod("refresh", (Class[]) null);
method.invoke(bluetoothGatt, (Object[]) null);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Connect to the GATT Peripheral device
* #param uuid GATT Peripheral address / mac / uuid to connect to
* #param pin PIN to authenticate and pair to the device
*/
public void connect(String uuid, String pin) throws BluetoothNotEnabledException, BluetoothDeviceNotFound {
checkBluetooth();
// do not connect twice
if (this.isConnected()){
return;
}
// get device
BluetoothDevice device = this.bluetoothScanResults.get(uuid);
if (device == null){
throw new BluetoothDeviceNotFound();
}
this.deviceUUID = uuid;
pairingRequestBroadcaster.setDevicePIN(pin);
removeBond(device);
// create connection to the bluetooth device
bluetoothGatt = device.connectGatt(activity, false, this);
refresh(bluetoothGatt);
}
/**
* Disconnect from BLE device. This method should be called whenever we want to
* close the APP, or the BLE connection.
*/
public void disconnect() {
Log.d(LOG_TAG, "disconnect() - executed");
if (bluetoothGatt != null) {
if (characteristic != null) {
bluetoothGatt.setCharacteristicNotification(characteristic, false);
}
//remove device authorization/ bond/ pairing
removeBond(bluetoothGatt);
// disconnect now
bluetoothGatt.disconnect();
bluetoothGatt.close();
Log.d(LOG_TAG, "disconnect() - bluetoothGatt disconnect happened");
}
bluetoothGatt = null;
characteristic = null;
bluetoothConnected = false;
servicesDiscovered = false;
// set device as not bonded anymore
bondedBroadcaster.resetDeviceBonded();
}
/**
* bluetooth nearby devices scan is on
* #return true if scanning is on, false otherwise
*/
public boolean isScanning(){
return (this.bluetoothScanning);
}
/**
* Check bluetooth system state (on or off)
* #return true if system is on, false otherwise
*/
public boolean isEnabled(){
try {
checkBluetooth();
return bluetoothAdapter.isEnabled();
} catch (BluetoothNotEnabledException e) {
return false;
}
}
/**
* Check bluetooth connection
* #return true if connected, false otherwise
*/
public boolean isConnected(){
return (this.bluetoothConnected);
}
/**
* Start bluetooth scan for nearby devices
* #param filters Scan filters that define what devices to scan for
*/
public void startScan(List<ScanFilter> filters)
throws BluetoothNotEnabledException{
checkBluetooth();
// dont run two scans simultaneously
if (isScanning()) {
return;
}
// disconnect previously connected devices
if (isConnected()) {
this.disconnect();
return;
}
// setup bluetooth scanning settings
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
// start scanning
this.bluetoothScanning = true;
this.bluetoothScanResults.clear();
this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
// Stops scanning after a pre-defined scan period.
Handler bluetoothHandler = new Handler();
bluetoothHandler.postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
}, SCAN_TIMEOUT);
// start scan with default scan callback
this.bluetoothLeScanner.startScan(filters, settings, bluetoothScanCallback);
// we have started successfully the BLE scanning
bluetoothListener.onBluetoothStartScan();
}
/**
* Stop bluetooth scan for nearby devices
*/
public void stopScan(){
if (!bluetoothScanning) {
return;
}
// set app scan state to false
bluetoothScanning = false;
if (bluetoothLeScanner != null) {
bluetoothLeScanner.stopScan(bluetoothScanCallback);
bluetoothLeScanner = null;
}
// we have stopped BLE scanning, call the user's callback
bluetoothListener.onBluetoothStopScan(bluetoothScanResults.values());
}
/**
* Send a message via bluetooth
* #param msg message to send
*/
public void send(String msg) {
if (!bluetoothConnected || characteristic == null){
bluetoothListener.onBluetoothNotConnected();
return;
}
try {
msg = msg.replaceAll(DATA_FILTER_REGEX, "") + TERMINATION_CHAR;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Sending message: %s",
msg));
characteristic.setValue(msg.getBytes(CHARSET));
bluetoothGatt.writeCharacteristic(characteristic);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG,
"BluetoothLowEnergy.send: Failed to convert message string to byte array");
}
}
public String getDeviceUUID(){
return deviceUUID;
}
public Activity getActivity(){
return activity;
}
/**
* Check if bluetooth is enabled and working properly
*/
private void checkBluetooth() throws BluetoothNotEnabledException{
if (bluetoothAdapter == null) {
final BluetoothManager bluetoothManager =
(BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null){
throw new BluetoothNotEnabledException();
}
bluetoothAdapter = bluetoothManager.getAdapter();
}
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
throw new BluetoothNotEnabledException();
}
}
}
The key methods and functions to avoid problems used above are:
Thread.sleep(600)
removeBond(device)
refresh(gatt)
gatt.disconnect()
gatt.close()
In my case I got this response from bluetooth stack because the device was already bonded with my phone. I removed it from my settings and the error 22 vanished.
in aosp (android source code). you can find any error in bluetooth source code, and know the meaning of status code.
the file path is system/bt/stack/include/gatt_api.h
Here's the link: https://android.googlesource.com/platform/system/bt/+/ea7ab70a711e642653dd5922b83aa04a53af9e0e/stack/include/gatt_api.h but it all display by hex.
for example:
hex
Decimal
reason
0x08
8
connection timeout
0x13
19
connection terminate by peer user
0x16
22
connectionterminated by local host
0x22
34
connection fail for LMP response tout
0x85
133
gatt_error
I am developing an android application which requires me to send data to a Bluetooth Low Energy device.After the connection event is successful and after I receive a call back message I want to change the activity and display new GUI where on switch click I want to send data to connected device. The problem is after the activity has changed my BluetoothGatt becomes null and BluetoothGattCharacteristic also becomes null and i am not able to send the data. how can I solve this issue? Below is my code main class which onResume calls the connection activity and connects to first available device and after connection is successful it receives callback message and changes the activity.
public class MainActivity extends Activity implements BluetoothLeUart.Callback {
public TextView messages;
private BluetoothLeUart uart;
public void writeLine(final CharSequence text) {
runOnUiThread(new Runnable() {
#Override
public void run() {
messages.append(text);
messages.append("\n");
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
messages = (TextView) findViewById(R.id.messages);
// Initialize UART.
uart = new BluetoothLeUart(getApplicationContext());
messages.setMovementMethod(new ScrollingMovementMethod());
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
protected void onResume() {
super.onResume();
writeLine("Scanning for devices ...");
uart.registerCallback(this);
uart.connectFirstAvailable();
}
#Override
protected void onStop() {
super.onStop();
uart.unregisterCallback(this);
uart.disconnect();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
// UART Callback event handlers.
// UART Callback event handlers.
#Override
public void onConnected(BluetoothLeUart uart) {
// Called when UART device is connected and ready to send/receive data.
//messages.append("connected2");
writeLine("Connected!");
Intent intent = new Intent(MainActivity.this,SwitchClass.class);
startActivity(intent);
}
}
This is my codes BluetoothLeUart class which does connection activity and gives callback message.
public class BluetoothLeUart extends BluetoothGattCallback implements BluetoothAdapter.LeScanCallback {
// UUIDs for UART service and associated characteristics.
public static UUID UART_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
public static UUID TX_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
public static UUID RX_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
// UUID for the UART BTLE client characteristic which is necessary for notifications.
public static UUID CLIENT_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
// UUIDs for the Device Information service and associated characeristics.
public static UUID DIS_UUID = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb");
public static UUID DIS_MANUF_UUID = UUID.fromString("00002a29-0000-1000-8000-00805f9b34fb");
public static UUID DIS_MODEL_UUID = UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb");
public static UUID DIS_HWREV_UUID = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
public static UUID DIS_SWREV_UUID = UUID.fromString("00002a28-0000-1000-8000-00805f9b34fb");
// Internal UART state.
private Context context;
private WeakHashMap<Callback, Object> callbacks;
private BluetoothAdapter adapter;
private BluetoothGatt gatt;
private BluetoothGattCharacteristic tx;
private BluetoothGattCharacteristic rx;
private boolean connectFirst;
private boolean writeInProgress; // Flag to indicate a write is currently in progress
// Queues for characteristic read (synchronous)
private Queue<BluetoothGattCharacteristic> readQueue;
// Interface for a BluetoothLeUart client to be notified of UART actions.
public interface Callback {
public void onConnected(BluetoothLeUart uart);
public void onConnectFailed(BluetoothLeUart uart);
public void onDisconnected(BluetoothLeUart uart);
public void onReceive(BluetoothLeUart uart, BluetoothGattCharacteristic rx);
public void onDeviceFound(BluetoothDevice device);
public void onDeviceInfoAvailable();
}
public BluetoothLeUart(Context context) {
super();
this.context = context;
this.callbacks = new WeakHashMap<Callback, Object>();
this.adapter = BluetoothAdapter.getDefaultAdapter();
this.gatt = null;
this.tx = null;
this.rx = null;
this.disManuf = null;
this.disModel = null;
this.disHWRev = null;
this.disSWRev = null;
this.disAvailable = false;
this.connectFirst = false;
this.writeInProgress = false;
this.readQueue = new ConcurrentLinkedQueue<BluetoothGattCharacteristic>();
}
// Send data to connected UART device.
public void sendbyte(byte[] data) {
if (tx == null || data == null || data.length == 0) {
// Do nothing if there is no connection or message to send.
return;
}
// Update TX characteristic value. Note the setValue overload that takes a byte array must be used.
tx.setValue(data);
writeInProgress = true; // Set the write in progress flag
gatt.writeCharacteristic(tx);
// ToDo: Update to include a timeout in case this goes into the weeds
while (writeInProgress); // Wait for the flag to clear in onCharacteristicWrite
gatt.readCharacteristic(rx);
}
// Send data to connected UART device.
public void send(String data) {
if (data != null && !data.isEmpty()) {
sendbyte(data.getBytes(Charset.forName("UTF-8")));
}
}
// Register the specified callback to receive UART callbacks.
public void registerCallback(Callback callback) {
callbacks.put(callback, null);
}
// Unregister the specified callback.
public void unregisterCallback(Callback callback) {
callbacks.remove(callback);
}
// Disconnect to a device if currently connected.
public void disconnect() {
if (gatt != null) {
gatt.disconnect();
}
gatt = null;
tx = null;
rx = null;
}
// Stop any in progress UART device scan.
public void stopScan() {
if (adapter != null) {
adapter.stopLeScan(this);
}
}
// Start scanning for BLE UART devices. Registered callback's onDeviceFound method will be called
// when devices are found during scanning.
public void startScan() {
if (adapter != null) {
adapter.startLeScan(this);
}
}
// Connect to the first available UART device.
public void connectFirstAvailable() {
// Disconnect to any connected device.
disconnect();
// Stop any in progress device scan.
stopScan();
// Start scan and connect to first available device.
connectFirst = true;
startScan();
}
// Handlers for BluetoothGatt and LeScan events.
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothGatt.STATE_CONNECTED) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Connected to device, start discovering services.
if (!gatt.discoverServices()) {
// Error starting service discovery.
connectFailure();
}
else {
notifyOnConnected(this);
}
}
else {
// Error connecting to device.
connectFailure();
}
}
else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// Disconnected, notify callbacks of disconnection.
rx = null;
tx = null;
notifyOnDisconnected(this);
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
// Notify connection failure if service discovery failed.
if (status == BluetoothGatt.GATT_FAILURE) {
connectFailure();
return;
}
// Save reference to each UART characteristic.
tx = gatt.getService(UART_UUID).getCharacteristic(TX_UUID);
rx = gatt.getService(UART_UUID).getCharacteristic(RX_UUID);
// Save reference to each DIS characteristic.
disManuf = gatt.getService(DIS_UUID).getCharacteristic(DIS_MANUF_UUID);
disModel = gatt.getService(DIS_UUID).getCharacteristic(DIS_MODEL_UUID);
disHWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_HWREV_UUID);
disSWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_SWREV_UUID);
// Add device information characteristics to the read queue
// These need to be queued because we have to wait for the response to the first
// read request before a second one can be processed (which makes you wonder why they
// implemented this with async logic to begin with???)
readQueue.offer(disManuf);
readQueue.offer(disModel);
readQueue.offer(disHWRev);
readQueue.offer(disSWRev);
// Request a dummy read to get the device information queue going
// gatt.readCharacteristic(disManuf);
// Setup notifications on RX characteristic changes (i.e. data received).
// First call setCharacteristicNotification to enable notification.
if (!gatt.setCharacteristicNotification(rx, true)) {
// Stop if the characteristic notification setup failed.
connectFailure();
return;
}
// Next update the RX characteristic's client descriptor to enable notifications.
BluetoothGattDescriptor desc = rx.getDescriptor(CLIENT_UUID);
if (desc == null) {
// Stop if the RX characteristic has no client descriptor.
connectFailure();
return;
}
desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
if (!gatt.writeDescriptor(desc)) {
// Stop if the client descriptor could not be written.
connectFailure();
return;
}
// Notify of connection completion.
notifyOnConnected(this);
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
notifyOnReceive(this, characteristic);
}
#Override
public void onCharacteristicRead (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//Log.w("DIS", characteristic.getStringValue(0));
// Check if there is anything left in the queue
BluetoothGattCharacteristic nextRequest = readQueue.poll();
if(nextRequest != null){
// Send a read request for the next item in the queue
// gatt.readCharacteristic(nextRequest);
}
else {
// We've reached the end of the queue
disAvailable = true;
// notifyOnDeviceInfoAvailable();
}
}
else {
//Log.w("DIS", "Failed reading characteristic " + characteristic.getUuid().toString());
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
// Log.d(TAG,"Characteristic write successful");
}
writeInProgress = false;
}
#Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
// Stop if the device doesn't have the UART service.
if (!parseUUIDs(scanRecord).contains(UART_UUID)) {
//main.writeLine("Parse UUID failed...");
//main.messages.append("Parse UUID failed...");
return;
}
// Connect to first found device if required.
if (connectFirst) {
// Stop scanning for devices.
stopScan();
// Prevent connections to future found devices.
connectFirst = false;
// Connect to device.
gatt = device.connectGatt(context, true, this);
}
}
// Private functions to simplify the notification of all callbacks of a certain event.
private void notifyOnConnected(BluetoothLeUart uart) {
for (Callback cb : callbacks.keySet()) {
if (cb != null) {
cb.onConnected(uart);
}
}
}
private List<UUID> parseUUIDs(final byte[] advertisedData) {
List<UUID> uuids = new ArrayList<UUID>();
int offset = 0;
while (offset < (advertisedData.length - 2)) {
int len = advertisedData[offset++];
if (len == 0)
break;
// main.writeLine(advertisedData.toString());
int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
//main.writeLine("case 02,03...");
// main.messages.append("case 02,03...");
while (len > 1) {
int uuid16 = advertisedData[offset++];
uuid16 += (advertisedData[offset++] << 8);
len -= 2;
uuids.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16)));
}
break;
case 0x06:// Partial list of 128-bit UUIDs
case 0x07:// Complete list of 128-bit UUIDs
// Loop through the advertised 128-bit UUID's.
//main.writeLine("case 06,07...");
// main.messages.append("case 06,07...");
while (len >= 16) {
try {
// Wrap the advertised bits and order them.
ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
long mostSignificantBit = buffer.getLong();
long leastSignificantBit = buffer.getLong();
uuids.add(new UUID(leastSignificantBit,
mostSignificantBit));
} catch (IndexOutOfBoundsException e) {
// Defensive programming.
//Log.e(LOG_TAG, e.toString());
continue;
} finally {
// Move the offset to read the next uuid.
offset += 15;
len -= 16;
}
}
break;
default:
//main.writeLine("case default...");
// main.messages.append("case default");
offset += (len - 1);
break;
}
}
return uuids;
}
}
Here is my class from where I send the data
public class SwitchClass extends Activity {
public TextView messages;
public Switch Switch1;
public byte[] switchData = {'U','1','1','1','0','0','2','Z'};
private BluetoothLeUart uart;
public void writeLine(final CharSequence text) {
runOnUiThread(new Runnable() {
#Override
public void run() {
messages.append(text);
messages.append("\n");
//messages.setText("anirudh");
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.switchlayout);
Switch1 = (Switch) findViewById(R.id.switch1);
uart = new BluetoothLeUart(getApplicationContext());
Switch1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
switchString = new String(switchData);
writeLine(switchString);
// send this array 8 bytes to BLE
sendData(switchString);
}
}
});
public void sendData(String sendVal) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(sendVal.toCharArray(), 0, 8);
uart.send(stringBuilder.toString());
}
You are creating a service from this activity and you are destroying it onStop
so your service is now been disconnected , so automatically you cant get bluetoothgatt and adapter for it
I suggest you to have child fragment inside you activity so your service will stay alive and you can have as many fragment you want to display the thing !!
Hopes this will help you !!
you can visit https://github.com/captain-miao/bleYan, It's a simple BLE library and example.
I also got exactly the same error like yours. In my case I comment out or deleted the following line under onServiceDiscover method. It works suddenly.It may not be the answer, Hope it will give you some clue to solve.
disManuf = gatt.getService(DIS_UUID).getCharacteristic(DIS_MANUF_UUID);
disModel = gatt.getService(DIS_UUID).getCharacteristic(DIS_MODEL_UUID);
disHWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_HWREV_UUID);
disSWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_SWREV_UUID);
readQueue.offer(disManuf);
readQueue.offer(disModel);
readQueue.offer(disHWRev);
readQueue.offer(disSWRev);
You must set to synchronized your BluetoothGatt Object like
public void setBluetoothGatt(BluetoothGatt gatt) {
synchronized (this) {
this.bluetoothGatt = gatt;
}
}
because BluetoothGatt throw DeadObject Exception when you change the Activity
I am trying to get data from Onyx2(Fingertip Oximeter) via Bluetooth using Health Device Profile and sample, which can be found at Android Developers site. But I am getting the following error
E/BluetoothEventLoop.cpp(432): onHealthDeviceConnectionResult: D-Bus error: org.bluez.Error.HealthError (Error getting remote SDP records).
What can be a reason of this problem?
BTW, approximately 1 time out of 50, I get the data.
// Callbacks to handle connection set up and disconnection clean up.
private final BluetoothProfile.ServiceListener mBluetoothServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEALTH) {
mBluetoothHealth = (BluetoothHealth) proxy;
if (Log.isLoggable(TAG, Log.DEBUG))
Log.d(TAG, "onServiceConnected to profile: " + profile);
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEALTH) {
mBluetoothHealth = null;
}
}
};
private final BluetoothHealthCallback mHealthCallback = new BluetoothHealthCallback() {
// Callback to handle application registration and unregistration events. The service
// passes the status back to the UI client.
public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
int status) {
if (status == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE) {
mHealthAppConfig = null;
sendMessage(STATUS_HEALTH_APP_REG, RESULT_FAIL);
} else if (status == BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS) {
mHealthAppConfig = config;
sendMessage(STATUS_HEALTH_APP_REG, RESULT_OK);
} else if (status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE ||
status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) {
sendMessage(STATUS_HEALTH_APP_UNREG,
status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS ?
RESULT_OK : RESULT_FAIL);
}
}
// Callback to handle channel connection state changes.
// Note that the logic of the state machine may need to be modified based on the HDP device.
// When the HDP device is connected, the received file descriptor is passed to the
// ReadThread to read the content.
public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd,
int channelId) {
if (Log.isLoggable(TAG, Log.DEBUG))
Log.d(TAG, String.format("prevState\t%d ----------> newState\t%d",
prevState, newState));
if (prevState == BluetoothHealth.STATE_CHANNEL_CONNECTING &&
newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) {
if (config.equals(mHealthAppConfig)) {
mChannelId = channelId;
sendMessage(STATUS_CREATE_CHANNEL, RESULT_OK);
(new ReadThread(fd)).start();
} else {
sendMessage(STATUS_CREATE_CHANNEL, RESULT_FAIL);
}
} else if (prevState == BluetoothHealth.STATE_CHANNEL_CONNECTING &&
newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED) {
sendMessage(STATUS_CREATE_CHANNEL, RESULT_FAIL);
} else if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED) {
if (config.equals(mHealthAppConfig)) {
sendMessage(STATUS_DESTROY_CHANNEL, RESULT_OK);
} else {
sendMessage(STATUS_DESTROY_CHANNEL, RESULT_FAIL);
}
}
}
};
// Initiates application registration through {#link
// BluetoothHDPService}.
Button registerAppButton = (Button) findViewById(R.id.button_register_app);
registerAppButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
sendMessage(BluetoothHDPService.MSG_REG_HEALTH_APP,
HEALTH_PROFILE_SOURCE_DATA_TYPE);
}
});
// Initiates application unregistration through {#link
// BluetoothHDPService}.
Button unregisterAppButton = (Button) findViewById(R.id.button_unregister_app);
unregisterAppButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
sendMessage(BluetoothHDPService.MSG_UNREG_HEALTH_APP, 0);
}
});
// Initiates channel creation through {#link BluetoothHDPService}. Some
// devices will
// initiate the channel connection, in which case, it is not necessary
// to do this in the
// application. When pressed, the user is asked to select from one of
// the bonded devices
// to connect to.
Button connectButton = (Button) findViewById(R.id.button_connect_channel);
connectButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mAllBondedDevices = (BluetoothDevice[]) mBluetoothAdapter
.getBondedDevices().toArray(new BluetoothDevice[0]);
if (mAllBondedDevices.length > 0) {
int deviceCount = mAllBondedDevices.length;
if (mDeviceIndex < deviceCount)
mDevice = mAllBondedDevices[mDeviceIndex];
else {
mDeviceIndex = 0;
mDevice = mAllBondedDevices[0];
}
String[] deviceNames = new String[deviceCount];
int i = 0;
for (BluetoothDevice device : mAllBondedDevices) {
deviceNames[i++] = device.getName();
}
SelectDeviceDialogFragment deviceDialog = SelectDeviceDialogFragment
.newInstance(deviceNames, mDeviceIndex);
deviceDialog.show(getFragmentManager(), "deviceDialog");
}
}
});
// Initiates channel disconnect through {#link BluetoothHDPService}.
Button disconnectButton = (Button) findViewById(R.id.button_disconnect_channel);
disconnectButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
disconnectChannel();
}
});
registerReceiver(mReceiver, initIntentFilter());
}
// Sets up communication with {#link BluetoothHDPService}.
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mHealthServiceBound = true;
Message msg = Message.obtain(null,
BluetoothHDPService.MSG_REG_CLIENT);
msg.replyTo = mMessenger;
mHealthService = new Messenger(service);
try {
mHealthService.send(msg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to register client to service.");
e.printStackTrace();
I remember that Android HDP demo app had some problem about not closing bluetooth connections when terminated.
One test that you can do to verify this is:
Turn off bluetooth of your android.
Turn it on again.
Pair with the health device (in android settings)
Start your android application.
Try to connect and transfer data with health device.
If these steps works consistently when repeated, i'm almost sure it has something to do with the app not releasing resources like i sad before.