GATT callback fails to register
Solution 1:
I finally figured this problem out. The device I am using is a Samsung Galaxy S4 and the actual problem (thanks Wibble for guidance in your answer, but you are slightly off in your conclusion) appears to be a threading issue.
In Wibble's answer, he stated that adding a button to connect fixed his issue. I started wondering why that matters, and I also can connect and disconnect fine during an entire session without a GUI button using background worker threads. As soon as I force close my application, restart it, and try to connect, I start getting the error "Failed to register callback." and nothing works any more. I almost pulled my hair out over this one :)
See my post in Samsung's forums for more detail on my exact issues.
Solution: To get around this issue, just make sure you run any BLE interaction code (device#connectGatt, connect, disconnect, etc) code in the UIThread (with a handler, local service, or Activity#runOnUiThread). Follow this rule of thumb and you will hopefully avoid this dreadful problem.
Deep in our library, I only had access to the application context. You can create a handler from a context that will post to the main thread by using new Handler(ctx.getMainLooper());
If you face other connection problems, deploy the sample app in samples\android-18\legacy\BluetoothLeGatt
and see if that application works. That was kind of my baseline for realizing BLE does actually work with my peripheral, and gave me hope that if I dug enough in our library I would eventually find the answer.
EDIT: I did not see this 'Failed to register callback' issue on the Nexus 4, Nexus 5, or Nexus 7 2013 when using background threads to perform BLE operations. It may just be an issue in Samsungs 4.3 implementation.
Solution 2:
So, My problem was running it from a recursive service. connectGatt worked fine with lollipop but older versions returned null. running on a main thread solved the problem. This is my solution:
public void connectToDevice( String deviceAddress) {
mDeviceAddress = deviceAddress;
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
if (device != null) {
mGatt = device.connectGatt(getApplicationContext(), true, mGattCallback);
scanLeDevice(false);// will stop after first device detection
}
}
});
}
Solution 3:
I can also confirm that Lo-Tan is the answer to check first. I've tested a lot of devices, some of them are behaving well when you run from a secondary thread. Some may block after a while, the behaviour is unpredicted.
Here is the list of things to do:
Maker sure you use new Handler(Looper.getMainLooper()).post(new Runnable) on any gatt operation (connect, disconnect, close) but also on the scanner operations (startScan, stopScan etc.).
-
There is race condition for direct connection on Android 6 (or maybe 5) so try to connect gatt like this:
new Handler(getContext().get().getMainLooper()).post(() -> { if (CommonHelper.isNOrAbove()) { connectedGatt = connectedBLEDevice.connectGatt(context.get(), true, gattCallback, BluetoothDevice.TRANSPORT_AUTO); Timber.tag("HED-BT").d("Connecting BLE after N"); } else { try { Method connectGattMethod = connectedBLEDevice.getClass().getMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class); connectedGatt = (BluetoothGatt) connectGattMethod.invoke(connectedBLEDevice, context.get(), false, gattCallback, BluetoothDevice.TRANSPORT_AUTO); Timber.tag("HED-BT").d("Connecting BLE before N"); } catch (Exception e) { failedConnectingBLE(); } } });
When disconnecting the gatt, call disconnect() first and close() after in the GattCallback routine.