package com.github.captain_miao.android.ble;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import com.github.captain_miao.android.ble.constant.BleConnectState;
import com.github.captain_miao.android.ble.constant.BleConstants;
import com.github.captain_miao.android.ble.utils.BleLog;
import com.github.captain_miao.android.ble.utils.BleUtils;
import com.github.captain_miao.android.ble.utils.HexUtil;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
public abstract class BaseBleService extends Service implements SimpleScanCallback{
private final static String TAG = BaseBleService.class.getName();
protected final BleServiceHandle mHandler;
private final Messenger mMessenger;
private BluetoothGatt mGatt = null;
public BleConnectState mState = BleConnectState.INITIALED;
private BleScanner mBleScanner;
//Messenger queue
private final List<Messenger> mClients = new LinkedList<>();
protected static final Queue<Object> sWriteQueue = new ConcurrentLinkedQueue<>();
private static boolean sIsWriting = false;
//after discover services call it.
public abstract void onDiscoverServices(final BluetoothGatt gatt);
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
BleLog.i(TAG, "onConnectionStateChange: State = " + BleUtils.getBleConnectStatus(status)
+ " newState = " + BleUtils.getBleConnectStatus(newState));
if (newState == BluetoothProfile.STATE_CONNECTED) {
updateState(BleConnectState.CONNECTED);
//start discoverServices
BleLog.i(TAG, "gatt.discoverServices()");
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_CONNECTING) {
updateState(BleConnectState.CONNECTING);
} else if (newState == BluetoothProfile.STATE_DISCONNECTING) {
updateState(BleConnectState.DISCONNECTING);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//disconnect
sIsWriting = false;
sWriteQueue.clear();
updateState(BleConnectState.DISCONNECTED);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
onDiscoverServices(gatt);
updateState(BleConnectState.SERVICE_IS_DISCOVERED);
} else {
BleUtils.refreshDeviceCache(mGatt);
//ServicesDiscovered: such as 129
if(mState != BleConnectState.SERVICE_IS_NOT_DISCOVERED) {
updateState(mState);
}
}
//MSG_BLE_ID_SERVICES_DISCOVERED
Message msg = Message.obtain();
msg.what = BleConstants.MSG_BLE_ID_SERVICES_DISCOVERED;
msg.arg1 = status;
msg.obj = gatt;
notifyAllBleClients(msg);
BleLog.i(TAG, "onServicesDiscovered: " + BleUtils.getGattStatus(status));
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
BleLog.i(TAG, "onCharacteristicWrite: " + BleUtils.getGattStatus(status));
UUID uuid = characteristic.getUuid();
sendBleMessage(BleConstants.MSG_BLE_ID_CHARACTERISTIC_WRITE, status, uuid);
onNextWrite();
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
BleLog.i(TAG, "onDescriptorWrite: " + BleUtils.getGattStatus(status));
UUID uuid = descriptor.getUuid();
sendBleMessage(BleConstants.MSG_BLE_ID_DESCRIPTOR_WRITE, status, uuid);
onNextWrite();
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
final byte[] data = characteristic.getValue();
BleLog.i(TAG, "onCharacteristicChanged: " + HexUtil.encodeHexStr(data));
UUID uuid = characteristic.getUuid();
sendBleMessage(BleConstants.MSG_BLE_ID_CHARACTERISTIC_NOTIFICATION, BluetoothGatt.GATT_SUCCESS, data, uuid);
onNextWrite();
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
final byte[] data = characteristic.getValue();
BleLog.i(TAG, "onCharacteristicRead: " + HexUtil.encodeHexStr(data));
UUID uuid = characteristic.getUuid();
sendBleMessage(BleConstants.MSG_BLE_ID_CHARACTERISTIC_READ, status, data, uuid);
onNextWrite();
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
final byte[] data = descriptor.getValue();
BleLog.i(TAG, "onCharacteristicRead: " + HexUtil.encodeHexStr(data));
UUID uuid = descriptor.getUuid();
sendBleMessage(BleConstants.MSG_BLE_ID_DESCRIPTOR_READ, status, data, uuid);
onNextWrite();
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
BleLog.i(TAG, "onReliableWriteCompleted: " + BleUtils.getGattStatus(status));
Message msg = Message.obtain();
msg.what = BleConstants.MSG_BLE_ID_RELIABLE_WRITE_COMPLETED;
msg.arg1 = status;
notifyAllBleClients(msg);
onNextWrite();
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
BleLog.i(TAG, "onReadRemoteRssi: " + rssi + " status:" + BleUtils.getGattStatus(status));
Message msg = Message.obtain();
msg.what = BleConstants.MSG_BLE_ID_READ_REMOTE_RSSI;
msg.arg1 = status;
msg.arg2 = rssi;
notifyAllBleClients(msg);
onNextWrite();
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
BleLog.i(TAG, "onMtuChanged: " + BleUtils.getGattStatus(status));
Message msg = Message.obtain();
msg.what = BleConstants.MSG_BLE_ID_MTU_CHANGED;
msg.arg1 = status;
msg.arg2 = mtu;
notifyAllBleClients(msg);
onNextWrite();
}
};
public BaseBleService() {
mHandler = new BleServiceHandle(this);
mMessenger = new Messenger(mHandler);
}
@Override
public IBinder onBind(Intent intent) {
BleLog.i(TAG, "onBind");
return mMessenger.getBinder();
}
synchronized public void updateState(BleConnectState newState) {
if (mState != newState) {
mState = newState;
Message msg = Message.obtain(null, BleConstants.BLE_MSG_ID_CONNECTION_STATE_CHANGED);
if (msg != null) {
msg.obj = mState;
notifyAllBleClients(msg);
}
}
}
/**
* start to scan bluetooth
*/
public void startScan(){
if(mBleScanner == null) {
mBleScanner = new BleScanner(this, this);
}
mBleScanner.startBleScan();
}
/**
* stop to scan bluetooth
*/
public void stopScan(){
if(mBleScanner != null) {
mBleScanner.stopBleScan();
mBleScanner = null;
}
}
/**
* when device is range, directly connect
*/
public boolean directlyConnectDevice(String deviceMac) {
return directlyConnectDevice(deviceMac, false);
}
public boolean directlyConnectDevice(String deviceMac, boolean autoConnect) {
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceMac);
return connectDevice(device, autoConnect);
}
public boolean connectDevice(final BluetoothDevice device, boolean autoConnect) {
mGatt = device.connectGatt(this, autoConnect, mGattCallback);
if(mGatt != null){
mHandler.post(new Runnable() {
@Override
public void run() {
mGatt.connect();
}
});
} else {
BleLog.e(TAG, "serviceConnect mGatt==null");
}
return true;
}
public boolean connectDevice(final BluetoothDevice device) {
return connectDevice(device, false);
}
private synchronized void sendBleMessage(int msgId, int status, byte[] values, UUID uuid){
Message msg = Message.obtain();
msg.what = msgId;
msg.arg1 = status;
msg.obj = values;
Bundle data = new Bundle();
data.putSerializable(BleConstants.BLE_MSG_CHARACTERISTIC_UUID_KEY, uuid);
msg.setData(data);
notifyAllBleClients(msg);
}
private synchronized void sendBleMessage(int msgId, int status, UUID uuid){
Message msg = Message.obtain();
msg.what = msgId;
msg.arg1 = status;
Bundle data = new Bundle();
data.putSerializable(BleConstants.BLE_MSG_CHARACTERISTIC_UUID_KEY, uuid);
msg.setData(data);
notifyAllBleClients(msg);
}
//notify subscriber
public void notifyAllBleClients(Message msg) {
for (int i = mClients.size() - 1; i >= 0; i--) {
Messenger messenger = mClients.get(i);
if (!sendMessage(messenger, msg)) {
mClients.remove(messenger);
}
}
}
private boolean sendMessage(Messenger messenger, Message msg) {
boolean success = true;
try {
messenger.send(msg);
} catch (RemoteException e) {
BleLog.w(TAG, "Lost connection to client" + e);
success = false;
}
return success;
}
protected synchronized boolean writeToCharacteristic(UUID serviceUUID, UUID characteristicUUID, byte[] values) {
BluetoothGattService gattService = mGatt == null ? null : mGatt.getService(serviceUUID);
if(gattService != null) {
BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(characteristicUUID);
if(gattCharacteristic != null) {
gattCharacteristic.setValue(values);
write(gattCharacteristic);
return true;
}
}
return false;
}
/**
* enable notify or disable notify
*/
public void updateCharacteristicNotification(UUID serviceUUID, UUID CharacteristicUUID, UUID descriptorUUID, boolean enable) {
final BluetoothGattService service = mGatt == null ? null : mGatt.getService(serviceUUID);
if (service != null) {
final BluetoothGattCharacteristic readData = service.getCharacteristic(CharacteristicUUID);
mGatt.setCharacteristicNotification(readData, enable);
final BluetoothGattDescriptor config = readData.getDescriptor(descriptorUUID);
if(config != null) {
config.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
: BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
write(config);
}
}
}
public boolean readFromCharacteristic(UUID serviceUUID, UUID CharacteristicUUID){
BluetoothGattService gattService = mGatt.getService(serviceUUID);
if(gattService != null) {
BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(CharacteristicUUID);
if(gattCharacteristic != null) {
mGatt.setCharacteristicNotification(gattCharacteristic, true);
mGatt.readCharacteristic(gattCharacteristic);
return true;
}
}
return false;
}
protected synchronized void write(Object o) {
if (sWriteQueue.isEmpty() && !sIsWriting) {
doWrite(o);
} else {
sWriteQueue.add(o);
}
}
private synchronized void onNextWrite() {
sIsWriting = false;
nextWrite();
}
private synchronized void nextWrite() {
//empty enable write
if(sIsWriting) {
sIsWriting = !sWriteQueue.isEmpty();
}
if (!sWriteQueue.isEmpty() && !sIsWriting) {
doWrite(sWriteQueue.poll());
}
}
private synchronized void doWrite(Object o) {
if (o instanceof BluetoothGattCharacteristic) {
sIsWriting = mGatt.writeCharacteristic((BluetoothGattCharacteristic) o);
} else if (o instanceof BluetoothGattDescriptor) {
sIsWriting = mGatt.writeDescriptor((BluetoothGattDescriptor) o);
} else {
nextWrite();
}
}
public void addClient(Messenger messenger) {
mClients.add(messenger);
}
public void removeClient(Messenger messenger) {
mClients.remove(messenger);
// all clients disconnected release ? TODO: 16/4/19
if(mClients.size() == 0){
release();
}
}
public BluetoothGatt getGatt() {
return mGatt;
}
private synchronized Bundle obtainData(UUID uuid) {
Bundle data = new Bundle();
data.putSerializable(BleConstants.BLE_MSG_CHARACTERISTIC_UUID_KEY, uuid);
return data;
}
//release about ble
public void release() {
BleLog.i(TAG, "release()");
sIsWriting = false;
sWriteQueue.clear();
mHandler.post(new Runnable() {
@Override
public void run() {
if(mGatt != null) {
mGatt.disconnect();
}
}
});
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
try {
if (mGatt != null) {
mGatt.close();
mGatt = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
@Override
public void onDestroy() {
super.onDestroy();
release();
BleLog.i(TAG, "onDestroy()");
}
}