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