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
Related
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
I've been struggling for quite a while now, trying to get my BLE device to communicate with my android app.
First off, here is my full code for BLE handling :
BleCentral.java
import java.util.HashMap;
import java.util.function.Supplier;
import android.util.Log;
import android.content.Context;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothManager;
public class BleCentral
{
private HashMap<String, BluetoothGatt> m_connectedDevices;
public BleCentral()
{
m_connectedDevices = new HashMap<>();
}
private BluetoothAdapter m_GetAdapter(Context ctx)
{
final BluetoothManager bleMgr = (BluetoothManager)(ctx.getSystemService(Context.BLUETOOTH_SERVICE));
BluetoothAdapter adapter = bleMgr.getAdapter();
if (adapter == null || !adapter.isEnabled())
{
Log.e("BLE Central", "BLE either not available or not enabled. Please do something about it.");
return null;
}
return adapter;
}
public BluetoothDevice GetDevice(Context ctx, String address)
{
return m_GetAdapter(ctx).getRemoteDevice(address);
}
public <T extends BlePeripheral>
T Connect(Context ctx, String address, Supplier<T> supplier)
{
BluetoothDevice device = GetDevice(ctx, address);
T result = supplier.get();
m_connectedDevices.put(address, device.connectGatt(ctx, false, result, BluetoothDevice.TRANSPORT_LE));
return result;
}
}
BlePeripheral.java
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattService;
public abstract class BlePeripheral
extends BluetoothGattCallback
{
private final String kCCC_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";
private Handler m_handler;
private boolean m_deviceReady;
private BluetoothGatt m_bluetoothGatt;
private Queue<CmdQueueItem> m_cmdQueue;
private boolean m_cmdQueueProcessing;
// ------------------------------------------------------------------------
// -- Own methods
protected BlePeripheral()
{
m_handler = new Handler();
m_deviceReady = false;
m_bluetoothGatt = null;
m_cmdQueue = new LinkedList<>();
m_cmdQueueProcessing = false;
}
public boolean IsDeviceReady()
{ return m_deviceReady; }
public void EnableNotifications(UUID service, UUID characteristic)
{ EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), true); }
public void DisableNotifications(UUID service, UUID characteristic)
{ EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), false); }
protected void WriteCharacteristic(UUID service, UUID characteristic, byte[] value, boolean requestResponse)
{ EnqueueWriteCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), value, requestResponse); }
protected void ReadCharacteristic(UUID service, UUID characteristic)
{ EnqueueReadCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic)); }
// ------------------------------------------------------------------------
// -- BluetoothGattCallback overrides
#Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState)
{
super.onConnectionStateChange(gatt, status, newState);
final BluetoothDevice device = gatt.getDevice();
switch (status)
{
case 133: /* GATT_ERROR */
Log.e("BLE", "GATT_ERROR");
gatt.close();
try { Thread.sleep(150); }
catch (InterruptedException e) { e.printStackTrace(); }
break;
case 0: /* GATT_SUCCESS */
switch (newState)
{
case BluetoothGatt.STATE_CONNECTED:
Log.i("BLE", "Connected to " + device.getAddress() + " (" + device.getName() + ")");
m_bluetoothGatt = gatt;
int delayWhenBonded = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) ? 2000 : 0;
switch (device.getBondState())
{
case BluetoothDevice.BOND_NONE:
delayWhenBonded = 0;
case BluetoothDevice.BOND_BONDED:
m_handler.postDelayed(new Runnable() {
#Override
public void run() {
boolean result = gatt.discoverServices();
if (!result)
Log.e("BLE", "discoverServices() failed to start");
}
}, delayWhenBonded);
break;
case BluetoothDevice.BOND_BONDING:
Log.i("BLE", "Waiting for bonding to complete");
break;
}
break;
case BluetoothGatt.STATE_DISCONNECTED:
gatt.close();
break;
}
break;
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
{
super.onServicesDiscovered(gatt, status);
if (status == 129 /* GATT_INTERNAL_ERROR */)
{
Log.e("BLE", "Service discovery failed");
gatt.disconnect();
return;
}
final List<BluetoothGattService> services = gatt.getServices();
Log.i("BLE", "Discovered " + services.size() + " services for " + gatt.getDevice().getAddress());
m_deviceReady = SetupDevice(gatt);
if (!m_deviceReady)
Log.e("BLE", "Peripheral does not comply to this device's requirements");
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic)
{
super.onCharacteristicChanged(gatt, characteristic);
Log.i("BLE", "onCharacteristicChanged: " + characteristic.getUuid());
final byte[] value = new byte[characteristic.getValue().length];
System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);
OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
{
super.onCharacteristicRead(gatt, characteristic, status);
ProcessCmdQueue();
Log.i("BLE", "onCharacteristicRead: " + characteristic.getUuid());
final byte[] value = new byte[characteristic.getValue().length];
System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);
OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
{
super.onCharacteristicWrite(gatt, characteristic, status);
ProcessCmdQueue();
Log.i("BLE", "onCharacteristicWrite: " + characteristic.getUuid());
}
#Override
public void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status)
{
super.onDescriptorWrite(gatt, descriptor, status);
ProcessCmdQueue();
final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic();
if(status != 0 /* GATT_SUCCESS */)
{
Log.e("BLE", "WriteDescriptor failed for characteristic " + parentCharacteristic.getUuid());
return;
}
if(descriptor.getUuid().equals(UUID.fromString(kCCC_DESCRIPTOR_UUID)))
{
if(status == 0 /* GATT_SUCCESS */)
{
byte[] value = descriptor.getValue();
if (value != null)
{
if (value[0] != 0)
Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now notifying");
else
Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now NOT notifying");
}
}
}
}
// ------------------------------------------------------------------------
// -- Command Queue implementation
/* An enqueueable write operation - notification subscription or characteristic write */
private class CmdQueueItem
{
BluetoothGattCharacteristic characteristic;
byte[] dataToWrite; // Only used for characteristic write
boolean writeWoRsp; // Only used for characteristic write
boolean enabled; // Only used for characteristic notification subscription
public m_queueItemType type;
}
private enum m_queueItemType
{
SubscribeCharacteristic,
ReadCharacteristic,
WriteCharacteristic
}
/* queues enables/disables notification for characteristic */
public void EnqueueSetNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.enabled = enabled;
m_queueItem.type = m_queueItemType.SubscribeCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/* queues enables/disables notification for characteristic */
public void EnqueueWriteCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean requestResponse)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.dataToWrite = dataToWrite;
m_queueItem.writeWoRsp = !requestResponse;
m_queueItem.type = m_queueItemType.WriteCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/* request to fetch newest value stored on the remote device for particular characteristic */
public void EnqueueReadCharacteristic(BluetoothGattCharacteristic ch)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.type = m_queueItemType.ReadCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/**
* Add a transaction item to transaction queue
* #param m_queueItem
*/
private void EnqueueBleCommand(CmdQueueItem m_queueItem)
{
m_cmdQueue.add(m_queueItem);
// If there is no other transmission processing, go do this one!
if (!m_cmdQueueProcessing)
ProcessCmdQueue();
}
/**
* Call when a transaction has been completed.
* Will process next transaction if queued
*/
private void ProcessCmdQueue()
{
if (m_cmdQueue.size() <= 0)
{
m_cmdQueueProcessing = false;
return;
}
m_cmdQueueProcessing = true;
CmdQueueItem m_queueItem = m_cmdQueue.remove();
switch (m_queueItem.type)
{
case WriteCharacteristic:
writeDataToCharacteristic(m_queueItem.characteristic, m_queueItem.dataToWrite, m_queueItem.writeWoRsp);
break;
case SubscribeCharacteristic:
setNotificationForCharacteristic(m_queueItem.characteristic, m_queueItem.enabled);
break;
case ReadCharacteristic:
requestCharacteristicValue(m_queueItem.characteristic);
break;
}
}
public void requestCharacteristicValue(BluetoothGattCharacteristic ch)
{
if (m_bluetoothGatt == null)
return;
m_bluetoothGatt.readCharacteristic(ch);
}
private void writeDataToCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean writeWoRsp)
{
if (m_bluetoothGatt == null || ch == null)
return;
ch.setValue(dataToWrite);
ch.setWriteType(writeWoRsp ? BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE : BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
m_bluetoothGatt.writeCharacteristic(ch);
}
/* enables/disables notification for characteristic */
private void setNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
{
if (m_bluetoothGatt == null || ch == null)
return;
ch.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
boolean success = m_bluetoothGatt.setCharacteristicNotification(ch, enabled);
if(success)
{
// This is also sometimes required (e.g. for heart rate monitors) to enable notifications/indications
// see: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
BluetoothGattDescriptor descriptor = ch.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if(descriptor != null)
{
if (enabled)
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
else
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
if (m_bluetoothGatt.writeDescriptor(descriptor))
{
Log.i("BLE Peripheral", "SetNotification (Set + CCC) succeeded!");
}
}
else
Log.i("BLE Peripheral", "SetNotification (Set only) succeeded!");
}
else
Log.e("BLE Peripheral", "SetNotification failed!");
}
// ------------------------------------------------------------------------
// -- Abstract methods
protected abstract boolean SetupDevice(BluetoothGatt gatt);
protected abstract void OnUpdate(UUID service, UUID characteristic, final byte[] value);
}
Code to create and connect to a device
BleCentral central = new BleCentral();
m_customDevice = central.Connect(this, deviceMacAddress, () -> new CustomDevice());
CustomDevice is just inheriting the BlePeripheral class, implementing SetupDevice (that checks that all services and characteristics are there) and OnUpdate (that receives new data and handles it).
Now, two things bother me:
When connecting to the device, sometimes it works right away, and sometimes not. If not, I have to go connect to the device through another app such as Bluefruit Connect, then start my app again, and then it will connect ;
When it connects, it goes through service discovery and all, and in the setNotificationForCharacteristic function everything gets called correctly (I get onDescriptorWrite called and all) but I never receive any notification.
Since I'm the one behind the code running on my BLE peripheral, I can guarantee that the characteristic I'm trying to get data from is of the NOTIFY type (and not e.g. INDICATE).
If it can help in any way, the only NOTIFY characteristic there is sends a 56-byte array filled with 14 floats as often as it can. Early prototypes with Web Bluetooth or NativeScript (with the nativescript-bluetooth plugin) showed me that this actually works and in these cases I get results roughly about every 90 milliseconds.
I think I've rewritten this code about 3 times already and I'm getting a bit desperate, so any help going in the right direction is appreciated. :D
Thanks a lot!
Edit: Just for science, I tried to switch the characteristic to a READ one on the device, then spawning a thread reading it every second, instead of waiting for notifications. Well, onCharacteristicRead is called, but the byte array passed to it always has a length of zero...
I found the issue - it was not related to the Java code at all but caused by a hardware fault in my BLE device that caused all value updates to be discarded.
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 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.