/* * 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(); } } }