// 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); } }