package com.betomaluje.miband.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.betomaluje.miband.ActionCallback; import com.betomaluje.miband.AppUtils; import com.betomaluje.miband.model.Profile; import com.betomaluje.miband.model.UserInfo; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.UUID; /** * Created by betomaluje on 6/26/15. */ public class BTConnectionManager { public interface DataRead { public void OnDataRead(); } //the scanning timeout period private static final long SCAN_PERIOD = 45000; private static BTConnectionManager instance; private final String TAG = getClass().getSimpleName(); private Context context; private boolean mScanning = false; private boolean mFound = false; private boolean mAlreadyPaired = false; private boolean isConnected = false; private boolean isConnecting = false; private boolean isSyncNotification = false; private Handler mHandler = new Handler(Looper.getMainLooper()); private BluetoothAdapter adapter; private ActionCallback connectionCallback; private BTCommandManager io; private BluetoothGatt gatt; private DataRead onDataRead; private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { Log.d(TAG, "onLeScan: name: " + device.getName() + ", uuid: " + device.getUuids() + ", add: " + device.getAddress() + ", type: " + device.getType() + ", bondState: " + device.getBondState() + ", rssi: " + rssi); if (device.getName() != null && device.getAddress() != null && device.getName().equals("MI") && device.getAddress().startsWith("88:0F:10")) { mFound = true; stopDiscovery(); device.connectGatt(context, false, btleGattCallback); } } }; private Runnable stopRunnable = new Runnable() { @Override public void run() { stopDiscovery(); } }; public BTConnectionManager(Context context, ActionCallback connectionCallback) { this.context = context; //Log.i(TAG, "new BTConnectionManager"); this.connectionCallback = connectionCallback; } public synchronized static BTConnectionManager getInstance(Context context, ActionCallback connectionCallback) { if (instance == null) { instance = new BTConnectionManager(context, connectionCallback); } return instance; } public void connect() { Log.i(TAG, "trying to connect"); mFound = false; BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); adapter = manager.getAdapter(); if (adapter == null || !adapter.isEnabled()) { connectionCallback.onFail(NotificationConstants.BLUETOOTH_OFF, "Bluetooth disabled"); isConnected = false; isConnecting = false; } else { if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { connectionCallback.onFail(NotificationConstants.BLUETOOTH_OFF, "Bluetooth LE not supported"); isConnected = false; isConnecting = false; return; } if (!isConnecting && !adapter.isDiscovering()) { Log.i(TAG, "connecting..."); isConnecting = true; if (!tryPairedDevices()) { Log.i(TAG, "not already paired"); mScanning = true; if (AppUtils.supportsBluetoothLE(context)) { //Log.i(TAG, "is BTLE"); adapter.stopLeScan(mLeScanCallback); startBTLEDiscovery(); } else { //Log.i(TAG, "is BT"); adapter.cancelDiscovery(); startBTDiscovery(); } } } } } public void toggleNotifications(boolean enable) { if (gatt == null) return; HashMap<UUID, BluetoothGattCharacteristic> mAvailableCharacteristics = null; for (BluetoothGattService service : gatt.getServices()) { if (Profile.UUID_SERVICE_MILI.equals(service.getUuid())) { List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics(); if (characteristics == null || characteristics.isEmpty()) { Log.e(TAG, "Supported LE service " + service.getUuid() + "did not return any characteristics"); continue; } mAvailableCharacteristics = new HashMap<>(characteristics.size()); for (BluetoothGattCharacteristic characteristic : characteristics) { mAvailableCharacteristics.put(characteristic.getUuid(), characteristic); } } } try { if (mAvailableCharacteristics != null && !mAvailableCharacteristics.isEmpty()) { isSyncNotification = enable; gatt.setCharacteristicNotification(mAvailableCharacteristics.get(Profile.UUID_CHAR_NOTIFICATION), enable); gatt.setCharacteristicNotification(mAvailableCharacteristics.get(Profile.UUID_CHAR_REALTIME_STEPS), enable); gatt.setCharacteristicNotification(mAvailableCharacteristics.get(Profile.UUID_CHAR_ACTIVITY_DATA), enable); gatt.setCharacteristicNotification(mAvailableCharacteristics.get(Profile.UUID_CHAR_BATTERY), enable); gatt.setCharacteristicNotification(mAvailableCharacteristics.get(Profile.UUID_CHAR_SENSOR_DATA), enable); } } catch (NullPointerException e) { } } public void disconnect() { if (gatt != null) { gatt.disconnect(); } isConnected = false; isConnecting = false; connectionCallback.onFail(-1, "disconnected"); } public void dispose() { if (gatt != null) { gatt.close(); gatt = null; } isConnected = false; isConnecting = false; connectionCallback.onFail(-1, "disconnected"); } private boolean tryPairedDevices() { String mDeviceAddress = ""; mAlreadyPaired = false; SharedPreferences sharedPreferences = context.getSharedPreferences(UserInfo.KEY_PREFERENCES, Context.MODE_PRIVATE); String btAddress = sharedPreferences.getString(UserInfo.KEY_BT_ADDRESS, ""); if (btAddress != null) { if (!btAddress.equals("")) { //we use our previously paired device //mFound = true; mAlreadyPaired = true; mDeviceAddress = btAddress; } else { //we search for paired devices final Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices(); for (BluetoothDevice pairedDevice : pairedDevices) { if (pairedDevice.getName().equals("MI") && pairedDevice.getAddress().startsWith("88:0F:10")) { mDeviceAddress = pairedDevice.getAddress(); //mFound = true; mAlreadyPaired = true; break; } } } if (mAlreadyPaired && !mDeviceAddress.equals("")) { mDeviceAddress = btAddress; } else { mAlreadyPaired = false; } } else { //we search only for paired devices final Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices(); for (BluetoothDevice pairedDevice : pairedDevices) { if (pairedDevice.getName().equals("MI") && pairedDevice.getAddress().startsWith("88:0F:10")) { mDeviceAddress = pairedDevice.getAddress(); //mFound = true; mAlreadyPaired = true; break; } } } if (mDeviceAddress.equals("")) mAlreadyPaired = false; if (mAlreadyPaired) { //Log.i(TAG, "already paired!"); BluetoothDevice mBluetoothMi = adapter.getRemoteDevice(mDeviceAddress); mBluetoothMi.connectGatt(context, false, btleGattCallback); //mGatt.connect(); } return mAlreadyPaired; } public boolean isAlreadyPaired() { return mAlreadyPaired; } public boolean isConnected() { return isConnected && adapter.isEnabled(); } public boolean isSyncNotification() { return isSyncNotification; } public BluetoothDevice getDevice() { return gatt.getDevice(); } public BluetoothGatt getGatt() { return gatt; } public void setIo(BTCommandManager io) { this.io = io; onDataRead = io.getmQueueConsumer(); } private final BluetoothGattCallback btleGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); //Log.e(TAG, "onConnectionStateChange (2): " + newState); BTConnectionManager.this.gatt = gatt; if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { gatt.discoverServices(); } else if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_DISCONNECTED) { //Log.e(TAG, "onConnectionStateChange disconnect: " + newState); //toggleNotifications(false); //disconnect(); } else if (status != BluetoothGatt.GATT_SUCCESS) { gatt.disconnect(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); //Log.e(TAG, "onServicesDiscovered (0): " + status + " paired: " + isAlreadyPaired()); if (status == BluetoothGatt.GATT_SUCCESS) { stopDiscovery(); //we update current band bluetooth MAC address SharedPreferences sharedPrefs = context.getSharedPreferences(UserInfo.KEY_PREFERENCES, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPrefs.edit(); editor.putString(UserInfo.KEY_BT_ADDRESS, gatt.getDevice().getAddress()); editor.commit(); //we set the Gatt instance BTConnectionManager.this.gatt = gatt; isConnected = true; //toggleNotifications(true); connectionCallback.onSuccess(isAlreadyPaired()); } else { //disconnect(); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); if (BluetoothGatt.GATT_SUCCESS == status) { if (io != null) io.onSuccess(characteristic); } else { io.onFail(status, "onCharacteristicRead fail"); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); //if status is 0, success on sending and received //Log.i(TAG, "handleControlPoint got status:" + status); if (BluetoothGatt.GATT_SUCCESS == status) { io.onSuccess(characteristic); if (characteristic.getUuid().equals(Profile.UUID_CHAR_CONTROL_POINT)) { io.handleControlPointResult(characteristic.getValue()); } } else { io.onFail(status, "onCharacteristicWrite fail"); } if (onDataRead != null) onDataRead.OnDataRead(); } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { super.onReadRemoteRssi(gatt, rssi, status); if (BluetoothGatt.GATT_SUCCESS == status) { io.onSuccess(rssi); } else { io.onFail(status, "onCharacteristicRead fail"); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); if (io.notifyListeners.containsKey(characteristic.getUuid())) { io.notifyListeners.get(characteristic.getUuid()).onNotify(characteristic.getValue()); } UUID characteristicUUID = characteristic.getUuid(); if (Profile.UUID_CHAR_ACTIVITY_DATA.equals(characteristicUUID)) { io.handleActivityNotif(characteristic.getValue()); } toggleNotifications(false); } }; /* * * * DISCOVERY REGION * * */ private void stopDiscovery() { Log.i(TAG, "Stopping discovery"); isConnecting = false; //if (mScanning) { if (AppUtils.supportsBluetoothLE(context)) { stopBTLEDiscovery(); } else { stopBTDiscovery(); } mHandler.removeMessages(0, stopRunnable); mScanning = false; if (!mFound) connectionCallback.onFail(-1, "No bluetooth devices"); //} } private void startBTDiscovery() { Log.i(TAG, "Starting BT Discovery"); mHandler.removeMessages(0, stopRunnable); mHandler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_PERIOD); stopBTDiscovery(); if (adapter.startDiscovery()) Log.v(TAG, "starting scan"); } private void startBTLEDiscovery() { Log.i(TAG, "Starting BTLE Discovery"); mHandler.removeMessages(0, stopRunnable); mHandler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_PERIOD); stopBTLEDiscovery(); if (adapter.startLeScan(mLeScanCallback)) Log.v(TAG, "starting scan"); } private void stopBTLEDiscovery() { if (adapter.isDiscovering()) adapter.stopLeScan(mLeScanCallback); } private void stopBTDiscovery() { if (adapter.isDiscovering()) adapter.cancelDiscovery(); } private Message getPostMessage(Runnable runnable) { Message m = Message.obtain(mHandler, runnable); m.obj = runnable; return m; } }