// Original source code: https://github.com/StevenRudenko/BleSensorTag. MIT License (Steven Rudenko)
package com.adafruit.bluefruit.le.connect.ble;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;
public class BleManager implements BleGattExecutor.BleExecutorListener {
// Log
private final static String TAG = BleManager.class.getSimpleName();
// Enumerations
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
// Singleton
private static BleManager mInstance = null;
// Data
private final BleGattExecutor mExecutor = BleGattExecutor.createExecutor(this);
private BluetoothAdapter mAdapter;
private BluetoothGatt mGatt;
// private Context mContext;
private BluetoothDevice mDevice;
private String mDeviceAddress;
private int mConnectionState = STATE_DISCONNECTED;
private BleManagerListener mBleListener;
public static BleManager getInstance(Context context) {
if(mInstance == null)
{
mInstance = new BleManager(context);
}
return mInstance;
}
public int getState() {
return mConnectionState;
}
public BluetoothDevice getConnectedDevice() {return mDevice;}
public String getConnectedDeviceAddress() {
return mDeviceAddress;
}
public void setBleListener(BleManagerListener listener) {
mBleListener = listener;
}
public BleManager(Context context) {
// Init Adapter
//mContext = context.getApplicationContext();
if (mAdapter == null) {
mAdapter = BleUtils.getBluetoothAdapter(context);
}
if (mAdapter == null || !mAdapter.isEnabled()) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
}
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* @param address The device address of the destination device.
* @return Return true if the connection is initiated successfully. The connection result is reported asynchronously through the {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} callback.
*/
public boolean connect(Context context, String address) {
if (mAdapter == null || address == null) {
Log.w(TAG, "connect: BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Get preferences
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
final boolean reuseExistingConnection = sharedPreferences.getBoolean("pref_recycleconnection", false);
if (reuseExistingConnection) {
// Previously connected device. Try to reconnect.
if (mDeviceAddress != null && address.equalsIgnoreCase(mDeviceAddress) && mGatt != null) {
Log.d(TAG, "Trying to use an existing BluetoothGatt for connection.");
if (mGatt.connect()) {
mConnectionState = STATE_CONNECTING;
if (mBleListener != null)
mBleListener.onConnecting();
return true;
} else {
return false;
}
}
} else {
final boolean forceCloseBeforeNewConnection = sharedPreferences.getBoolean("pref_forcecloseconnection", true);
if (forceCloseBeforeNewConnection) {
close();
}
}
mDevice = mAdapter.getRemoteDevice(address);
if (mDevice == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
/*
// Refresh device cache
final boolean refreshDeviceCache = sharedPreferences.getBoolean("pref_refreshdevicecache", true);
if (refreshDeviceCache) {
refreshDeviceCache(); // hack to force refresh the device cache and avoid problems with characteristic services read from cache and not updated
}
*/
Log.d(TAG, "Trying to create a new connection.");
mDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
if (mBleListener != null) {
mBleListener.onConnecting();
}
final boolean gattAutoconnect = sharedPreferences.getBoolean("pref_gattautoconnect", false);
mGatt = mDevice.connectGatt(context, gattAutoconnect, mExecutor);
return true;
}
public void clearExecutor() {
if (mExecutor != null) {
mExecutor.clear();
}
}
/**
* Call to private Android method 'refresh'
* 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.
* http://stackoverflow.com/questions/22596951/how-to-programmatically-force-bluetooth-low-energy-service-discovery-on-android
*/
public boolean refreshDeviceCache(){
try {
BluetoothGatt localBluetoothGatt = mGatt;
Method localMethod = localBluetoothGatt.getClass().getMethod("refresh");
if (localMethod != null) {
boolean result = (Boolean) localMethod.invoke(localBluetoothGatt);
if (result) {
Log.d(TAG, "Bluetooth refresh cache");
}
return result;
}
}
catch (Exception localException) {
Log.e(TAG, "An exception occurred while refreshing device");
}
return false;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} callback.
*/
public void disconnect() {
mDevice = null;
if (mAdapter == null || mGatt == null) {
Log.w(TAG, "disconnect: BluetoothAdapter not initialized");
return;
}
/*
// Refresh device cache before disconnect
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
final boolean refreshDeviceCache = sharedPreferences.getBoolean("pref_refreshdevicecache", true);
if (refreshDeviceCache) {
refreshDeviceCache(); // hack to force refresh the device cache and avoid problems with characteristic services read from cache and not updated
}
*/
// Disconnect
mGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are released properly.
*/
private void close() {
if (mGatt != null) {
mGatt.close();
mGatt = null;
mDeviceAddress = null;
mDevice = null;
}
}
public boolean readRssi() {
if (mGatt != null) {
return mGatt.readRemoteRssi(); // if true: Caller should wait for onReadRssi callback
}
else {
return false; // Rsii read is not available
}
}
public void readCharacteristic(BluetoothGattService service, String characteristicUUID) {
readService(service, characteristicUUID, null);
}
public void readDescriptor(BluetoothGattService service, String characteristicUUID, String descriptorUUID) {
readService(service, characteristicUUID, descriptorUUID);
}
private void readService(BluetoothGattService service, String characteristicUUID, String descriptorUUID) {
if (service != null) {
if (mAdapter == null || mGatt == null) {
Log.w(TAG, "readService: BluetoothAdapter not initialized");
return;
}
mExecutor.read(service, characteristicUUID, descriptorUUID);
mExecutor.execute(mGatt);
}
}
public void writeService(BluetoothGattService service, String uuid, byte[] value)
{
if (service != null) {
if (mAdapter == null || mGatt == null) {
Log.w(TAG, "writeService: BluetoothAdapter not initialized");
return;
}
mExecutor.write(service, uuid, value);
mExecutor.execute(mGatt);
}
}
public void enableNotification(BluetoothGattService service, String uuid, boolean enabled) {
if (service != null) {
if (mAdapter == null || mGatt == null) {
Log.w(TAG, "enableNotification: BluetoothAdapter not initialized");
return;
}
mExecutor.enableNotification(service, uuid, enabled);
mExecutor.execute(mGatt);
}
}
public void enableIndication(BluetoothGattService service, String uuid, boolean enabled) {
if (service != null) {
if (mAdapter == null || mGatt == null) {
Log.w(TAG, "enableNotification: BluetoothAdapter not initialized");
return;
}
mExecutor.enableIndication(service, uuid, enabled);
mExecutor.execute(mGatt);
}
}
// Properties
private int getCharacteristicProperties(BluetoothGattService service, String characteristicUUIDString) {
final UUID characteristicUuid = UUID.fromString(characteristicUUIDString);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(characteristicUuid);
int properties = 0;
if (characteristic != null) {
properties = characteristic.getProperties();
}
return properties;
}
public boolean isCharacteristicReadable(BluetoothGattService service, String characteristicUUIDString) {
final int properties = getCharacteristicProperties(service, characteristicUUIDString);
final boolean isReadable = (properties & BluetoothGattCharacteristic.PROPERTY_READ) != 0;
return isReadable;
}
public boolean isCharacteristicNotifiable(BluetoothGattService service, String characteristicUUIDString) {
final int properties = getCharacteristicProperties(service, characteristicUUIDString);
final boolean isNotifiable = (properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0;
return isNotifiable;
}
// Permissions
private int getDescriptorPermissions(BluetoothGattService service, String characteristicUUIDString, String descriptorUUIDString) {
final UUID characteristicUuid = UUID.fromString(characteristicUUIDString);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(characteristicUuid);
int permissions = 0;
if (characteristic != null) {
final UUID descriptorUuid = UUID.fromString(descriptorUUIDString);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorUuid);
if (descriptor != null) {
permissions = descriptor.getPermissions();
}
}
return permissions;
}
public boolean isDescriptorReadable(BluetoothGattService service, String characteristicUUIDString, String descriptorUUIDString) {
final int permissions = getDescriptorPermissions(service, characteristicUUIDString, descriptorUUIDString);
final boolean isReadable = (permissions & BluetoothGattCharacteristic.PERMISSION_READ) != 0;
return isReadable;
}
/**
* Retrieves a list of supported GATT services on the connected device. This should be
* invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
*
* @return A {@code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices() {
if (mGatt != null) {
return mGatt.getServices();
} else {
return null;
}
}
public BluetoothGattService getGattService(String uuid) {
if (mGatt != null) {
final UUID serviceUuid = UUID.fromString(uuid);
return mGatt.getService(serviceUuid);
} else {
return null;
}
}
public BluetoothGattService getGattService(String uuid, int instanceId) {
if (mGatt != null) {
List<BluetoothGattService> services = getSupportedGattServices();
boolean found = false;
int i=0;
while (i<services.size() && !found) {
BluetoothGattService service = services.get(i);
if (service.getUuid().toString().equalsIgnoreCase(uuid) && service.getInstanceId() == instanceId)
{
found = true;
}
else {
i++;
}
}
if (found) {
return services.get(i);
}
else {
return null;
}
} else {
return null;
}
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
// Log.d(TAG, "onConnectionStateChange status: "+status+ " newState: "+newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
mConnectionState = STATE_CONNECTED;
if (mBleListener != null) {
mBleListener.onConnected();
}
// Attempts to discover services after successful connection.
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
mConnectionState = STATE_DISCONNECTED;
if (mBleListener != null) {
mBleListener.onDisconnected();
}
} else if (newState == BluetoothProfile.STATE_CONNECTING) {
mConnectionState = STATE_CONNECTING;
if (mBleListener != null) {
mBleListener.onConnecting();
}
}
}
// region BleExecutorListener
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
// if (status == BluetoothGatt.GATT_SUCCESS) {
// Call listener
if (mBleListener != null)
mBleListener.onServicesDiscovered();
// }
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onServicesDiscovered status: "+status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
// if (status == BluetoothGatt.GATT_SUCCESS) {
if (mBleListener != null) {
mBleListener.onDataAvailable(characteristic);
}
// }
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onCharacteristicRead status: "+status);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (mBleListener != null) {
mBleListener.onDataAvailable(characteristic);
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
// if (status == BluetoothGatt.GATT_SUCCESS) {
if (mBleListener != null) {
mBleListener.onDataAvailable(descriptor);
}
// }
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onDescriptorRead status: "+status);
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
if (mBleListener != null) {
mBleListener.onReadRemoteRssi(rssi);
}
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "onReadRemoteRssi status: "+status);
}
}
//endregion
public interface BleManagerListener {
void onConnected();
void onConnecting();
void onDisconnected();
void onServicesDiscovered();
void onDataAvailable(BluetoothGattCharacteristic characteristic);
void onDataAvailable(BluetoothGattDescriptor descriptor);
void onReadRemoteRssi(int rssi);
}
}