package com.inuker.bluetooth.library.connect; import android.annotation.TargetApi; 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.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import com.inuker.bluetooth.library.Constants; import com.inuker.bluetooth.library.RuntimeChecker; import com.inuker.bluetooth.library.connect.listener.GattResponseListener; import com.inuker.bluetooth.library.connect.listener.IBluetoothGattResponse; import com.inuker.bluetooth.library.connect.listener.ReadCharacterListener; import com.inuker.bluetooth.library.connect.listener.ReadDescriptorListener; import com.inuker.bluetooth.library.connect.listener.ReadRssiListener; import com.inuker.bluetooth.library.connect.listener.RequestMtuListener; import com.inuker.bluetooth.library.connect.listener.ServiceDiscoverListener; import com.inuker.bluetooth.library.connect.listener.WriteCharacterListener; import com.inuker.bluetooth.library.connect.listener.WriteDescriptorListener; import com.inuker.bluetooth.library.connect.response.BluetoothGattResponse; import com.inuker.bluetooth.library.model.BleGattProfile; import com.inuker.bluetooth.library.utils.BluetoothLog; import com.inuker.bluetooth.library.utils.BluetoothUtils; import com.inuker.bluetooth.library.utils.ByteUtils; import com.inuker.bluetooth.library.utils.Version; import com.inuker.bluetooth.library.utils.proxy.ProxyBulk; import com.inuker.bluetooth.library.utils.proxy.ProxyInterceptor; import com.inuker.bluetooth.library.utils.proxy.ProxyUtils; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; /** * Created by dingjikerbo on 16/4/8. */ public class BleConnectWorker implements Handler.Callback, IBleConnectWorker, IBluetoothGattResponse, ProxyInterceptor, RuntimeChecker { private static final int MSG_GATT_RESPONSE = 0x120; private BluetoothGatt mBluetoothGatt; private BluetoothDevice mBluetoothDevice; private GattResponseListener mGattResponseListener; private Handler mWorkerHandler; private volatile int mConnectStatus; private BleGattProfile mBleGattProfile; private Map<UUID, Map<UUID, BluetoothGattCharacteristic>> mDeviceProfile; private IBluetoothGattResponse mBluetoothGattResponse; private RuntimeChecker mRuntimeChecker; public BleConnectWorker(String mac, RuntimeChecker runtimeChecker) { BluetoothAdapter adapter = BluetoothUtils.getBluetoothAdapter(); if (adapter != null) { mBluetoothDevice = adapter.getRemoteDevice(mac); } else { throw new IllegalStateException("ble adapter null"); } mRuntimeChecker = runtimeChecker; mWorkerHandler = new Handler(Looper.myLooper(), this); mDeviceProfile = new HashMap<UUID, Map<UUID, BluetoothGattCharacteristic>>(); mBluetoothGattResponse = ProxyUtils.getProxy(this, IBluetoothGattResponse.class, this); } private void refreshServiceProfile() { BluetoothLog.v(String.format("refreshServiceProfile for %s", mBluetoothDevice.getAddress())); List<BluetoothGattService> services = mBluetoothGatt.getServices(); Map<UUID, Map<UUID, BluetoothGattCharacteristic>> newProfiles = new HashMap<UUID, Map<UUID, BluetoothGattCharacteristic>>(); for (BluetoothGattService service : services) { UUID serviceUUID = service.getUuid(); Map<UUID, BluetoothGattCharacteristic> map = newProfiles.get(serviceUUID); if (map == null) { BluetoothLog.v("Service: " + serviceUUID); map = new HashMap<UUID, BluetoothGattCharacteristic>(); newProfiles.put(service.getUuid(), map); } List<BluetoothGattCharacteristic> characters = service .getCharacteristics(); for (BluetoothGattCharacteristic character : characters) { UUID characterUUID = character.getUuid(); BluetoothLog.v("character: uuid = " + characterUUID); map.put(character.getUuid(), character); } } mDeviceProfile.clear(); mDeviceProfile.putAll(newProfiles); mBleGattProfile = new BleGattProfile(mDeviceProfile); } private BluetoothGattCharacteristic getCharacter(UUID service, UUID character) { BluetoothGattCharacteristic characteristic = null; if (service != null && character != null) { Map<UUID, BluetoothGattCharacteristic> characters = mDeviceProfile.get(service); if (characters != null) { characteristic = characters.get(character); } } if (characteristic == null) { if (mBluetoothGatt != null) { BluetoothGattService gattService = mBluetoothGatt.getService(service); if (gattService != null) { characteristic = gattService.getCharacteristic(character); } } } return characteristic; } private void setConnectStatus(int status) { BluetoothLog.v(String.format("setConnectStatus status = %s", Constants.getStatusText(status))); mConnectStatus = status; } @Override public void onConnectionStateChange(int status, int newState) { checkRuntime(); BluetoothLog.v(String.format("onConnectionStateChange for %s: status = %d, newState = %d", mBluetoothDevice.getAddress(), status, newState)); if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { setConnectStatus(Constants.STATUS_DEVICE_CONNECTED); if (mGattResponseListener != null) { mGattResponseListener.onConnectStatusChanged(true); } } else { closeGatt(); } } @Override public void onServicesDiscovered(int status) { checkRuntime(); BluetoothLog.v(String.format("onServicesDiscovered for %s: status = %d", mBluetoothDevice.getAddress(), status)); if (status == BluetoothGatt.GATT_SUCCESS) { setConnectStatus(Constants.STATUS_DEVICE_SERVICE_READY); broadcastConnectStatus(Constants.STATUS_CONNECTED); refreshServiceProfile(); } if (mGattResponseListener != null && mGattResponseListener instanceof ServiceDiscoverListener) { ((ServiceDiscoverListener) mGattResponseListener).onServicesDiscovered(status, mBleGattProfile); } } @Override public void onCharacteristicRead(BluetoothGattCharacteristic characteristic, int status, byte[] value) { checkRuntime(); BluetoothLog.v(String.format( "onCharacteristicRead for %s: status = %d, service = 0x%s, character = 0x%s, value = %s", mBluetoothDevice.getAddress(), status, characteristic.getService().getUuid(), characteristic.getUuid(), ByteUtils.byteToString(value))); if (mGattResponseListener != null && mGattResponseListener instanceof ReadCharacterListener) { ((ReadCharacterListener) mGattResponseListener).onCharacteristicRead(characteristic, status, value); } } @Override public void onCharacteristicWrite(BluetoothGattCharacteristic characteristic, int status, byte[] value) { checkRuntime(); BluetoothLog.v(String.format( "onCharacteristicWrite for %s: status = %d, service = 0x%s, character = 0x%s, value = %s", mBluetoothDevice.getAddress(), status, characteristic.getService().getUuid(), characteristic.getUuid(), ByteUtils.byteToString(value))); if (mGattResponseListener != null && mGattResponseListener instanceof WriteCharacterListener) { ((WriteCharacterListener) mGattResponseListener).onCharacteristicWrite(characteristic, status, value); } } @Override public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic, byte[] value) { checkRuntime(); BluetoothLog.v(String.format("onCharacteristicChanged for %s: value = %s, service = 0x%s, character = 0x%s", mBluetoothDevice.getAddress(), ByteUtils.byteToString(value), characteristic.getService().getUuid(), characteristic.getUuid())); broadcastCharacterChanged(characteristic.getService().getUuid(), characteristic.getUuid(), value); } @Override public void onDescriptorRead(BluetoothGattDescriptor descriptor, int status, byte[] value) { checkRuntime(); BluetoothLog.v(String.format("onDescriptorRead for %s: status = %d, service = 0x%s, character = 0x%s, descriptor = 0x%s", mBluetoothDevice.getAddress(), status, descriptor.getCharacteristic().getService().getUuid(), descriptor.getCharacteristic().getUuid(), descriptor.getUuid())); if (mGattResponseListener != null && mGattResponseListener instanceof ReadDescriptorListener) { ((ReadDescriptorListener) mGattResponseListener).onDescriptorRead(descriptor, status, value); } } @Override public void onDescriptorWrite(BluetoothGattDescriptor descriptor, int status) { checkRuntime(); BluetoothLog.v(String.format("onDescriptorWrite for %s: status = %d, service = 0x%s, character = 0x%s, descriptor = 0x%s", mBluetoothDevice.getAddress(), status, descriptor.getCharacteristic().getService().getUuid(), descriptor.getCharacteristic().getUuid(), descriptor.getUuid())); if (mGattResponseListener != null && mGattResponseListener instanceof WriteDescriptorListener) { ((WriteDescriptorListener) mGattResponseListener).onDescriptorWrite(descriptor, status); } } @Override public void onReadRemoteRssi(int rssi, int status) { checkRuntime(); BluetoothLog.v(String.format("onReadRemoteRssi for %s, rssi = %d, status = %d", mBluetoothDevice.getAddress(), rssi, status)); if (mGattResponseListener != null && mGattResponseListener instanceof ReadRssiListener) { ((ReadRssiListener) mGattResponseListener).onReadRemoteRssi(rssi, status); } } @Override public void onMtuChanged(int mtu, int status) { checkRuntime(); BluetoothLog.v(String.format("onMtuChanged for %s, mtu = %d, status = %d", mBluetoothDevice.getAddress(), mtu, status)); if (mGattResponseListener != null && mGattResponseListener instanceof RequestMtuListener) { ((RequestMtuListener) mGattResponseListener).onMtuChanged(mtu, status); } } private void broadcastConnectStatus(int status) { Intent intent = new Intent(Constants.ACTION_CONNECT_STATUS_CHANGED); intent.putExtra(Constants.EXTRA_MAC, mBluetoothDevice.getAddress()); intent.putExtra(Constants.EXTRA_STATUS, status); BluetoothUtils.sendBroadcast(intent); } private void broadcastCharacterChanged(UUID service, UUID character, byte[] value) { Intent intent = new Intent( Constants.ACTION_CHARACTER_CHANGED); intent.putExtra(Constants.EXTRA_MAC, mBluetoothDevice.getAddress()); intent.putExtra(Constants.EXTRA_SERVICE_UUID, service); intent.putExtra(Constants.EXTRA_CHARACTER_UUID, character); intent.putExtra(Constants.EXTRA_BYTE_VALUE, value); BluetoothUtils.sendBroadcast(intent); } @Override public boolean openGatt() { checkRuntime(); BluetoothLog.v(String.format("openGatt for %s", getAddress())); if (mBluetoothGatt != null) { BluetoothLog.e(String.format("Previous gatt not closed")); return true; } Context context = BluetoothUtils.getContext(); BluetoothGattCallback callback = new BluetoothGattResponse(mBluetoothGattResponse); if (Version.isMarshmallow()) { mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, callback, BluetoothDevice.TRANSPORT_LE); } else { mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, callback); } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("openGatt failed: connectGatt return null!")); return false; } return true; } private String getAddress() { return mBluetoothDevice.getAddress(); } @Override public void closeGatt() { checkRuntime(); BluetoothLog.v(String.format("closeGatt for %s", getAddress())); if (mBluetoothGatt != null) { mBluetoothGatt.close(); mBluetoothGatt = null; } if (mGattResponseListener != null) { mGattResponseListener.onConnectStatusChanged(false); } setConnectStatus(Constants.STATUS_DEVICE_DISCONNECTED); broadcastConnectStatus(Constants.STATUS_DISCONNECTED); } @Override public boolean discoverService() { checkRuntime(); BluetoothLog.v(String.format("discoverService for %s", getAddress())); if (mBluetoothGatt == null) { BluetoothLog.e(String.format("discoverService but gatt is null!")); return false; } if (!mBluetoothGatt.discoverServices()) { BluetoothLog.e(String.format("discoverServices failed")); return false; } return true; } @Override public int getCurrentStatus() { checkRuntime(); return mConnectStatus; } @Override public void registerGattResponseListener(GattResponseListener listener) { checkRuntime(); mGattResponseListener = listener; } @Override public void clearGattResponseListener(GattResponseListener listener) { checkRuntime(); if (mGattResponseListener == listener) { mGattResponseListener = null; } } @Override public boolean refreshDeviceCache() { BluetoothLog.v(String.format("refreshDeviceCache for %s", getAddress())); checkRuntime(); if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } if (!BluetoothUtils.refreshGattCache(mBluetoothGatt)) { BluetoothLog.e(String.format("refreshDeviceCache failed")); return false; } return true; } @Override public boolean readCharacteristic(UUID service, UUID character) { BluetoothLog.v(String.format("readCharacteristic for %s: service = 0x%s, character = 0x%s", mBluetoothDevice.getAddress(), service, character)); checkRuntime(); BluetoothGattCharacteristic characteristic = getCharacter(service, character); if (characteristic == null) { BluetoothLog.e(String.format("characteristic not exist!")); return false; } // if (!isCharacteristicReadable(characteristic)) { // BluetoothLog.e(String.format("characteristic not readable!")); // return false; // } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } if (!mBluetoothGatt.readCharacteristic(characteristic)) { BluetoothLog.e(String.format("readCharacteristic failed")); return false; } return true; } @Override public boolean writeCharacteristic(UUID service, UUID character, byte[] value) { BluetoothLog.v(String.format("writeCharacteristic for %s: service = 0x%s, character = 0x%s, value = 0x%s", mBluetoothDevice.getAddress(), service, character, ByteUtils.byteToString(value))); checkRuntime(); BluetoothGattCharacteristic characteristic = getCharacter(service, character); if (characteristic == null) { BluetoothLog.e(String.format("characteristic not exist!")); return false; } // if (!isCharacteristicWritable(characteristic)) { // BluetoothLog.e(String.format("characteristic not writable!")); // return false; // } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } characteristic.setValue(value != null ? value : ByteUtils.EMPTY_BYTES); if (!mBluetoothGatt.writeCharacteristic(characteristic)) { BluetoothLog.e(String.format("writeCharacteristic failed")); return false; } return true; } @Override public boolean readDescriptor(UUID service, UUID character, UUID descriptor) { BluetoothLog.v(String.format("readDescriptor for %s: service = 0x%s, character = 0x%s, descriptor = 0x%s", mBluetoothDevice.getAddress(), service, character, descriptor)); checkRuntime(); BluetoothGattCharacteristic characteristic = getCharacter(service, character); if (characteristic == null) { BluetoothLog.e(String.format("characteristic not exist!")); return false; } BluetoothGattDescriptor gattDescriptor = characteristic.getDescriptor(descriptor); if (gattDescriptor == null) { BluetoothLog.e(String.format("descriptor not exist")); return false; } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } if (!mBluetoothGatt.readDescriptor(gattDescriptor)) { BluetoothLog.e(String.format("readDescriptor failed")); return false; } return true; } @Override public boolean writeDescriptor(UUID service, UUID character, UUID descriptor, byte[] value) { BluetoothLog.v(String.format("writeDescriptor for %s: service = 0x%s, character = 0x%s, descriptor = 0x%s, value = 0x%s", mBluetoothDevice.getAddress(), service, character, descriptor, ByteUtils.byteToString(value))); checkRuntime(); BluetoothGattCharacteristic characteristic = getCharacter(service, character); if (characteristic == null) { BluetoothLog.e(String.format("characteristic not exist!")); return false; } BluetoothGattDescriptor gattDescriptor = characteristic.getDescriptor(descriptor); if (gattDescriptor == null) { BluetoothLog.e(String.format("descriptor not exist")); return false; } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } gattDescriptor.setValue(value != null ? value : ByteUtils.EMPTY_BYTES); if (!mBluetoothGatt.writeDescriptor(gattDescriptor)) { BluetoothLog.e(String.format("writeDescriptor failed")); return false; } return true; } @Override public boolean writeCharacteristicWithNoRsp(UUID service, UUID character, byte[] value) { BluetoothLog.v(String.format("writeCharacteristicWithNoRsp for %s: service = 0x%s, character = 0x%s, value = 0x%s", mBluetoothDevice.getAddress(), service, character, ByteUtils.byteToString(value))); checkRuntime(); BluetoothGattCharacteristic characteristic = getCharacter(service, character); if (characteristic == null) { BluetoothLog.e(String.format("characteristic not exist!")); return false; } // if (!isCharacteristicNoRspWritable(characteristic)) { // BluetoothLog.e(String.format("characteristic not norsp writable!")); // return false; // } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } characteristic.setValue(value != null ? value : ByteUtils.EMPTY_BYTES); characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); if (!mBluetoothGatt.writeCharacteristic(characteristic)) { BluetoothLog.e(String.format("writeCharacteristic failed")); return false; } return true; } @Override public boolean setCharacteristicNotification(UUID service, UUID character, boolean enable) { checkRuntime(); BluetoothLog.v(String.format("setCharacteristicNotification for %s, service = %s, character = %s, enable = %b", getAddress(), service, character, enable)); BluetoothGattCharacteristic characteristic = getCharacter(service, character); if (characteristic == null) { BluetoothLog.e(String.format("characteristic not exist!")); return false; } // if (!isCharacteristicNotifyable(characteristic)) { // BluetoothLog.e(String.format("characteristic not notifyable!")); // return false; // } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } if (!mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) { BluetoothLog.e(String.format("setCharacteristicNotification failed")); return false; } BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG); if (descriptor == null) { BluetoothLog.e(String.format("getDescriptor for notify null!")); return false; } byte[] value = (enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); if (!descriptor.setValue(value)) { BluetoothLog.e(String.format("setValue for notify descriptor failed!")); return false; } if (!mBluetoothGatt.writeDescriptor(descriptor)) { BluetoothLog.e(String.format("writeDescriptor for notify failed")); return false; } return true; } @Override public boolean setCharacteristicIndication(UUID service, UUID character, boolean enable) { checkRuntime(); BluetoothLog.v(String.format("setCharacteristicIndication for %s, service = %s, character = %s, enable = %b", getAddress(), service, character, enable)); BluetoothGattCharacteristic characteristic = getCharacter(service, character); if (characteristic == null) { BluetoothLog.e(String.format("characteristic not exist!")); return false; } // if (!isCharacteristicIndicatable(characteristic)) { // BluetoothLog.e(String.format("characteristic not indicatable!")); // return false; // } if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } if (!mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) { BluetoothLog.e(String.format("setCharacteristicIndication failed")); return false; } BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG); if (descriptor == null) { BluetoothLog.e(String.format("getDescriptor for indicate null!")); return false; } byte[] value = (enable ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); if (!descriptor.setValue(value)) { BluetoothLog.e(String.format("setValue for indicate descriptor failed!")); return false; } if (!mBluetoothGatt.writeDescriptor(descriptor)) { BluetoothLog.e(String.format("writeDescriptor for indicate failed")); return false; } return true; } @Override public boolean readRemoteRssi() { checkRuntime(); BluetoothLog.v(String.format("readRemoteRssi for %s", getAddress())); if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } if (!mBluetoothGatt.readRemoteRssi()) { BluetoothLog.e(String.format("readRemoteRssi failed")); return false; } return true; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean requestMtu(int mtu) { checkRuntime(); BluetoothLog.v(String.format("requestMtu for %s, mtu = %d", getAddress(), mtu)); if (mBluetoothGatt == null) { BluetoothLog.e(String.format("ble gatt null")); return false; } if (!mBluetoothGatt.requestMtu(mtu)) { BluetoothLog.e(String.format("requestMtu failed")); return false; } return true; } @Override public BleGattProfile getGattProfile() { return mBleGattProfile; } private boolean isCharacteristicReadable(BluetoothGattCharacteristic characteristic) { return characteristic != null && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0; } private boolean isCharacteristicWritable(BluetoothGattCharacteristic characteristic) { return characteristic != null && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0; } private boolean isCharacteristicNoRspWritable(BluetoothGattCharacteristic characteristic) { return characteristic != null && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0; } private boolean isCharacteristicNotifyable(BluetoothGattCharacteristic characteristic) { return characteristic != null && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0; } private boolean isCharacteristicIndicatable(BluetoothGattCharacteristic characteristic) { return characteristic != null && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_GATT_RESPONSE: ProxyBulk.safeInvoke(msg.obj); break; } return true; } @Override public boolean onIntercept(Object object, Method method, Object[] args) { mWorkerHandler.obtainMessage(MSG_GATT_RESPONSE, new ProxyBulk(object, method, args)).sendToTarget(); return true; } @Override public void checkRuntime() { mRuntimeChecker.checkRuntime(); } }