com.t2.compassionMeditation.Graphs1Activity.java Source code

Java tutorial

Introduction

Here is the source code for com.t2.compassionMeditation.Graphs1Activity.java

Source

/*****************************************************************
BioZen
    
Copyright (C) 2011 The National Center for Telehealth and 
Technology
    
Eclipse Public License 1.0 (EPL-1.0)
    
This library is free software; you can redistribute it and/or
modify it under the terms of the Eclipse Public License as
published by the Free Software Foundation, version 1.0 of the 
License.
    
The Eclipse Public License is a reciprocal license, under 
Section 3. REQUIREMENTS iv) states that source code for the 
Program is available from such Contributor, and informs licensees 
how to obtain it in a reasonable manner on or through a medium 
customarily used for software exchange.
    
Post your updates and modifications to our GitHub or email to 
t2@tee2.org.
    
This library is distributed WITHOUT ANY WARRANTY; without 
the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE.  See the Eclipse Public License 1.0 (EPL-1.0)
for more details.
     
You should have received a copy of the Eclipse Public License
along with this library; if not, 
visit http://www.opensource.org/licenses/EPL-1.0
    
*****************************************************************/
package com.t2.compassionMeditation;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView;
import org.achartengine.chart.PointStyle;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.t2health.lib1.BioParameter;
import org.t2health.lib1.BioSensor;

import spine.SPINEFactory;
import spine.SPINEFunctionConstants;
import spine.SPINEListener;
import spine.SPINEManager;
import spine.SPINESensorConstants;
import spine.datamodel.Address;
import spine.datamodel.Data;
import spine.datamodel.Feature;
import spine.datamodel.FeatureData;
import spine.datamodel.HeartBeatData;
import spine.datamodel.MindsetData;
import spine.datamodel.Node;
import spine.datamodel.ServiceMessage;
import spine.datamodel.ShimmerData;
import spine.datamodel.functions.ShimmerNonSpineSetupSensor;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import bz.org.t2health.lib.activity.BaseActivity;

import com.t2.Constants;
import com.t2.R;
import com.t2.SpineReceiver;
import com.t2.SpineReceiver.BioFeedbackStatus;
import com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener;
import com.t2.antlib.ANTPlusService;
import com.t2.antlib.AntPlusManager;
import com.t2.biofeedback.device.shimmer.ShimmerDevice;
import com.t2.compassionUtils.MathExtra;
import com.t2.compassionUtils.Util;
import com.t2.dataouthandler.DataOutHandler;
import com.t2.dataouthandler.DataOutHandlerException;
import com.t2.dataouthandler.DataOutHandlerTags;
import com.t2.dataouthandler.DataOutPacket;
import com.t2.t2sensorlib.BigBrotherService;

