kr.ac.kaist.resl.sensorservice.BluetoothService.java Source code

Java tutorial

Introduction

Here is the source code for kr.ac.kaist.resl.sensorservice.BluetoothService.java

Source

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