How to Programmatically Force Bluetooth Low Energy Service Discovery on Android Without Using Cache

How to programmatically force bluetooth low energy service discovery on Android without using cache

I just had the same problem. If you see the source code of BluetoothGatt.java you can see that there is a method called refresh()

/**
* Clears the internal cache and forces a refresh of the services from the
* remote device.
* @hide
*/
public boolean refresh() {
if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return false;

try {
mService.refreshDevice(mClientIf, mDevice.getAddress());
} catch (RemoteException e) {
Log.e(TAG,"",e);
return false;
}

return true;
}

This method does actually clear the cache from a bluetooth device. But the problem is that we don't have access to it.
But in java we have reflection, so we can access this method. Here is my code to connect a bluetooth device refreshing the cache.

private boolean refreshDeviceCache(BluetoothGatt gatt){
try {
BluetoothGatt localBluetoothGatt = gatt;
Method localMethod = localBluetoothGatt.getClass().getMethod("refresh", new Class[0]);
if (localMethod != null) {
boolean bool = ((Boolean) localMethod.invoke(localBluetoothGatt, new Object[0])).booleanValue();
return bool;
}
}
catch (Exception localException) {
Log.e(TAG, "An exception occurred while refreshing device");
}
return false;
}


public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG,"BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothGatt != null) {
Log.d(TAG,"Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
return true;
} else {
return false;
}
}

final BluetoothDevice device = mBluetoothAdapter
.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}

// We want to directly connect to the device, so we are setting the
// autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(MyApp.getContext(), false, mGattCallback));
refreshDeviceCache(mBluetoothGatt);
Log.d(TAG, "Trying to create a new connection.");
return true;
}

Android BLE stack caching services?

STOP PRESS:

Found an answer to this: 'Android Bluetooth not discovering the characteristic after changing UUID'

It appears that the BLE stack does cache services etc for an indeterminate time, in order to speed up future service discovery. Bit of a problem if the services for a MAC address do change (like mine), but I suppose that is not really expected behaviour (though not forbidden as far as I know). Don't know how long it is cached for, but I noticed that overnight it sorted itself out - then got it wrong on the next change, of course.

There is an internal BluetoothGatt.refresh() method that clears the cache - but it is not published. The link shows how to get at it with reflection, and it does appear to work. On an old Nexus 7 2013 running 6.0.1, that is. Not sure about later OSs as reflection seems to be frowned on by Google these days. About to try it on a Pixel 2 running Android 11.

How to refresh services / clear cache?

Usually Android should not cache not-bonded devices. BUT it ignores the rule.
To refresh the cache, call a hidden methode using reflections.

import java.lang.reflect.Method;

Do this in a method:

try {
// BluetoothGatt gatt
final Method refresh = gatt.getClass().getMethod("refresh");
if (refresh != null) {
refresh.invoke(gatt);
}
} catch (Exception e) {
// Log it
}

Usuage example:

If you see a poblem with the characteristic cache.

  1. Call the method to clear the cache. (Wait a few seconds).
  2. Reconnect (Disconnect -> Connect).

Should be fixed now.

NOTE: The refresh method has no complete callback.

Remove Bluetooth from BLE state

Looking through the source code, it does not appear to be adjustable from the API. It seems like sometimes, the service (Bluetooth Service) will enter a BT LE only state. Even more interestingly if you look at the source code of BluetoothAdapter.getState() on API level 23, the framework treats this "internal state" as a off public state. So maybe this is a transitionary state that takes place first and maybe a few moment later non-LE functionality is enabled (There is no documentation on this just a shot in the dark).

Furthermore, if you look at the source code from API 25 and beyond, the method enable doesn't check state any more and is simply a asynchronous pass through to the underlying Bluetooth Service. Poking around the source code I found two methods regarding enabling/disabling BLE, respectively enableBLE and disableBLE().

This methods are public, but hidden to the outside world. It wouldn't be hard to write some reflection code that will call these methods in a few lines but I'd definitely recommend heavily researching using another approach and using this as a last resort. I say this because enable() will try to turn on Bluetooth Services on the device without the user being explicitly aware of whats taking place. This is a potentially dangerous methods and is included in the public Bluetooth Framework for setting apps and widget. I'd recommend using the "android way" to enable Bluetooth by requesting it from the system. This could also possibly help fix your problem.



Related Topics



Leave a reply



Submit