public class Graphs1Activity extends BaseActivity
        implements OnBioFeedbackMessageRecievedListener, SPINEListener, AntPlusManager.Callbacks {
    private static final String TAG = "BFDemo";

    private static final String KEY_NAME = "results_visible_ids_16";
    private static final int BLUETOOTH_SETTINGS_ID = 987;

    private static final int HEARTRATE_SHIMMER = 1;
    private static final int HEARTRATE_ZEPHYR = 2;
    private static final int HEARTRATE_ANT = 3;

    private String mAppId = "bioZenGraphs";

    private boolean mLogCatEnabled = true;
    private boolean mLoggingEnabled = true;
    private int mPrevSigQuality = 0;
    private boolean mInternalSensorMonitoring = false;

    /**
     * Intent to start Big Brother service
     */
    private PendingIntent mBigBrotherService;
    private int mPollingPeriod = 30; // seconds
    private int mSecondsWithoutActivityThreshold = 5; // seconds
    private double mAccelerationThreshold = 12.0; // m/s^2   

    /**
     * Application version info determined by the package manager
     */
    private String mApplicationVersion = "";

    /**
     * The Spine manager contains the bulk of the Spine server. 
     */
    private static SPINEManager mManager;

    /**
    * This is a broadcast receiver. Note that this is used ONLY for command/status messages from the AndroidBTService
    * All data from the service goes through the mail SPINE mechanism (received(Data data)).
    */
    private SpineReceiver mCommandReceiver;

    /**
     * Static instance of this activity
     */
    private static Graphs1Activity mInstance;

    /**
     * Timer for updating the UI
     */
    private static Timer mDataUpdateTimer;

    /**
     * Timer for Resp Rate Average
     */
    private static Timer mRespRateAverageTimer;

    protected SharedPreferences sharedPref;

    private boolean mPaused = false;

    private Boolean mBluetoothEnabled = false;

    // UI Elements   
    private Button mAddMeasureButton;
    private Button mPauseButton;
    private TextView mTextInfoView;
    private TextView mMeasuresDisplayText;
    private MindsetData currentMindsetData;
    private GraphicalView mDeviceChartView;

    private int mConfiguredGSRRange = ShimmerDevice.GSR_RANGE_HW_RES_3M3;
    /**
     * List of all BioParameters used in this activity
     */
    private ArrayList<GraphBioParameter> mBioParameters = new ArrayList<GraphBioParameter>();

    /**
     * List of all currently PAIRED BioSensors
     */
    private ArrayList<BioSensor> mBioSensors = new ArrayList<BioSensor>();

    /**
     * Class to help in saving received data to file
     */
    private DataOutHandler mDataOutHandler;

    /**
     * Class to help in processing biometeric data
     */
    private BioDataProcessor mBioDataProcessor = new BioDataProcessor(this);

    // Charting stuff
    private final static int SPINE_CHART_SIZE = 20;
    private int mSpineChartX = 0;

    private Node mShimmerNode = null;

    /**
     * Node object for shimmer device as returned by spine
     */
    public Node mSpineNode = null;

    private int numTicsWithoutData = 0;
    private static Object mKeysLock = new Object();
    private static Object mRespRateAverageLock = new Object();

    // We'll use these to get easy access to parameters in the mBioParameters array
    private int eegPos;
    private int gsrPos;
    private int emgPos;
    private int ecgPos;
    private int heartRatePos;
    private int respRatePos;
    private int skinTempPos;

    private int eHealthAirFlowPos;
    private int eHealthTempPos;
    private int eHealthSpO2Pos;
    private int eHealthHeartRatePos;
    private int eHealthGSRPos;

    boolean mIsActive = false;

    int mDisplaySampleRate;

    //   int mHeartRateSource = HEARTRATE_SHIMMER;
    int mHeartRateSource = HEARTRATE_ZEPHYR;

    long mLastRespRateTime;
    int mRespRateTotal;
    int mRespRateIndex;

    private boolean mDatabaseEnabled;
    private boolean mAntHrmEnabled;

    /**
     * Static names dealing with the external database
     */
    public static final String dDatabaseName = "";
    public static final String dDesignDocName = "bigbrother-local";
    public static final String dDesignDocId = "_design/" + dDesignDocName;
    public static final String byDateViewName = "byDate";

    /** Class to manage all the ANT messaging and setup */
    private AntPlusManager mAntManager;

    private boolean mAntServiceBound;

    /** Shared preferences data filename. */
    public static final String PREFS_NAME = "ANTDemo1Prefs";

    /** Pair to any device. */
    static final short ANT_WILDCARD = 0;

    /** The default proximity search bin. */
    private static final byte ANT_DEFAULT_BIN = 7;

    /** The default event buffering buffer threshold. */
    private static final short ANT_DEFAULT_BUFFER_THRESHOLD = 0;

    /**
     *  Right now we're using only one shimmer node for all shimmer devices
     *  (since we address they by BT address)
     * @return singleton for the shimmer node
     */
    private Node getShimmerNode() {
        if (mShimmerNode == null) {
            mShimmerNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_SHIMMER));
            mManager.getActiveNodes().add(mShimmerNode);
        }
        return mShimmerNode;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, this.getClass().getSimpleName() + ".onCreate()");

        // We don't want the screen to timeout in this activity
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        this.requestWindowFeature(Window.FEATURE_NO_TITLE); // This needs to happen BEFORE setContentView
        setContentView(R.layout.graphs_activity_layout);
        mInstance = this;

        sharedPref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        mLoggingEnabled = SharedPref.getBoolean(this, "enable_logging", true);
        mDatabaseEnabled = SharedPref.getBoolean(this, "database_enabled", false);
        mAntHrmEnabled = SharedPref.getBoolean(this, "enable_ant_hrm", false);

        mInternalSensorMonitoring = SharedPref.getBoolean(this, "inernal_sensor_monitoring_enabled", false);

        if (mAntHrmEnabled) {
            mHeartRateSource = HEARTRATE_ANT;
        } else {
            mHeartRateSource = HEARTRATE_ZEPHYR;
        }

        // The session start time will be used as session id
        // Note this also sets session start time
        // **** This session ID will be prepended to all JSON data stored
        //      in the external database until it's changed (by the start
        //      of a new session.
        Calendar cal = Calendar.getInstance();
        SharedPref.setBioSessionId(sharedPref, cal.getTimeInMillis());

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US);
        String sessionDate = sdf.format(new Date());
        String userId = SharedPref.getString(this, "SelectedUser", "");
        long sessionId = SharedPref.getLong(this, "bio_session_start_time", 0);

        mDataOutHandler = new DataOutHandler(this, userId, sessionDate, mAppId,
                DataOutHandler.DATA_TYPE_EXTERNAL_SENSOR, sessionId);

        if (mDatabaseEnabled) {
            TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
            String myNumber = telephonyManager.getLine1Number();

            String remoteDatabaseUri = SharedPref.getString(this, "database_sync_name",
                    getString(R.string.database_uri));
            //            remoteDatabaseUri += myNumber; 

            Log.d(TAG, "Initializing database at " + remoteDatabaseUri); // TODO: remove
            try {
                mDataOutHandler.initializeDatabase(dDatabaseName, dDesignDocName, dDesignDocId, byDateViewName,
                        remoteDatabaseUri);
            } catch (DataOutHandlerException e) {
                Log.e(TAG, e.toString());
                e.printStackTrace();
            }
            mDataOutHandler.setRequiresAuthentication(false);

        }

        mBioDataProcessor.initialize(mDataOutHandler);

        if (mLoggingEnabled) {
            mDataOutHandler.enableLogging(this);
        }

        if (mLogCatEnabled) {
            mDataOutHandler.enableLogCat();
        }

        // Log the version
        try {
            PackageManager packageManager = getPackageManager();
            PackageInfo info = packageManager.getPackageInfo(getPackageName(), 0);
            mApplicationVersion = info.versionName;
            String versionString = mAppId + " application version: " + mApplicationVersion;

            DataOutPacket packet = new DataOutPacket();
            packet.add(DataOutHandlerTags.version, versionString);
            try {
                mDataOutHandler.handleDataOut(packet);
            } catch (DataOutHandlerException e) {
                Log.e(TAG, e.toString());
                e.printStackTrace();
            }

        } catch (NameNotFoundException e) {
            Log.e(TAG, e.toString());
        }

        // Set up UI elements
        Resources resources = this.getResources();
        AssetManager assetManager = resources.getAssets();

        mPauseButton = (Button) findViewById(R.id.buttonPause);
        mAddMeasureButton = (Button) findViewById(R.id.buttonAddMeasure);
        mTextInfoView = (TextView) findViewById(R.id.textViewInfo);
        mMeasuresDisplayText = (TextView) findViewById(R.id.measuresDisplayText);

        // Don't actually show skin conductance meter unless we get samples
        ImageView image = (ImageView) findViewById(R.id.imageView1);
        image.setImageResource(R.drawable.signal_bars0);

        // Check to see of there a device configured for EEG, if so then show the skin conductance meter
        String tmp = SharedPref.getString(this, "EEG", null);

        if (tmp != null) {
            image.setVisibility(View.VISIBLE);
        } else {
            image.setVisibility(View.INVISIBLE);
        }

        // Initialize SPINE by passing the fileName with the configuration properties
        try {
            mManager = SPINEFactory.createSPINEManager("SPINETestApp.properties", resources);
        } catch (InstantiationException e) {
            Log.e(TAG, "Exception creating SPINE manager: " + e.toString());
            e.printStackTrace();
        }

        try {
            currentMindsetData = new MindsetData(this);
        } catch (Exception e1) {
            Log.e(TAG, "Exception creating MindsetData: " + e1.toString());
            e1.printStackTrace();
        }

        // Establish nodes for BSPAN

        // Create a broadcast receiver. Note that this is used ONLY for command messages from the service
        // All data from the service goes through the mail SPINE mechanism (received(Data data)).
        // See public void received(Data data)
        this.mCommandReceiver = new SpineReceiver(this);

        int itemId = 0;
        eegPos = itemId; // eeg always comes first
        mBioParameters.clear();

        // First create GraphBioParameters for each of the EEG static params (ie mindset)
        for (itemId = 0; itemId < MindsetData.NUM_BANDS + 2; itemId++) { // 2 extra, for attention and meditation
            GraphBioParameter param = new GraphBioParameter(itemId, MindsetData.spectralNames[itemId], "", true);
            param.isShimmer = false;
            mBioParameters.add(param);
        }

        // Now create all of the potential dynamic GBraphBioParameters (GSR, EMG, ECG, EEG, HR, Skin Temp, Resp Rate
        //       String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names);
        String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names_less_eeg);

        for (String paramName : paramNamesStringArray) {
            if (paramName.equalsIgnoreCase("not assigned"))
                continue;

            GraphBioParameter param = new GraphBioParameter(itemId, paramName, "", true);

            if (paramName.equalsIgnoreCase("gsr")) {
                gsrPos = itemId;
                param.isShimmer = true;
                param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_GSR_SENSOR;
                param.shimmerNode = getShimmerNode();
            }

            if (paramName.equalsIgnoreCase("emg")) {
                emgPos = itemId;
                param.isShimmer = true;
                param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_EMG_SENSOR;
                param.shimmerNode = getShimmerNode();
            }

            if (paramName.equalsIgnoreCase("ecg")) {
                ecgPos = itemId;
                param.isShimmer = true;
                param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_ECG_SENSOR;
                param.shimmerNode = getShimmerNode();
            }

            if (paramName.equalsIgnoreCase("heart rate")) {
                heartRatePos = itemId;
                param.isShimmer = false;
            }

            if (paramName.equalsIgnoreCase("resp rate")) {
                respRatePos = itemId;
                param.isShimmer = false;
            }

            if (paramName.equalsIgnoreCase("skin temp")) {
                skinTempPos = itemId;
                param.isShimmer = false;
            }

            if (paramName.equalsIgnoreCase("EHealth Airflow")) {
                eHealthAirFlowPos = itemId;
                param.isShimmer = false;
            }

            if (paramName.equalsIgnoreCase("EHealth Temp")) {
                eHealthTempPos = itemId;
                param.isShimmer = false;
            }

            if (paramName.equalsIgnoreCase("EHealth SpO2")) {
                eHealthSpO2Pos = itemId;
                param.isShimmer = false;
            }

            if (paramName.equalsIgnoreCase("EHealth Heartrate")) {
                eHealthHeartRatePos = itemId;
                param.isShimmer = false;
            }

            if (paramName.equalsIgnoreCase("EHealth GSR")) {
                eHealthGSRPos = itemId;
                param.isShimmer = false;
            }

            itemId++;
            mBioParameters.add(param);
        }

        // Since These are static nodes (Non-spine) we have to manually put them in the active node list
        Node mindsetNode = null;
        mindsetNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_MINDSET)); // Note that the sensor id 0xfff1 (-15) is a reserved id for this particular sensor
        mManager.getActiveNodes().add(mindsetNode);

        Node zepherNode = null;
        zepherNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_ZEPHYR));
        mManager.getActiveNodes().add(zepherNode);

        // The arduino node is programmed to look like a static Spine node
        // Note that currently we don't have  to turn it on or off - it's always streaming
        // Since Spine (in this case) is a static node we have to manually put it in the active node list
        // Since the 
        final int RESERVED_ADDRESS_ARDUINO_SPINE = 1; // 0x0001
        mSpineNode = new Node(new Address("" + RESERVED_ADDRESS_ARDUINO_SPINE));
        mManager.getActiveNodes().add(mSpineNode);

        final String sessionName;

        // Check to see if we were requested to play back a previous session
        try {
            Bundle bundle = getIntent().getExtras();

            if (bundle != null) {
                sessionName = bundle.getString(BioZenConstants.EXTRA_SESSION_NAME);

                AlertDialog.Builder alert = new AlertDialog.Builder(this);
                alert.setTitle("Replay Session " + sessionName + "?");
                alert.setMessage("Make sure to turn off all Bluetooth Sensors!");

                alert.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {

                        try {
                            mDataOutHandler.logNote("Replaying data from session " + sessionName);
                        } catch (DataOutHandlerException e) {
                            Log.e(TAG, e.toString());
                            e.printStackTrace();
                        }

                        replaySessionData(sessionName);
                        AlertDialog.Builder alert1 = new AlertDialog.Builder(mInstance);
                        alert1.setTitle("INFO");
                        alert1.setMessage("Replay of session complete!");
                        alert1.show();

                    }
                });
                alert.show();
            }

        } catch (Exception e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
        }

        if (mInternalSensorMonitoring) {
            // IntentSender Launches our service scheduled with with the alarm manager 
            mBigBrotherService = PendingIntent.getService(Graphs1Activity.this, 0,
                    new Intent(Graphs1Activity.this, BigBrotherService.class), 0);

            long firstTime = SystemClock.elapsedRealtime();
            // Schedule the alarm!
            AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
            am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, mPollingPeriod * 1000,
                    mBigBrotherService);

            // Tell the user about what we did.
            Toast.makeText(Graphs1Activity.this, R.string.service_scheduled, Toast.LENGTH_LONG).show();

        }

        //testFIRFilter();

        //       testHR();

    } // End onCreate(

    boolean replaySessionData(String sessionName) {
        BufferedReader logReader = null;

        // Open a file for saving data
        try {
            File root = Environment.getExternalStorageDirectory();
            if (root.canWrite()) {
                File gpxfile = new File(root, sessionName);
                FileReader gpxreader = new FileReader(gpxfile); // open for append
                logReader = new BufferedReader(gpxreader);

                String lineToParse;
                while ((lineToParse = logReader.readLine()) != null) {
                    Log.i("SensorData", lineToParse);

                    if (lineToParse.contains("ECG,")) {

                        try {
                            String[] tokens = lineToParse.split(",");

                            if (tokens.length == 7) {
                                ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER,
                                        SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
                                shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
                                shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
                                shimmerData.ecgLaLL = 2000 + (int) Float.parseFloat(tokens[5].trim());
                                shimmerData.ecgRaLL = 2000;
                                shimmerData.timestamp = Integer.parseInt(tokens[1].trim());
                                this.received(shimmerData);
                            }
                            if (tokens.length == 6) {
                                ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER,
                                        SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
                                shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
                                shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
                                shimmerData.ecgLaLL = 2000 + Integer.parseInt(tokens[4].trim());
                                shimmerData.ecgRaLL = 2000;
                                shimmerData.timestamp = Integer.parseInt(tokens[2].trim());
                                this.received(shimmerData);
                            }
                            if (tokens.length == 5) {
                                ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER,
                                        SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
                                shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
                                shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
                                shimmerData.ecgLaLL = 2000 + Integer.parseInt(tokens[3].trim());
                                shimmerData.ecgRaLL = 2000;
                                shimmerData.timestamp = Integer.parseInt(tokens[2].trim());
                                this.received(shimmerData);
                            }

                        } catch (Exception e) {
                        }
                    }

                }

            } else {
                Log.e(TAG, "Could not open file ");
                AlertDialog.Builder alert = new AlertDialog.Builder(this);

                alert.setTitle("ERROR");
                alert.setMessage("Cannot open to file");
                alert.show();

            }
        } catch (IOException e) {
            Log.e(TAG, "Could not open file " + e.getMessage());
            AlertDialog.Builder alert = new AlertDialog.Builder(this);

            alert.setTitle("ERROR");
            alert.setMessage("Cannot write to file");
            alert.show();

        }

        try {
            logReader.close();
        } catch (IOException e) {
            Log.e(TAG, "Could not close file " + e.getMessage());
        }

        return true;
    }

    private void testHR() {

        byte[] input = { -22, -29, -33, -93, -48, 27, -3, 24, -20, -44, 22, -40, -70, -82, 33, -64, -23, 4, -20, 28,
                -78, 3, 7, 23, 92, 43, 14, 19, -27, 111, 93, -38, 93, -31, -29, -40, 2, 59, 70, -76, -14, 15, -44,
                -66, -17, -16, -19, -18, -10, -16, 53, 98, -23, -23, -15, -14, -8, -1, 3, -20, -10, -26, -5, -23,
                -9, -6, 18, 18, 17, 16, 33, 56, 46, 10, 7, 9, 20, 0, 12, 10, 8, 8, -5, 14, -5, 6, -12, 1, -9, 22,
                -12, -7, -29, -18, 59, 47, -25, -31, -33, -39, -38, -27, -15, -8, -45, -42, -26, -15, -32, -13, 8,
                4, 21, 31, 17, 22, 8, 17, 5, 16, 2, 0, 3, 34, 1, -1, 7, 9, 6, 5, 11, -9, 20, 7, -14, -17, 29, 99,
                15, -23, -32, -11, -27, -30, -24, -35, -18, -40, -22, -30, -34, -21, 1, 27, 13, 32, 29, 14, 29, 29,
                14, 7, 14, 11, 17, -5, 9, -4, 4, 14, -1, 8, 11, 6, 12, 8, -1, -40, 50, 98, -29, -19, -33, -22, -20,
                -30, -36, -5, -28, -28, -49, -45, -38, 5, 21, 12, 27, 13, 14, 14, 8, 22, 7, 28, 6, 18, 8, 8, 12, 1,
                -7, -13, -22, 19, -11, 19, -7, 4, -2, 43, 102, -17, -26, -33, -44, -15, -25, -36, -29, -19, -35,
                -24, -33, -10, -8, 18, 44, 17, 21, 12, 13, 38, 1, 14, 10, 6, 13, 2, -2, 12 };
        long timeStamp = 0;

        for (int i = 0; i < input.length; i++) {
            ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER,
                    SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
            shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
            shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
            shimmerData.ecgLaLL = 2000 + input[i];
            shimmerData.ecgRaLL = 2000;
            shimmerData.timestamp = (int) timeStamp;
            timeStamp += 512;

            this.received(shimmerData);

        }

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mInternalSensorMonitoring) {
            // And cancel the alarm.
            AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
            am.cancel(mBigBrotherService);

            Intent intent = new Intent();
            intent.setAction(BigBrotherConstants.ACTION_COMMAND_BROADCAST);
            intent.putExtra("message", BigBrotherConstants.SERVICE_OFF);
            sendBroadcast(intent);

            // Tell the user about what we did.
            Toast.makeText(Graphs1Activity.this, R.string.service_unscheduled, Toast.LENGTH_LONG).show();
        }

        mDataOutHandler.close();

        if (mDataUpdateTimer != null) {
            mDataUpdateTimer.cancel();
            mDataUpdateTimer.purge();
        }

        if (mRespRateAverageTimer != null) {
            mRespRateAverageTimer.cancel();
            mRespRateAverageTimer.purge();
        }

        Log.i(TAG, this.getClass().getSimpleName() + ".onDestroy()");

        // Send stop command to every shimmer device
        // You might think that it would be better to iterate through the mBioSensors table
        // instead of the mBioParameters table but it's actually easier this way
        for (GraphBioParameter param : mBioParameters) {
            if (param.isShimmer && param.shimmerNode != null) {
                ShimmerNonSpineSetupSensor setup = new ShimmerNonSpineSetupSensor();
                setup.setSensor(param.shimmerSensorConstant);

                String deviceAddress = SharedPref.getDeviceForParam(this, param.title1);
                if (deviceAddress != null) {

                    setup.setBtAddress(Util.AsciiBTAddressToBytes(deviceAddress));
                    setup.setCommand(ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_STOPPED);

                    Log.d(TAG, String.format("Setting up Shimmer sensor: %s (%s) (%d) SHIMMER_COMMAND_STOPPED",
                            param.shimmerNode.getPhysicalID(), deviceAddress, param.shimmerSensorConstant));
                    mManager.setup(param.shimmerNode, setup);

                }
            }
        }
        this.unregisterReceiver(this.mCommandReceiver);
    }

    @Override
    protected void onStart() {
        super.onStart();

        mIsActive = true;

        // we need to register a SPINEListener implementation to the SPINE manager instance
        // to receive sensor node data from the Spine server
        // (I register myself since I'm a SPINEListener implementation!)
        mManager.addListener(this);

        Log.i(TAG, this.getClass().getSimpleName() + ".onStart()");

        // Set up filter intents so we can receive broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.t2.biofeedback.service.status.BROADCAST");
        this.registerReceiver(this.mCommandReceiver, filter);

        int displaySampleTime = 1000;
        String s = SharedPref.getString(this, "display_sample_rate", "1");
        mDisplaySampleRate = Integer.parseInt(s);

        switch (mDisplaySampleRate) {
        default:
        case 1:
            displaySampleTime = 1000;
            Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz");
            break;
        case 10:
            displaySampleTime = 100;
            Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz");
            break;
        case 100:
            displaySampleTime = 10;
            Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz");
            break;
        case 9999:
            displaySampleTime = 9999;
            Log.d(TAG, "Setting display sample rate to match sensor sample rate");
            break;
        }

        if (mDisplaySampleRate != 9999) {
            // Set up a timer to do graphical updates
            mDataUpdateTimer = new Timer();
            mDataUpdateTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    TimerMethod();
                }

            }, 0, displaySampleTime);
        }

        // Set up a timer for GSR average reporting (10 seconds

        mRespRateAverageTimer = new Timer();
        mRespRateAverageTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                respRateAverageMethod();
            }

        }, 0, 10000);

        if (mAntHrmEnabled) {
            mAntServiceBound = bindService(new Intent(this, ANTPlusService.class), mConnection, BIND_AUTO_CREATE);
        }

    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mDataUpdateTimer != null)
            mDataUpdateTimer.cancel();

        if (mAntManager != null) {
            saveAntState();
            mAntManager.setCallbacks(null);

            if (mAntManager.isChannelOpen(AntPlusManager.HRM_CHANNEL)) {
                Log.d(TAG, "onClick (HRM): Close channel");
                mAntManager.closeChannel(AntPlusManager.HRM_CHANNEL);
            }

        }
        if (mAntServiceBound) {
            unbindService(mConnection);
        }

        Log.i(TAG, this.getClass().getSimpleName() + ".onStop()");
    }

    @Override
    protected void onPause() {
        super.onPause();

        mIsActive = false;

        // *******************
        // Make sure to to this or else you will get more and more notifications from Spine as you 
        // go into and out of activities!
        // Also make sure to do this in on pause (as opposed to onStop or ondestroy.
        // This will prevent you from receiving messages possibly requested by another activity
        mManager.removeListener(this);

        Log.i(TAG, this.getClass().getSimpleName() + ".onPause()");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.i(TAG, this.getClass().getSimpleName() + ".onResume()");
        mLastRespRateTime = System.currentTimeMillis();
        int mRespRateTotal = 0;
        int mRespRateIndex = 0;

        // Set up Device data chart
        generateChart();

        mManager.discoveryWsn(); // discoveryCompleted() is called after this is done      
    }

    @Override
    public void newNodeDiscovered(Node newNode) {
        Log.d(TAG, this.getClass().getSimpleName() + ".newNodeDiscovered()" + newNode.toString());
    }

    @Override
    public void received(ServiceMessage msg) {

    }

    /**
     * This is where we receive sensor data that comes through the actual
     * Spine channel. 
     * @param data      Generic Spine data packet. Should be cast to specifid data type indicated by data.getFunctionCode()
     *
     * @see spine.SPINEListener#received(spine.datamodel.Data)
     */
    @Override
    public void received(Data data) {
        //Log.d(TAG, this.getClass().getSimpleName() + ".received()"); 

        if (data != null) {
            switch (data.getFunctionCode()) {

            // E-Health board
            case SPINEFunctionConstants.FEATURE: {
                FeatureData featureData = (FeatureData) data;

                Feature[] feats = featureData.getFeatures();

                if (feats.length < 2) {
                    break;
                }
                Feature firsFeat = feats[0];
                Feature Feat2 = feats[1];
                int airFlow = firsFeat.getCh1Value();
                int scaledTemp = firsFeat.getCh2Value();
                float temp = (float) scaledTemp / (65535F / 9F) + 29F;
                int BPM = firsFeat.getCh3Value();
                int SPO2 = firsFeat.getCh4Value();
                int scaledConductance = Feat2.getCh1Value();
                float conductance = (float) scaledConductance / (65535F / 4F);
                Log.d(TAG, "E-health Values = " + airFlow + ", " + temp + ", " + BPM + ", " + SPO2 + ", "
                        + conductance + ", ");
                synchronized (mKeysLock) {
                    mBioParameters.get(eHealthAirFlowPos).rawValue = airFlow;
                    mBioParameters.get(eHealthAirFlowPos).scaledValue = (int) map(airFlow, 0, 360, 0, 100);

                    mBioParameters.get(eHealthTempPos).rawValue = (int) temp;
                    mBioParameters.get(eHealthTempPos).scaledValue = (int) map(temp, 29, 40, 0, 100);

                    mBioParameters.get(eHealthHeartRatePos).rawValue = BPM;
                    mBioParameters.get(eHealthHeartRatePos).scaledValue = (int) map(BPM, 30, 220, 0, 100);

                    mBioParameters.get(eHealthSpO2Pos).rawValue = SPO2;
                    mBioParameters.get(eHealthSpO2Pos).scaledValue = SPO2;

                    mBioParameters.get(eHealthGSRPos).rawValue = (int) map(scaledConductance, 0, 65535, 0, 100);
                    mBioParameters.get(eHealthGSRPos).scaledValue = (int) map(scaledConductance, 0, 65535, 0, 100);
                    ;

                    DataOutPacket packet = new DataOutPacket();
                    packet.add(DataOutHandlerTags.RAW_HEARTRATE, BPM);
                    packet.add(DataOutHandlerTags.RAW_GSR, conductance);
                    packet.add(DataOutHandlerTags.RAW_SKINTEMP, temp);
                    packet.add(DataOutHandlerTags.SPO2, SPO2);
                    packet.add(DataOutHandlerTags.AIRFLOW, airFlow);
                    try {
                        mDataOutHandler.handleDataOut(packet);
                    } catch (DataOutHandlerException e) {
                        Log.e(TAG, e.toString());
                        //                  e.printStackTrace();
                    }

                }

                break;
            }

            case SPINEFunctionConstants.HEARTBEAT: {

                synchronized (mKeysLock) {

                    HeartBeatData thisData = (HeartBeatData) data;

                    int scaled = (thisData.getBPM()) / 2;

                    if (mHeartRateSource == HEARTRATE_ANT) {
                        mBioParameters.get(heartRatePos).rawValue = thisData.getBPM();
                        mBioParameters.get(heartRatePos).scaledValue = scaled;
                    }

                    // Send data to output
                    DataOutPacket packet = new DataOutPacket();
                    packet.add(DataOutHandlerTags.RAW_HEARTRATE, thisData.getBPM());
                    try {
                        mDataOutHandler.handleDataOut(packet);
                    } catch (DataOutHandlerException e) {
                        Log.e(TAG, e.toString());
                        //                  e.printStackTrace();
                    }

                    // See if we are configured to update display every time we get sensor data
                    if (mDisplaySampleRate == 9999 && mIsActive) {
                        this.runOnUiThread(Timer_Tick);
                    }

                }

                break;
            }

            case SPINEFunctionConstants.SHIMMER: {
                Node node = data.getNode();
                numTicsWithoutData = 0;

                Node source = data.getNode();
                ShimmerData shimmerData = (ShimmerData) data;

                synchronized (mKeysLock) {
                    switch (shimmerData.sensorCode) {
                    case SPINESensorConstants.SHIMMER_GSR_SENSOR:

                        mBioDataProcessor.processShimmerGSRData(shimmerData, mConfiguredGSRRange);

                        mBioParameters.get(gsrPos).rawValue = (int) (mBioDataProcessor.mGsrConductance * 1000); // scale by 1000 to fit a float into an int
                        double scaled = mBioDataProcessor.mGsrConductance * 10;
                        mBioParameters.get(gsrPos).scaledValue = (int) scaled;

                        // See if we are configured to update display every time we get sensor data
                        if (mDisplaySampleRate == 9999 && mIsActive) {
                            this.runOnUiThread(Timer_Tick);
                        }

                        break;
                    case SPINESensorConstants.SHIMMER_EMG_SENSOR:

                        mBioDataProcessor.processShimmerEMGData(shimmerData);

                        scaled = MathExtra.scaleData((float) shimmerData.emg, 4000F, 0F, 100);
                        mBioParameters.get(emgPos).rawValue = (int) scaled;
                        mBioParameters.get(emgPos).scaledValue = (int) scaled;

                        // See if we are configured to update display every time we get sensor data
                        if (mDisplaySampleRate == 9999) {
                            this.runOnUiThread(Timer_Tick);
                        }

                        break;
                    case SPINESensorConstants.SHIMMER_ECG_SENSOR:

                        // If we're receiving packets from shimmer egg then swith the heartrate to shimmer
                        // Otherwise we'll leave it at the default which is zephyr
                        mHeartRateSource = HEARTRATE_SHIMMER;

                        mBioDataProcessor.processShimmerECGData(shimmerData);

                        scaled = (mBioDataProcessor.mRawEcg + 50) / 2;
                        mBioParameters.get(ecgPos).rawValue = (int) scaled;
                        mBioParameters.get(ecgPos).scaledValue = (int) scaled;

                        if (mHeartRateSource == HEARTRATE_SHIMMER) {
                            mBioParameters.get(heartRatePos).rawValue = mBioDataProcessor.mShimmerHeartRate;
                            mBioParameters.get(heartRatePos).scaledValue = mBioDataProcessor.mShimmerHeartRate;
                        }

                        // See if we are configured to update display every time we get sensor data
                        if (mDisplaySampleRate == 9999) {
                            this.runOnUiThread(Timer_Tick);
                        }

                        break;
                    }
                }
                break;
            }

            case SPINEFunctionConstants.ZEPHYR: {

                numTicsWithoutData = 0;
                mBioDataProcessor.processZephyrData(data);

                synchronized (mKeysLock) {

                    if (mHeartRateSource == HEARTRATE_ZEPHYR) {
                        mBioParameters.get(heartRatePos).scaledValue = mBioDataProcessor.mZephyrHeartRate / 3;
                        mBioParameters.get(heartRatePos).rawValue = mBioDataProcessor.mZephyrHeartRate;
                    }

                    mBioParameters.get(respRatePos).scaledValue = (int) mBioDataProcessor.mRespRate * 5;
                    mBioParameters.get(respRatePos).rawValue = (int) mBioDataProcessor.mRespRate;

                    mBioParameters.get(skinTempPos).scaledValue = (int) mBioDataProcessor.mSkinTempF;
                    mBioParameters.get(skinTempPos).rawValue = (int) mBioDataProcessor.mSkinTempF;
                }

                synchronized (mRespRateAverageLock) {
                    mRespRateTotal += mBioDataProcessor.mRespRate;
                    mRespRateIndex++;
                }

                // See if we are configured to update display every time we get sensor data
                if (mDisplaySampleRate == 9999) {
                    this.runOnUiThread(Timer_Tick);
                }
                break;
            } // End case SPINEFunctionConstants.ZEPHYR:         

            case SPINEFunctionConstants.MINDSET: {
                Node source = data.getNode();
                MindsetData mindsetData = (MindsetData) data;

                mBioDataProcessor.processMindsetData(data, currentMindsetData);

                if (mindsetData.exeCode == Constants.EXECODE_SPECTRAL
                        || mindsetData.exeCode == Constants.EXECODE_RAW_ACCUM) {

                    numTicsWithoutData = 0;

                    synchronized (mKeysLock) {
                        for (int i = 0; i < MindsetData.NUM_BANDS + 2; i++) { // 2 extra, for attention and meditation
                            mBioParameters.get(i).scaledValue = currentMindsetData.getFeatureValue(i);
                            mBioParameters.get(i).rawValue = currentMindsetData.getFeatureValue(i);
                        }
                    }
                    // See if we are configured to update display every time we get sensor data
                    if (mDisplaySampleRate == 9999) {
                        this.runOnUiThread(Timer_Tick);
                    }
                }

                if (mindsetData.exeCode == Constants.EXECODE_POOR_SIG_QUALITY) {

                    // Now show signal strength as bars
                    int sigQuality = mindsetData.poorSignalStrength & 0xff;
                    ImageView image = (ImageView) findViewById(R.id.imageView1);
                    if (sigQuality == 200)
                        image.setImageResource(R.drawable.signal_bars0);
                    else if (sigQuality > 150)
                        image.setImageResource(R.drawable.signal_bars1);
                    else if (sigQuality > 100)
                        image.setImageResource(R.drawable.signal_bars2);
                    else if (sigQuality > 50)
                        image.setImageResource(R.drawable.signal_bars3);
                    else if (sigQuality > 25)
                        image.setImageResource(R.drawable.signal_bars4);
                    else
                        image.setImageResource(R.drawable.signal_bars5);

                    if (sigQuality == 200 && mPrevSigQuality != 200) {
                        Toast.makeText(getApplicationContext(),
                                "Headset not makeing good skin contact. Please Adjust", Toast.LENGTH_LONG).show();
                    }
                    mPrevSigQuality = sigQuality;
                }

                break;
            } // End case SPINEFunctionConstants.MINDSET:
            } // End switch (data.getFunctionCode())
        } // End if (data != null)
    }

    // Note that this is really inaptly named. This simply gets called a certain time period after
    // discovery is initiated (2 sec?)
    @Override
    public void discoveryCompleted(Vector activeNodes) {
        Log.d(TAG, this.getClass().getSimpleName() + ".discoveryCompleted()");

        // Tell the bluetooth service to send us a list of bluetooth devices and system status
        // Response comes in public void onStatusReceived(BioFeedbackStatus bfs) STATUS_PAIRED_DEVICES
        mManager.pollBluetoothDevices();
    }

    /**
     * This callback is called whenever the AndroidBTService sends us an indication that
     * it is actively trying to establish a BT connection to one of the nodes.
     * 
     * @see com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener#onStatusReceived(com.t2.SpineReceiver.BioFeedbackStatus)
     */
    @Override
    public void onStatusReceived(BioFeedbackStatus bfs) {
        String name = bfs.name;
        if (name == null)
            name = "sensor node";
        if (bfs.messageId.equals("CONN_CONNECTING")) {
            Log.d(TAG, "Received command : " + bfs.messageId + " to " + name);
            Toast.makeText(getApplicationContext(), "Connecting to " + name, Toast.LENGTH_SHORT).show();
        } else if (bfs.messageId.equals("CONN_ANY_CONNECTED")) {
            Log.d(TAG, "Received command : " + bfs.messageId + " to " + name);
            // Something has connected - discover what it was
            mManager.discoveryWsn();
            Toast.makeText(getApplicationContext(), name + " Connected", Toast.LENGTH_SHORT).show();
        } else if (bfs.messageId.equals("CONN_CONNECTION_LOST")) {
            Log.d(TAG, "Received command : " + bfs.messageId + " to " + name);
            Toast.makeText(getApplicationContext(), name + " Connection lost ****", Toast.LENGTH_SHORT).show();
        } else if (bfs.messageId.equals("STATUS_PAIRED_DEVICES")) {
            Log.d(TAG, "Received command : " + bfs.messageId + " to " + name);
            Log.d(TAG, bfs.address);

            // We don't want to take any action unless we're ready to go 
            if (!mIsActive)
                return;
            populateBioSensors(bfs.address);
            validateBioSensors();

            // Send startup command to every shimmer device
            for (GraphBioParameter param : mBioParameters) {
                if (param.isShimmer && param.shimmerNode != null) {
                    ShimmerNonSpineSetupSensor setup = new ShimmerNonSpineSetupSensor();
                    setup.setSensor(param.shimmerSensorConstant);

                    String deviceAddress = SharedPref.getDeviceForParam(this, param.title1);
                    if (deviceAddress != null) {

                        setup.setBtAddress(Util.AsciiBTAddressToBytes(deviceAddress));

                        byte startShimmercommand;
                        String s = SharedPref.getString(this, "sensor_sample_rate", "4");
                        int sensorSampleRate = Integer.parseInt(s);

                        Log.d(TAG, "Initializing sensor sample rate to " + sensorSampleRate);
                        switch (sensorSampleRate) {
                        default:
                        case 4:
                            startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_4HZ_AUTORANGE;
                            break;
                        case 10:
                            startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_10HZ_AUTORANGE;
                            break;
                        case 32:
                            startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_32HZ_AUTORANGE;
                            break;
                        case 50:
                            startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_50HZ_AUTORANGE;
                            break;
                        case 64:
                            startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_64HZ_AUTORANGE;
                            break;
                        case 100:
                            startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_100HZ_AUTORANGE;
                            break;
                        case 125:
                            startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_125HZ_AUTORANGE;
                            break;

                        }

                        setup.setCommand(startShimmercommand);
                        mConfiguredGSRRange = Util.getGsrRangeFromShimmerCommand(startShimmercommand);

                        Log.d(TAG, String.format("Setting up Shimmer sensor: %s (%s) (%d) SHIMMER_COMMAND_RUNNING",
                                param.shimmerNode.getPhysicalID(), deviceAddress, param.shimmerSensorConstant));
                        mManager.setup(param.shimmerNode, setup);

                    } else {

                    }
                }
            }
        }
    }

    /**
     * This method is called directly by the timer and runs in the same thread as the timer
     * From here We call the method that will work with the UI through the runOnUiThread method.
     */
    private void TimerMethod() {
        this.runOnUiThread(Timer_Tick);
    }

    /**
     * This method runs in the same thread as the UI.
     */
    private Runnable Timer_Tick = new Runnable() {
        public void run() {
            numTicsWithoutData++;

            if (mPaused == true || currentMindsetData == null) {
                //            if (mPaused == true || currentMindsetData == null || numTicsWithoutData > 2) {
                return;
            }

            String bandValuesString = "";

            // Output a point for each visible key item
            int keyCount = mBioParameters.size();
            for (int i = 0; i < mBioParameters.size(); ++i) {
                GraphBioParameter item = mBioParameters.get(i);
                int rawValue = item.rawValue;
                int scaledValue = item.scaledValue;

                if (!item.visible) {
                    continue;
                }

                // Special case for GSR since it's actually a float scaled to fit into an int
                if (gsrPos == i) {
                    float conductance = (float) rawValue / 1000F;
                    bandValuesString += item.title1 + ":" + conductance + ", ";
                } else {
                    bandValuesString += item.title1 + ":" + rawValue + ", ";
                }

                item.series.add(mSpineChartX, scaledValue);
                if (item.series.getItemCount() > SPINE_CHART_SIZE) {
                    item.series.remove(0);
                }

            }
            mSpineChartX++;

            if (mDeviceChartView != null) {
                mDeviceChartView.repaint();
            }

            mTextInfoView.setText(bandValuesString);
        }
    };

    private void respRateAverageMethod() {
        this.runOnUiThread(respRate_Average_Tick);
    }

    /**
     * This method runs in the same thread as the UI.
     */
    private Runnable respRate_Average_Tick = new Runnable() {
        public void run() {

            if (mRespRateIndex == 0)
                return;

            final int rrAvg;
            synchronized (mRespRateAverageLock) {

                rrAvg = mRespRateTotal / mRespRateIndex;
                mRespRateTotal = 0;
                mRespRateIndex = 0;
            }

            // Send data to output
            DataOutPacket packet = new DataOutPacket();
            packet.add(DataOutHandlerTags.AVERAGE_RESP_RATE, rrAvg);
            try {
                mDataOutHandler.handleDataOut(packet);
            } catch (DataOutHandlerException e) {
                Log.e(TAG, e.toString());
                //            e.printStackTrace();
            }
        }
    };

    /**
     * Goes through all all parameters in "keyItems", it saves the visible ones to the long array toggledIds[]
     * Then calls setVisibleIds to save this long list to a string list in SharedPref at
     *  
     * "results_visible_ids_measure2"
     */
    private void saveVisibleKeyIds() {
        ArrayList<Long> toggledIds = new ArrayList<Long>();
        for (int i = 0; i < mBioParameters.size(); ++i) {
            GraphBioParameter item = mBioParameters.get(i);
            if (item.visible) {
                toggledIds.add(item.id);
            }
        }
        setVisibleIds(KEY_NAME, toggledIds);
    }

    /**
     * Saves long array of ids to a string array at 
     * "results_visible_ids_measure3"
     *   
     * @param keySuffix   Id of the array
     * @return         a long array containing ids of parameters that are visible
     */
    private void setVisibleIds(String keyName, ArrayList<Long> ids) {
        SharedPref.setValues(sharedPref, keyName, ",",
                ArraysExtra.toStringArray(ids.toArray(new Long[ids.size()])));
    }

    /**
     * 
     * @param keySuffix   id of the array
     * @param ids       long array of ids to save to Shared Params
     */
    private ArrayList<Long> getVisibleIds(String keyName) {
        String[] idsStrArr = SharedPref.getValues(sharedPref, keyName, ",", new String[0]);

        return new ArrayList<Long>(Arrays.asList(ArraysExtra.toLongArray(idsStrArr)));
    }

    /**
     * Returns a unique key color based on the current index and total count of parameters
     * @param currentIndex      Index of current parameter
     * @param totalCount      Total number of parameters
     * @return               Unique color based in inputs
     */
    protected int getKeyColor(int currentIndex, int totalCount) {
        float hue = currentIndex / (1.00f * totalCount) * 360.00f;

        return Color.HSVToColor(255, new float[] { hue, 1.0f, 1.0f });
    }

    public void onButtonClick(View v) {
        final int id = v.getId();
        switch (id) {
        //          case R.id.buttonBack:
        //             finish();
        //             
        //             break;
        //                    
        case R.id.buttonAddMeasure:

            boolean toggleArray[] = new boolean[mBioParameters.size()];
            for (int j = 0; j < mBioParameters.size(); ++j) {
                GraphBioParameter item = mBioParameters.get(j);
                if (item.visible)
                    toggleArray[j] = true;
                else
                    toggleArray[j] = false;
            }

            String[] measureNames = new String[mBioParameters.size()];
            int i = 0;
            for (GraphBioParameter item : mBioParameters) {
                measureNames[i++] = item.title1;
            }

            // Present dialog to allow user to choose which parameters to view in this activity
            AlertDialog.Builder alert = new AlertDialog.Builder(this);
            alert.setTitle(R.string.alert_dialog_measure_selector);
            //             alert.setMultiChoiceItems(R.array.measure_select_dialog_items,
            alert.setMultiChoiceItems(measureNames, toggleArray, new DialogInterface.OnMultiChoiceClickListener() {

                public void onClick(DialogInterface dialog, int whichButton, boolean isChecked) {

                    GraphBioParameter item = mBioParameters.get(whichButton);
                    item.visible = item.visible ? false : true;
                    saveVisibleKeyIds();
                    generateChart();
                }
            });
            alert.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    generateChart();
                }
            });

            alert.show();

            break;

        case R.id.buttonPause:
            if (mPaused == true) {
                mPaused = false;
                mPauseButton.getBackground().setColorFilter(Color.LTGRAY, PorterDuff.Mode.MULTIPLY);
                mPauseButton.setText(R.string.button_running);
                if (mLoggingEnabled) {
                    try {
                        mDataOutHandler.logNote(getString(R.string.un_paused));
                    } catch (DataOutHandlerException e) {
                        Log.e(TAG, e.toString());
                        e.printStackTrace();
                    } // data header
                }
                if (mLogCatEnabled) {
                    Log.d(TAG, "Un-Paused");
                }
            } else {
                mPaused = true;
                mPauseButton.getBackground().setColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY);
                mPauseButton.setText(R.string.button_pause);
                if (mLoggingEnabled) {
                    try {
                        mDataOutHandler.logNote(getString(R.string.paused));
                    } catch (DataOutHandlerException e) {
                        Log.e(TAG, e.toString());
                        e.printStackTrace();
                    } // data header
                }
                if (mLogCatEnabled) {
                    Log.d(TAG, "Paused");
                }
            }
            break;
        } // End switch      
    }

    /**
     * Sets up all parameters for display of both the chart on the screen 
     * AND a color coded display of the parameters and their values 
     */
    private void generateChart() {
        // Set up chart
        XYMultipleSeriesDataset deviceDataset = new XYMultipleSeriesDataset();
        XYMultipleSeriesRenderer deviceRenderer = new XYMultipleSeriesRenderer();

        LinearLayout layout = (LinearLayout) findViewById(R.id.deviceChart);
        if (mDeviceChartView != null) {
            layout.removeView(mDeviceChartView);
        }
        if (true) {
            mDeviceChartView = ChartFactory.getLineChartView(this, deviceDataset, deviceRenderer);
            mDeviceChartView.setBackgroundColor(Color.BLACK);
            layout.addView(mDeviceChartView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        }

        deviceRenderer.setShowLabels(false);
        deviceRenderer.setMargins(new int[] { 0, 5, 5, 0 });
        deviceRenderer.setShowAxes(true);
        deviceRenderer.setShowLegend(false);

        deviceRenderer.setZoomEnabled(false, false);
        deviceRenderer.setPanEnabled(false, false);
        deviceRenderer.setYAxisMin(0);
        deviceRenderer.setYAxisMax(100);

        SpannableStringBuilder sMeasuresText = new SpannableStringBuilder("Displaying: ");

        ArrayList<Long> visibleIds = getVisibleIds(KEY_NAME);

        int keyCount = mBioParameters.size();
        keyCount = mBioParameters.size();

        int lineNum = 0;
        for (int i = 0; i < mBioParameters.size(); ++i) {
            GraphBioParameter item = mBioParameters.get(i);

            item.visible = visibleIds.contains(item.id);
            if (!item.visible) {
                continue;
            }

            deviceDataset.addSeries(item.series);
            item.color = getKeyColor(i, keyCount);

            // Add name of the measure to the displayed text field
            ForegroundColorSpan fcs = new ForegroundColorSpan(item.color);
            int start = sMeasuresText.length();
            sMeasuresText.append(mBioParameters.get(i).title1 + ", ");
            int end = sMeasuresText.length();
            sMeasuresText.setSpan(fcs, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
            if (sMeasuresText.length() > 40 && lineNum == 0) {
                lineNum++;
            }

            XYSeriesRenderer seriesRenderer = new XYSeriesRenderer();
            seriesRenderer.setColor(item.color);
            seriesRenderer.setPointStyle(PointStyle.CIRCLE);

            deviceRenderer.addSeriesRenderer(seriesRenderer);
        }

        mMeasuresDisplayText.setText(sMeasuresText);
    }

    /**
     * Receives a json string containing data about all of the paired sensors
     * the adds a new BioSensor for each one to the mBioSensors collection
     * 
     * @param jsonString String containing info on all paired devices
     */
    private void populateBioSensors(String jsonString) {

        Log.d(TAG, this.getClass().getSimpleName() + " populateBioSensors");
        // Now clear it out and populate it. The only difference is that
        // if a sensor previously existed, then 
        mBioSensors.clear();
        try {
            JSONArray jsonArray = new JSONArray(jsonString);
            for (int i = 0; i < jsonArray.length(); i++) {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                Boolean enabled = jsonObject.getBoolean("enabled");
                String name = jsonObject.getString("name");
                String address = jsonObject.getString("address");
                int connectionStatus = jsonObject.getInt("connectionStatus");

                if (name.equalsIgnoreCase("system")) {
                    mBluetoothEnabled = enabled;
                } else {

                    Log.d(TAG, "Adding sensor " + name + ", " + address + (enabled ? ", enabled" : ", disabled")
                            + " : " + Util.connectionStatusToString(connectionStatus));
                    BioSensor bioSensor = new BioSensor(name, address, enabled);
                    bioSensor.mConnectionStatus = connectionStatus;
                    mBioSensors.add(bioSensor);
                }
            }
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
        }
    }

    /**
     * Validates sensors, makes sure that bluetooth is on and each sensor has a parameter associated with it
     */
    void validateBioSensors() {

        // First make sure that bluetooth is enabled
        if (!mBluetoothEnabled) {
            AlertDialog.Builder alert1 = new AlertDialog.Builder(this);

            alert1.setMessage("Bluetooth is not enabled on your device. Press OK to go to the wireless system"
                    + "settings to turn it on");

            alert1.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    mInstance.startActivityForResult(
                            new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS), BLUETOOTH_SETTINGS_ID);
                }
            });
            alert1.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                }
            });
            alert1.show();
        }
        String badSensorName = null;

        // Now make sure that every device has a parameter associated with it
        for (BioSensor sensor : mBioSensors) {
            if (sensor.mEnabled) {
                String param = SharedPref.getParamForDevice(mInstance, sensor.mBTAddress);
                //Log.d(TAG, "sensor: " + sensor.mBTName + ", parameter: " + param);
                if (param == null) {
                    badSensorName = sensor.mBTName;
                    break;
                }
            }
        } // end for (BioSensor sensor: mBioSensors)

        if (badSensorName != null) {
            AlertDialog.Builder alert1 = new AlertDialog.Builder(this);

            alert1.setMessage("Sensor " + badSensorName + " is enabled but "
                    + " does not have a parameter associated with it." + "Press Ok to associate one");

            alert1.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    Intent intent2 = new Intent(mInstance, DeviceManagerActivity.class);
                    mInstance.startActivity(intent2);
                }
            });
            alert1.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                }
            });
            alert1.show();

        }
    }

    /**
     * Wrapper for BioParameter that has a graph element
     * 
     * @author scott.coleman
     *
     */
    static class GraphBioParameter extends BioParameter {
        public XYSeries series;
        public Boolean isShimmer;
        Node shimmerNode;
        byte shimmerSensorConstant;

        public GraphBioParameter(long id, String title1, String title2, Boolean enabled) {
            super(id, title1, title2, enabled);
            isShimmer = false;
            shimmerNode = null;
            shimmerSensorConstant = 0;
            series = new XYSeries(title1);
        }

    }

    // ----------------------------------------------------------
    // ANT specific stuff
    //-----------------------------------------------------------

    @Override
    public void errorCallback() {
        Log.e(TAG, "ANT error");

    }

    @Override
    public void notifyAntStateChanged() {
        // TODO Auto-generated method stub

    }

    @Override
    public void notifyChannelStateChanged(byte channel) {
        // TODO Auto-generated method stub

    }

    @Override
    public void notifyChannelDataChanged(byte channel) {
        //      Log.i(TAG, "notifyChannelDataChanged");

        HeartBeatData thisData = new HeartBeatData();
        thisData.setFunctionCode(SPINEFunctionConstants.HEARTBEAT);
        thisData.setBPM(mAntManager.getBPM());
        this.received(thisData);
    }

    private final ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            //This is very unlikely to happen with a local service (ie. one in the same process)
            mAntManager.setCallbacks(null);
            mAntManager = null;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mAntManager = ((ANTPlusService.LocalBinder) service).getManager();
            mAntManager.setCallbacks(Graphs1Activity.this);
            loadAntState();
            notifyAntStateChanged();

            // Start ANT automatically
            mAntManager.doEnable();
            Log.i(TAG, "Starting heart rate data");
            mAntManager.openChannel(AntPlusManager.HRM_CHANNEL, true);
            mAntManager.requestReset();

        }
    };

    /**
     * Store application persistent data.
     */
    private void saveAntState() {
        // Save current Channel Id in preferences
        // We need an Editor object to make changes
        SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
        SharedPreferences.Editor editor = settings.edit();
        editor.putInt("DeviceNumberHRM", mAntManager.getDeviceNumberHRM());
        editor.putInt("DeviceNumberSDM", mAntManager.getDeviceNumberSDM());
        editor.putInt("DeviceNumberWGT", mAntManager.getDeviceNumberWGT());
        editor.putInt("ProximityThreshold", mAntManager.getProximityThreshold());
        editor.putInt("BufferThreshold", mAntManager.getBufferThreshold());
        editor.commit();
    }

    /**
     * Retrieve application persistent data.
     */
    private void loadAntState() {
        // Restore preferences
        SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
        mAntManager.setDeviceNumberHRM((short) settings.getInt("DeviceNumberHRM", ANT_WILDCARD));
        mAntManager.setDeviceNumberSDM((short) settings.getInt("DeviceNumberSDM", ANT_WILDCARD));
        mAntManager.setDeviceNumberWGT((short) settings.getInt("DeviceNumberWGT", ANT_WILDCARD));
        mAntManager.setProximityThreshold((byte) settings.getInt("ProximityThreshold", ANT_DEFAULT_BIN));
        mAntManager.setBufferThreshold((short) settings.getInt("BufferThreshold", ANT_DEFAULT_BUFFER_THRESHOLD));
    }

    private long map(long x, long in_min, long in_max, long out_min, long out_max) {
        return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
    }

    private double map(double x, double in_min, double in_max, double out_min, double out_max) {
        return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
    }
}