/*
HeartRateManager
Copyright (c) 2015 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.heartrate;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import org.deviceconnect.android.deviceplugin.heartrate.ble.BleDeviceDetector;
import org.deviceconnect.android.deviceplugin.heartrate.data.HeartRateDBHelper;
import org.deviceconnect.android.deviceplugin.heartrate.data.HeartRateData;
import org.deviceconnect.android.deviceplugin.heartrate.data.HeartRateDevice;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import static org.deviceconnect.android.deviceplugin.heartrate.HeartRateConnector.HeartRateConnectEventListener;
import static org.deviceconnect.android.deviceplugin.heartrate.ble.BleDeviceDetector.BleDeviceDiscoveryListener;
/**
* This class manages a BLE device and GATT Service.
* <p>
* This class provides the following functions:
* <li>Scan a BLE device</li>
* <li>Connect a GATT of Heart Rate Service</li>
* <li>Get heart rate</li>
* </p>
*
* @author NTT DOCOMO, INC.
*/
public class HeartRateManager {
/**
* Logger.
*/
private final Logger mLogger = Logger.getLogger("heartrate.dplugin");
/**
* Application context.
*/
private Context mContext;
/**
* Instance of {@link BleDeviceDetector}.
*/
private BleDeviceDetector mDetector;
/**
* Instance of {@link HeartRateConnector}.
*/
private HeartRateConnector mConnector;
/**
* Instance of {@link HeartRateDBHelper}.
*/
private HeartRateDBHelper mDBHelper;
private List<OnHeartRateDiscoveryListener> mHRDiscoveryListener;
private OnHeartRateEventListener mHREvtListener;
// TODO: consider synchronized
private final List<HeartRateDevice> mConnectedDevices = Collections.synchronizedList(
new ArrayList<HeartRateDevice>());
private final List<HeartRateDevice> mRegisterDevices = Collections.synchronizedList(
new ArrayList<HeartRateDevice>());
private final Map<HeartRateDevice, HeartRateData> mHRData = new ConcurrentHashMap<>();
private Handler mHandler = new Handler();
/**
* Constructor.
*
* @param context application context
*/
HeartRateManager(final Context context) {
mContext = context;
mDBHelper = new HeartRateDBHelper(context);
List<HeartRateDevice> list = mDBHelper.getHeartRateDevices();
for (HeartRateDevice device : list) {
if (device.isRegisterFlag()) {
mRegisterDevices.add(device);
}
}
mHRDiscoveryListener = new ArrayList<>();
mDetector = new BleDeviceDetector(context);
mDetector.setListener(mDiscoveryListener);
mConnector = new HeartRateConnector(context, list);
mConnector.setListener(mHRConnectListener);
mConnector.setBleDeviceDetector(mDetector);
if (mDetector.isEnabled()) {
start();
}
}
/**
* Sets the OnHeartRateDiscoveryListener.
*
* @param listener The listener to be told when found device or connected device
*/
public void addOnHeartRateDiscoveryListener(final OnHeartRateDiscoveryListener listener) {
mHRDiscoveryListener.add(listener);
}
/**
* Remove the OnHeartRateDiscoveryListener.
* @param listener The listener to be told when found device or connected device
*/
public void removeOnHeartRateDiscoveryListener(final OnHeartRateDiscoveryListener listener) {
mHRDiscoveryListener.remove(listener);
}
/**
* Sets the OnHeartRateEventListener.
*
* @param listener The listener to be told when get a data of heart rate
*/
public void setOnHeartRateEventListener(final OnHeartRateEventListener listener) {
mHREvtListener = listener;
}
/**
* Starts the HeartRateManager.
*/
public void start() {
mDetector.initialize();
synchronized (mRegisterDevices) {
for (HeartRateDevice device : mRegisterDevices) {
connectBleDevice(device.getAddress());
}
}
if (mDetector.isScanning()) {
mDetector.startScan();
}
mConnector.start();
}
/**
* Stops the HeartRateManager.
*/
public void stop() {
mDetector.stopScan();
mConnectedDevices.clear();
mConnector.stop();
}
/**
* Return true if BLE is currently enabled and ready for use.
*
* @return true if the local adapter is turned on
*/
public boolean isEnabledBle() {
return mDetector != null && mDetector.isEnabled();
}
/**
* Starts BLE scan.
*/
public void startScanBle() {
mDetector.startScan();
}
/**
* Stops BLE scan.
*/
public void stopScanBle() {
mDetector.stopScan();
}
/**
* Register the {@link HeartRateDevice} to database from BluetoothDevice.
*
* @param device Instance of BluetoothDevice
* @return {@link HeartRateDevice}
*/
private HeartRateDevice registerHeartRateDevice(final BluetoothDevice device) {
HeartRateDevice hr = new HeartRateDevice();
hr.setName(device.getName());
hr.setAddress(device.getAddress());
hr.setRegisterFlag(true);
mDBHelper.addHeartRateDevice(hr);
mRegisterDevices.add(hr);
return hr;
}
/**
* Unregister the {@link HeartRateDevice} to database from BluetoothDevice.
*
* @param address address of device
*/
private void unregisterHeartRateDevice(final String address) {
HeartRateDevice hr = findRegisteredHeartRateDeviceByAddress(address);
if (hr != null) {
mDBHelper.removeHeartRateDevice(hr);
mRegisterDevices.remove(hr);
}
}
/**
* Connect to GATT Server by address.
*
* @param address address for ble device
*/
public void connectBleDevice(final String address) {
BluetoothDevice blue = mDetector.getDevice(address);
if (blue != null) {
mConnector.connectDevice(blue);
}
}
/**
* Disconnect to GATT Server by address.
*
* @param address address for ble device
*/
public void disconnectBleDevice(final String address) {
BluetoothDevice blue = mDetector.getDevice(address);
if (blue != null) {
mConnector.disconnectDevice(blue);
}
unregisterHeartRateDevice(address);
}
/**
* Gets the list of BLE device that connected.
*
* @return list of BLE device
*/
public List<HeartRateDevice> getConnectedDevices() {
return mConnectedDevices;
}
/**
* Gets the list of BLE device that was registered to automatic connection.
*
* @return list of BLE device
*/
public List<HeartRateDevice> getRegisterDevices() {
return mRegisterDevices;
}
/**
* Gets the set of BluetoothDevice that are bonded (paired) to the local adapter.
*
* @return set of BluetoothDevice, or null on error
*/
public Set<BluetoothDevice> getBondedDevices() {
return mDetector.getBondedDevices();
}
/**
* Gets the {@link HeartRateData} from address.
*
* @param address address of ble device
* @return {@link HeartRateData}, or null on error
*/
public HeartRateData getHeartRateData(final String address) {
HeartRateDevice device = findRegisteredHeartRateDeviceByAddress(address);
if (device == null) {
return null;
}
return getHeartRateData(device);
}
/**
* Gets the {@link HeartRateDevice} from address.
*
* @param address address of ble device
* @return {@link HeartRateDevice}, or null on error
*/
public HeartRateDevice getHeartRateDevice(final String address) {
for (HeartRateDevice device : mRegisterDevices) {
if (device.getAddress().equals(address)) {
return device;
}
}
return null;
}
/**
* Gets the {@link HeartRateData} from {@link HeartRateDevice}.
*
* @param device Instance of {@link HeartRateDevice}
* @return {@link HeartRateData}, or null on error
*/
public HeartRateData getHeartRateData(final HeartRateDevice device) {
return mHRData.get(device);
}
/**
* Find the {@link HeartRateDevice} from address.
*
* @param address address of ble device
* @return {@link HeartRateDevice}, or null
*/
private HeartRateDevice findRegisteredHeartRateDeviceByAddress(final String address) {
synchronized (mRegisterDevices) {
for (HeartRateDevice d : mRegisterDevices) {
if (d.getAddress().equalsIgnoreCase(address)) {
return d;
}
}
}
return null;
}
/**
* Find the {@link HeartRateDevice} from address.
*
* @param address address of ble device
* @return {@link HeartRateDevice}, or null
*/
private HeartRateDevice findConnectedHeartRateDeviceByAddress(final String address) {
synchronized (mConnectedDevices) {
for (HeartRateDevice d : mConnectedDevices) {
if (d.getAddress().equalsIgnoreCase(address)) {
return d;
}
}
}
return null;
}
/**
* Tests whether this mRegisterDevices contains the BluetoothDevice.
*
* @param device device will be checked
* @return true if object is an element of mRegisterDevices, false
* otherwise
*/
private boolean containRegisteredHeartRateDevice(final BluetoothDevice device) {
synchronized (mRegisterDevices) {
for (HeartRateDevice d : mRegisterDevices) {
if (d.getAddress().equalsIgnoreCase(device.getAddress())) {
return true;
}
}
}
return false;
}
/**
* Tests whether this mConnectedDevices contains the address.
* @param address address will be checked
* @return true if address is an element of mConnectedDevices, false otherwise
*/
public boolean containConnectedHeartRateDevice(final String address) {
synchronized (mConnectedDevices) {
for (HeartRateDevice d : mConnectedDevices) {
if (d.getAddress().equalsIgnoreCase(address)) {
return true;
}
}
}
return false;
}
/**
* Implementation of BleDeviceDiscoveryListener.
*/
private final BleDeviceDiscoveryListener mDiscoveryListener = new BleDeviceDiscoveryListener() {
@Override
public void onDiscovery(final List<BluetoothDevice> devices) {
mLogger.fine("BleDeviceDiscoveryListener#onDiscovery: " + devices.size());
if (mHRDiscoveryListener != null) {
for (OnHeartRateDiscoveryListener l : mHRDiscoveryListener) {
l.onDiscovery(devices);
}
}
}
};
/**
* Implementation of HeartRateConnectEventListener.
*/
private final HeartRateConnectEventListener mHRConnectListener = new HeartRateConnectEventListener() {
@Override
public void onConnected(final BluetoothDevice device) {
mLogger.fine("HeartRateConnectEventListener#onConnected: [" + device + "]");
HeartRateDevice hr = findRegisteredHeartRateDeviceByAddress(device.getAddress());
if (hr == null) {
hr = registerHeartRateDevice(device);
}
if (!mConnectedDevices.contains(hr)) {
mConnectedDevices.add(hr);
}
if (mHRDiscoveryListener != null) {
for (OnHeartRateDiscoveryListener l : mHRDiscoveryListener) {
l.onConnected(hr);
}
}
// DEBUG
mHandler.post(new Runnable() {
@Override
public void run() {
try {
String name = device.getName();
if (name == null) {
name = device.getAddress();
}
Toast.makeText(mContext, "Connect to " + name,
Toast.LENGTH_SHORT).show();
} catch (Exception e) {
mLogger.warning("Exception occurred.");
}
}
});
}
@Override
public void onDisconnected(final BluetoothDevice device) {
mLogger.fine("HeartRateConnectEventListener#onDisconnected: [" + device + "]");
HeartRateDevice hr = findConnectedHeartRateDeviceByAddress(device.getAddress());
if (hr != null) {
mConnectedDevices.remove(hr);
}
if (hr == null) {
if (mHRDiscoveryListener != null) {
for (OnHeartRateDiscoveryListener l : mHRDiscoveryListener) {
l.onConnectFailed(device);
}
}
} else {
if (mHRDiscoveryListener != null) {
for (OnHeartRateDiscoveryListener l : mHRDiscoveryListener) {
l.onDisconnected(hr);
}
}
// DEBUG
mHandler.post(new Runnable() {
@Override
public void run() {
try {
String name = device.getName();
if (name == null) {
name = device.getAddress();
}
Toast.makeText(mContext, "Disconnect to " + name,
Toast.LENGTH_SHORT).show();
} catch (Exception e) {
mLogger.warning("Exception occurred.");
}
}
});
}
}
@Override
public void onConnectFailed(final BluetoothDevice device) {
mLogger.fine("HeartRateConnectEventListener#onConnectFailed: [" + device + "]");
if (mHRDiscoveryListener != null) {
for (OnHeartRateDiscoveryListener l : mHRDiscoveryListener) {
l.onConnectFailed(device);
}
}
}
@Override
public void onReadSensorLocation(final BluetoothDevice device, final int location) {
mLogger.fine("HeartRateConnectEventListener#onReadSensorLocation: ["
+ device + "]: " + location);
HeartRateDevice hr = findConnectedHeartRateDeviceByAddress(device.getAddress());
if (hr != null) {
hr.setSensorLocation(location);
mDBHelper.updateHeartRateDevice(hr);
}
}
@Override
public void onReceivedData(final BluetoothDevice device, final int heartRate,
final int energyExpended, final double rrInterval) {
mLogger.fine("HeartRateConnectEventListener#onReceivedData: [" + device + "]");
HeartRateDevice hr = findRegisteredHeartRateDeviceByAddress(device.getAddress());
if (hr == null) {
mLogger.warning("device not found. device:[" + device + "]");
return;
}
HeartRateData data = new HeartRateData();
data.setHeartRate(heartRate);
data.setEnergyExpended(energyExpended);
data.setRRInterval(rrInterval);
mHRData.put(hr, data);
if (mHREvtListener != null) {
mHREvtListener.onReceivedData(hr, data);
}
}
};
/**
* This interface is used to implement {@link HeartRateManager} callbacks.
*/
public interface OnHeartRateDiscoveryListener {
void onDiscovery(List<BluetoothDevice> devices);
void onConnected(HeartRateDevice device);
void onConnectFailed(BluetoothDevice device);
void onDisconnected(HeartRateDevice device);
}
/**
* This interface is used to implement {@link HeartRateManager} callbacks.
*/
public interface OnHeartRateEventListener {
void onReceivedData(HeartRateDevice device, HeartRateData data);
}
}