/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author jack
* This class refer to com.example.bluetooth.le's DeviceControlActivity.java
*/
package kr.ac.kaist.resl.sensorservice;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import com.example.bluetooth.le.BluetoothLeManagerService;
import com.example.bluetooth.le.BluetoothLeService;
import com.example.bluetooth.le.DeviceScanner;
import com.example.bluetooth.le.SampleGattAttributes;
import android.annotation.SuppressLint;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
public class BluetoothService extends Service {
private DeviceScanner scanner;
private BluetoothAdapter mBluetoothAdapter;
private final static String TAG = "MIO";
@SuppressWarnings("unused")
private static final int REQUEST_ENABLE_BT = 1;
@SuppressWarnings("unused")
private static final String MIO_DEVICE_INFORMATION_SERVICE_NAME = "Device Information Service";
private static final String MIO_HEART_RATE_SERVICE_NAME = "Heart Rate Service";
private static final String MIO_HEART_RATE_SERVICE_HEART_RATE_MEASUREMENT_CHARACTERISTIC = "Heart Rate Measurement";
private BluetoothLeService bleService;
private ServiceConnection serviceConnection;
private BluetoothGattCharacteristic mNotifyCharacteristic;
private Handler scannerMsgHandler;
private Node<String> device_service_characteristicTree;
private ServiceConnection bleManagerConnection;
private BluetoothLeManagerService bleManagerService;
private FileThread fileThread;
private RemoteThread remoteThread;
private String androidId;
private int heartrate;
private String mio_uid;
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a result of read
// or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
}
else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
if( remoteThread != null )
{
remoteThread.cont = false;
}
if(!scanner.isScanning()){
scanner.scanLeDevice(true);
}
else
bleService.reconnect();
}
else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
int hashcode = intent.getIntExtra(BluetoothLeService.GATT_HASHCODE, -1);
BluetoothGatt gatt = bleService.findGattbyHashcode(hashcode);
// Read characteristics based on the device-service-characteristic tree
readCharacteristics(gatt);
}
else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
String data = intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
String device_uid = intent.getStringExtra(BluetoothLeService.DEVICE_UID);
heartrate = Integer.parseInt(data);
mio_uid = device_uid;
if( fileThread == null )
{
fileThread = new FileThread();
fileThread.start();
}
if( remoteThread == null )
{
remoteThread = new RemoteThread();
remoteThread.start();
}
Log.i("MIO", device_uid + "::" + data);
}
}
};
// It makes BluetoothLeService to read data of the characteristic and broadcast the data.
private boolean readCharacteristic(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic){
if(null == characteristic)
return false;
final int charaProp = characteristic.getProperties();
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
// If there is an active notification on a characteristic,
// clear it first so it doesn't update the data field on the user interface.
if (mNotifyCharacteristic != null) {
bleService.setCharacteristicNotification(gatt, mNotifyCharacteristic, false);
mNotifyCharacteristic = null;
}
//JS: if no data is displayed, immediately read data from the connected sensor.
gatt.readCharacteristic(characteristic);
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
mNotifyCharacteristic = characteristic;
bleService.setCharacteristicNotification(gatt, characteristic, true);
}
return true;
}
public BluetoothService(){}
@SuppressLint("HandlerLeak")
@Override
public void onStart(Intent intent, int startId) {
androidId = android.provider.Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
invokeBLEmanagerService();
invokeBLEservice();
// Check if the android phone supports BLE
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
}
// Init msg handler for device scanner
scannerMsgHandler = new Handler(){
public void handleMessage(Message msg){
String deviceName = msg.getData().getString(DeviceScanner.MSG_KEY_DEVICE_NAME);
String deviceAddress = msg.getData().getString(DeviceScanner.MSG_KEY_DEVICE_ADDRESS);
if( deviceName.equals("MIO GLOBAL LINK"))
{
connectTo(deviceName, deviceAddress);
}
}
};
// Init scanner
scanner = new DeviceScanner((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE), scannerMsgHandler);
mBluetoothAdapter = scanner.initialize();
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
return;
}
// Init device-service-characteristic table.
init_Device_Service_CharacteristicTable();
// Register braodcast receiver
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
if(!scanner.isScanning()){
scanner.scanLeDevice(true);
}
else
bleService.reconnect();
}
@Override
public void onDestroy() {
scanner.close();
unbindService(serviceConnection);
serviceConnection = null;
bleService = null;
fileThread.cont = false;
remoteThread.cont = false;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/* This tree is composed with device name, service name, and characteristic name.
* The controller will connect to services in the tree.
*
* Example:
* <device name> <service name> <characteristic name>
* MIO GLOBAL --- Heart Rate Service --- Heart Rate Measurement
* --- Heart Rate Control Point
* --- Device Information
*/
public void init_Device_Service_CharacteristicTable(){
// Set Mio Device
Node<String> mio = new Node<String>(DeviceScanner.MIO_DEVICE_NAME)
// add heart rate service
.addChild(new Node<String>(MIO_HEART_RATE_SERVICE_NAME)
// add Heart Rate Measurement characteristic of heart rate service
.addChild(new Node<String>(MIO_HEART_RATE_SERVICE_HEART_RATE_MEASUREMENT_CHARACTERISTIC)));
// Add devices to root node
device_service_characteristicTree = new Node<String>(null);
device_service_characteristicTree.addChild(mio);
}
// Connect to target device
private void connectTo(String deviceName, String address){
Log.i("MIO", "Connect to MIO!! " + deviceName + ":" + address);
// Connect to device
bleService.connect(address);
}
public void invokeBLEmanagerService(){
bleManagerConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName componentName, IBinder service){
bleManagerService = ((BluetoothLeManagerService.LocalBinder)service).getService();
}
@Override
public void onServiceDisconnected(ComponentName componentName){
bleManagerService.close();
bleManagerService = null;
bleManagerConnection = null;
}
};
Intent managerServiceIntent = new Intent(this, BluetoothLeManagerService.class);
bindService(managerServiceIntent, bleManagerConnection, BIND_AUTO_CREATE);
}
public void invokeBLEservice(){
if(null != serviceConnection && null != bleService)
return;
serviceConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName componentName, IBinder service){
bleService = ((BluetoothLeService.LocalBinder)service).getService();
if (!bleService.initialize()){
Log.e(TAG, "Unable to initialize Bluetooth");
}
}
@Override
public void onServiceDisconnected(ComponentName componentName){
bleService.close();
bleService = null;
serviceConnection = null;
}
};
// Invoke bind the service
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, serviceConnection, BIND_AUTO_CREATE);
}
// Demonstrates how to iterate through the supported GATT Services/Characteristics.
// In this sample, we populate the data structure that is bound to the ExpandableListView
// on the UI.
private boolean readCharacteristics(BluetoothGatt gatt) {
if(null == gatt)
return false;
String deviceName = gatt.getDevice().getName();
List<BluetoothGattService> gattServices = gatt.getServices();
if (gattServices == null)
return false;
String unknownServiceString = getResources().getString(R.string.unknown_service);
String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
// Find the device node from device-service-characteristic tree
Node<String> deviceNode = null;
for (Node<String> node : device_service_characteristicTree.getChildren()){
if (node.data.equals(deviceName)){
deviceNode = node;
break;
}
}
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
String serviceUuid = gattService.getUuid().toString();
String serviceName = SampleGattAttributes.lookup(serviceUuid, unknownServiceString);
// Find desired service
for (Node<String> serviceNode: deviceNode.getChildren()){
if(serviceName.equals(serviceNode.data)){
List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
// Loops through available Characteristics.
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
String characteristicUuid = gattCharacteristic.getUuid().toString();
String characteristicName = SampleGattAttributes.lookup(characteristicUuid, unknownCharaString);
// Find desired characteristic of selected service
for (Node<String> characteristicNode: serviceNode.getChildren()){
if(characteristicName.equals(characteristicNode.data)){
Log.i(null, "FOUND DESIRED characteristic!!!");
readCharacteristic(gatt, gattCharacteristic);
break;
}
}
}
}
}
}
return true;
}
private static IntentFilter makeGattUpdateIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
return intentFilter;
}
private class Node<T>{
private T data;
private ArrayList<Node<T>> children;
@SuppressWarnings("unused")
private Node<T> parent;
public Node(T data){
this.data = data;
children = new ArrayList<Node<T>>();
}
public String toString(){
return data.toString();
}
public Node<T> addChild(Node<T> child){
children.add(child);
child.parent = this;
return this;
}
public ArrayList<Node<T>> getChildren(){
return children;
}
}
public class RemoteThread extends Thread
{
public boolean cont;
public RemoteThread()
{
cont = true;
}
public void run()
{
try {
while(cont)
{
if( heartrate != 0 )
{
JSONObject jObj = new JSONObject();
jObj.put("android_id", androidId);
jObj.put("heartrate", heartrate);
jObj.put("mio_id", mio_uid);
new HttpAsyncTask().execute(jObj.toString());
}
Thread.sleep(MainActivity.period*1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
public class FileThread extends Thread
{
public boolean cont;
public FileThread()
{
cont = true;
}
public void run()
{
try {
while(cont)
{
if( isExternalStorageWritable() )
{
JSONObject jObj = new JSONObject();
jObj.put("android_id", androidId);
jObj.put("heartrate", heartrate);
jObj.put("mio_id", mio_uid);
writeToSDFile("Mio", jObj.toString());
}
Thread.sleep(10000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
private class HttpAsyncTask extends AsyncTask<String, Integer, Double>{
@Override
protected Double doInBackground(String... params) {
try {
postData(params[0]);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
public void postData(String message) throws ClientProtocolException, IOException
{
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(MainActivity.URL);
StringEntity params = new StringEntity(message);
post.setEntity(params);
client.execute(post);
}
public boolean isExternalStorageWritable(){
String state = Environment.getExternalStorageState();
if( Environment.MEDIA_MOUNTED.equals( state)){
return true;
}
return false;
}
private void writeToSDFile(String fileName, String message){
// Find the root of the external storage.
// See http://developer.android.com/guide/topics/data/data- storage.html#filesExternal
File root = android.os.Environment.getExternalStorageDirectory();
// See http://stackoverflow.com/questions/3551821/android-write-to-sd-card-folder
File dir = new File (root.getAbsolutePath() + "/SensorService");
dir.mkdirs();
File file = new File(dir, fileName+".json");
try {
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
pw.print(message);
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}