com.t2.compassionMeditation.MeditationActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.t2.compassionMeditation.MeditationActivity.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.File;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import org.achartengine.model.XYSeries;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.t2health.lib1.BioParameter;
import org.t2health.lib1.BioSensor;
import org.t2health.lib1.dsp.T2MovingAverageFilter;

import bz.org.t2health.lib.activity.BaseActivity;
import bz.org.t2health.lib.analytics.Analytics;

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.QueryBuilder;
import com.oregondsp.signalProcessing.filter.iir.ChebyshevI;

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.activity.BTServiceManager;
import com.t2.biofeedback.device.shimmer.ShimmerDevice;
import com.t2.compassionDB.BioSession;
import com.t2.compassionDB.BioUser;
import com.t2.compassionUtils.MathExtra;
import com.t2.compassionUtils.TMovingAverageFilter;
import com.t2.compassionUtils.RateOfChange;
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;

import com.t2.Constants;

import spine.datamodel.Node;
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.ServiceMessage;
import spine.datamodel.ShimmerData;
import spine.datamodel.ZephyrData;
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.media.MediaPlayer;
import android.media.ToneGenerator;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

//Need the following import to get access to the app resources, since this
//class is in a sub-package.
import com.t2.R;

public class MeditationActivity extends BaseActivity implements OnBioFeedbackMessageRecievedListener, SPINEListener,
        View.OnTouchListener, SeekBar.OnSeekBarChangeListener, AntPlusManager.Callbacks {
    private static final String TAG = "BFDemo";
    private static final String mActivityVersion = "2.4";
    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;

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

    private String mAppId = "bioZenMeditation";

    /**
     * Flag to set for manual debugging of activity (Shows values on screen
     */
    private boolean mDebug = false;

    private int mIntroFade = 255;
    private int mSubTimerClick = 100;

    private String mUserId;
    private String mSessionId;

    Dao<BioUser, Integer> mBioUserDao;
    Dao<BioSession, Integer> mBioSessionDao;

    BioUser mCurrentBioUser = null;
    BioSession mCurrentBioSession = null;
    List<BioUser> currentUsers;

    File mLogFile;

    /**
     * Number of seconds remaining in the session
     *   This is set initially from SharedPref.PREF_SESSION_LENGTH
     */
    private int mSecondsRemaining = 0;
    private int mSecondsTotal = 0;

    /**
     * Determines state of on screen button
     *   true = this is a start button
     *   false = this is a quit button
     */
    private boolean mButtonIsStart = true;

    /**
     * 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 mInstance of this activity
     */
    private static MeditationActivity mInstance;

    /**
     * Toggled by screen press, indicates whether or not to show buttons/tools on screen
     */
    private boolean mShowingControls = true;

    /**
     * Signal quality as reported by the mindset headset
     * Value 0 - 200 0 is best, 199 is worst, 200 is no connection 
     */
    private int mSigQuality = 200;

    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   

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

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

    private boolean mLoggingEnabled = true;
    private boolean mLogCatEnabled = true;
    private boolean mPaused = true;

    private Boolean mBluetoothEnabled = false;

    // UI Elements
    private Button mToggleLogButton;
    private Button mLlogMarkerButton;
    private ImageButton mPauseButton;
    private TextView mTextInfoView;
    private TextView mTextViewInstructions;
    private TextView mTextBioHarnessView;
    private ImageView mBackgroundImage;
    private ImageView mForegroundImage;
    private ImageView mBaseImage;

    private SeekBar mSeekBar;
    private ImageView mSignalImage;
    private ImageView mCountdownImageView;
    private TextView mCountdownTextView;

    private T2HeartRateDetector mHeartRateDetector = new T2HeartRateDetector();
    private T2MovingAverageFilter mGroundLeadFilter = new T2MovingAverageFilter(64);
    ChebyshevI mEcgBaselineFilter;
    ChebyshevI mEcgNoiseFilter;

    /**
     * Moving average used to smooth the display of the band of interest
     */
    private TMovingAverageFilter mMovingAverage;
    private int mMovingAverageSize = 10;

    private TMovingAverageFilter mMovingAverageROC;
    private int mMovingAverageSizeROC = 6;

    float maxMindsetValue = 0;
    float minMindsetValue = 0;
    float AverageMindsetValue = 0;

    /**
     * Gain used to determine how band of interest affects the background image 
     */
    private double mAlphaGain = 1;

    protected SharedPreferences sharedPref;

    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>();
    boolean mIsActive = false;

    // 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;

    MindsetData currentMindsetData;
    ZephyrData currentZephyrData = new ZephyrData();

    private int mBackgroundControlParameter = MindsetData.THETA_ID; // Default to theta
    private int mForegroundControlParameter = MindsetData.THETA_ID; // Default to theta
    private int numSecsWithoutData = 0;

    private String mAudioTrackResourceName;
    private String mBaseImageResourceName;

    private static Object mKeysLock = new Object();
    private RateOfChange mRateOfChange;
    private int mRateOfChangeSize = 6;

    int mForegroundRawValue = 0;;
    double mForegroundScaledValue = 0;;
    int mForegroundFilteredValue = 0;;

    private MediaPlayer mMediaPlayer;
    private ToneGenerator mToneGenerator;

    private boolean mShowForeground;
    private boolean mShowToast;
    /**
     * Temp variable used in SelectUser() to indicate which user was selected
     *  Note that this needed to be a member variable because of error: 
     *     "Cannot refer to a non-final variable mSelection inside an inner 
     *      class defined in a different method" 
     */
    private int mSelection = 0;

    boolean mSaveRawWave;
    boolean mAllowComments;
    boolean mShowAGain;
    String[] mBioHarnessParameters;
    String mLogFileName = "";

    private Node mShimmerNode = null;

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

    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;
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, this.getClass().getSimpleName() + ".onCreate()");

        mInstance = this;
        mRateOfChange = new RateOfChange(mRateOfChangeSize);

        mIntroFade = 255;

        // 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.buddah_activity_layout);

        sharedPref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());

        currentMindsetData = new MindsetData(this);
        mSaveRawWave = SharedPref.getBoolean(this, BioZenConstants.PREF_SAVE_RAW_WAVE,
                BioZenConstants.PREF_SAVE_RAW_WAVE_DEFAULT);

        mShowAGain = SharedPref.getBoolean(this, BioZenConstants.PREF_SHOW_A_GAIN,
                BioZenConstants.PREF_SHOW_A_GAIN_DEFAULT);

        mAllowComments = SharedPref.getBoolean(this, BioZenConstants.PREF_COMMENTS,
                BioZenConstants.PREF_COMMENTS_DEFAULT);

        mShowForeground = SharedPref.getBoolean(this, "show_lotus", true);
        mShowToast = SharedPref.getBoolean(this, "show_toast", true);

        mAudioTrackResourceName = SharedPref.getString(this, "audio_track", "None");
        mBaseImageResourceName = SharedPref.getString(this, "background_images", "Sunset");

        mBioHarnessParameters = getResources().getStringArray(R.array.bioharness_parameters_array);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        String s = SharedPref.getString(this, BioZenConstants.PREF_SESSION_LENGTH, "10");
        mSecondsRemaining = Integer.parseInt(s) * 60;
        mSecondsTotal = mSecondsRemaining;

        s = SharedPref.getString(this, BioZenConstants.PREF_ALPHA_GAIN, "5");
        mAlphaGain = Float.parseFloat(s);

        mMovingAverage = new TMovingAverageFilter(mMovingAverageSize);
        mMovingAverageROC = new TMovingAverageFilter(mMovingAverageSizeROC);

        View v1 = findViewById(R.id.buddahView);
        v1.setOnTouchListener(this);

        Resources resources = this.getResources();
        AssetManager assetManager = resources.getAssets();

        // Set up member variables to UI Elements
        mTextInfoView = (TextView) findViewById(R.id.textViewInfo);
        mTextBioHarnessView = (TextView) findViewById(R.id.textViewBioHarness);
        mCountdownTextView = (TextView) findViewById(R.id.countdownTextView);
        mCountdownImageView = (ImageView) findViewById(R.id.imageViewCountdown);

        mPauseButton = (ImageButton) findViewById(R.id.buttonPause);
        mSignalImage = (ImageView) findViewById(R.id.imageView1);
        mTextViewInstructions = (TextView) findViewById(R.id.textViewInstructions);

        // Note that the seek bar is a debug thing - used only to set the
        // alpha of the buddah image manually for visual testing
        mSeekBar = (SeekBar) findViewById(R.id.seekBar1);
        mSeekBar.setOnSeekBarChangeListener(this);

        // Scale such that values to the right of center are scaled 1 - 10
        // and values to the left of center are scaled 0 = .99999
        if (mAlphaGain > 1.0) {
            mSeekBar.setProgress(50 + (int) (mAlphaGain * 5));
        } else {
            int i = (int) (mAlphaGain * 50);
            mSeekBar.setProgress(i);
        }
        //      mSeekBar.setProgress((int) mAlphaGain * 10);      

        // Controls start as invisible, need to touch screen to activate them
        mCountdownTextView.setVisibility(View.INVISIBLE);
        mCountdownImageView.setVisibility(View.INVISIBLE);
        mTextInfoView.setVisibility(View.INVISIBLE);
        mTextBioHarnessView.setVisibility(View.INVISIBLE);
        mPauseButton.setVisibility(View.INVISIBLE);
        mPauseButton.setVisibility(View.VISIBLE);
        mSeekBar.setVisibility(View.INVISIBLE);

        mBackgroundImage = (ImageView) findViewById(R.id.buddahView);
        mForegroundImage = (ImageView) findViewById(R.id.lotusView);
        mBaseImage = (ImageView) findViewById(R.id.backgroundView);

        if (!mShowForeground) {
            mForegroundImage.setVisibility(View.INVISIBLE);
        }

        int resource = 0;
        if (mBaseImageResourceName.equalsIgnoreCase("Buddah")) {
            mBackgroundImage.setImageResource(R.drawable.buddha);
            mForegroundImage.setImageResource(R.drawable.lotus_flower);
            mBaseImage.setImageResource(R.drawable.none_bg);
        } else if (mBaseImageResourceName.equalsIgnoreCase("Bob")) {
            mBackgroundImage.setImageResource(R.drawable.bigbob);
            mForegroundImage.setImageResource(R.drawable.red_nose);
            mBaseImage.setImageResource(R.drawable.none_bg);
        } else if (mBaseImageResourceName.equalsIgnoreCase("Sunset")) {
            mBackgroundImage.setImageResource(R.drawable.eeg_layer);
            mForegroundImage.setImageResource(R.drawable.breathing_rate);
            mBaseImage.setImageResource(R.drawable.meditation_bg);
        }

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

        // Since Mindset is a static node we have to manually put it in the active node list
        // Note that the sensor id 0xfff1 (-15) is a reserved id for this particular sensor
        Node mindsetNode = null;
        mindsetNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_MINDSET));
        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);

        // 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);

        try {
            PackageManager packageManager = this.getPackageManager();
            PackageInfo info = packageManager.getPackageInfo(this.getPackageName(), 0);
            mApplicationVersion = info.versionName;
            Log.i(TAG, "BioZen Application Version: " + mApplicationVersion + ", Activity Version: "
                    + mActivityVersion);
        } catch (NameNotFoundException e) {
            Log.e(TAG, e.toString());
        }

        // First create GraphBioParameters for each of the ECG static params (ie mindset)      
        int itemId = 0;
        eegPos = itemId; // eeg always comes first
        mBioParameters.clear();

        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, HR, Skin Temp, Resp Rate
        String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names);

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

            if (paramName.equalsIgnoreCase("EEG"))
                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);
        }

        // 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());
        long sessionId = SharedPref.getLong(this, "bio_session_start_time", 0);

        mUserId = SharedPref.getString(this, "SelectedUser", "");

        // Now get the database object associated with this user

        try {
            mBioUserDao = getHelper().getBioUserDao();
            mBioSessionDao = getHelper().getBioSessionDao();

            QueryBuilder<BioUser, Integer> builder = mBioUserDao.queryBuilder();
            builder.where().eq(BioUser.NAME_FIELD_NAME, mUserId);
            builder.limit(1);
            //         builder.orderBy(ClickCount.DATE_FIELD_NAME, false).limit(30);
            List<BioUser> list = mBioUserDao.query(builder.prepare());

            if (list.size() == 1) {
                mCurrentBioUser = list.get(0);
            } else if (list.size() == 0) {
                try {
                    mCurrentBioUser = new BioUser(mUserId, System.currentTimeMillis());
                    mBioUserDao.create(mCurrentBioUser);
                } catch (SQLException e1) {
                    Log.e(TAG, "Error creating user " + mUserId, e1);
                }
            } else {
                Log.e(TAG, "General Database error" + mUserId);
            }

        } catch (SQLException e) {
            Log.e(TAG, "Can't find user: " + mUserId, e);

        }

        mSignalImage.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) {
            mSignalImage.setVisibility(View.VISIBLE);
            mTextViewInstructions.setVisibility(View.VISIBLE);

        } else {
            mSignalImage.setVisibility(View.INVISIBLE);
            mTextViewInstructions.setVisibility(View.INVISIBLE);
        }

        mDataOutHandler = new DataOutHandler(this, mUserId, 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);

        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;
        }

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

        if (mInternalSensorMonitoring) {
            // IntentSender Launches our service scheduled with with the alarm manager 
            mBigBrotherService = PendingIntent.getService(MeditationActivity.this, 0,
                    new Intent(MeditationActivity.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(MeditationActivity.this, R.string.service_scheduled, Toast.LENGTH_LONG).show();

        }

    } // End onCreate(Bundle savedInstanceState)

    @Override
    public void onBackPressed() {
        handlePause("Session Complete"); // Allow opportinuty for a note
    }

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

        Log.i(TAG, this.getClass().getSimpleName() + ".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(MeditationActivity.this, R.string.service_unscheduled, Toast.LENGTH_LONG).show();
        }

        // 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.i(TAG, String.format("Setting up Shimmer sensor: %s (%s) (%d) SHIMMER_COMMAND_STOPPED",
                            param.shimmerNode.getPhysicalID(), deviceAddress, param.shimmerSensorConstant));
                    mManager.setup(param.shimmerNode, setup);
                }
            }
        }

        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }

        this.unregisterReceiver(this.mCommandReceiver);

        mDataOutHandler.close();

    } // End onDestroy()

    @Override
    protected void onStart() {
        super.onStart();
        mIsActive = true;
        Log.i(TAG, this.getClass().getSimpleName() + ".onStart()");

        //      mPauseButton.setText("Start");
        mPauseButton.setImageResource(R.drawable.start);

        // 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);

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

        }, 0, 10);

        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
        }

        int resource = 0;
        if (mAudioTrackResourceName.contains("Air Synth"))
            resource = R.raw.dave_luxton_air_synth_meditation;
        if (mAudioTrackResourceName.contains("Entity and Echo"))
            resource = R.raw.dave_luxton_entity_and_echo_meditation;
        if (mAudioTrackResourceName.contains("Starlit Lake"))
            resource = R.raw.dave_luxton_starlit_lake_meditation;

        if (resource != 0) {
            mMediaPlayer = MediaPlayer.create(this, resource);
            if (mMediaPlayer != null) {
                mMediaPlayer.start();
                mMediaPlayer.setLooping(true);
            }

        }

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

    /**
     * Convert seconds to string display of hours:minutes:seconds 
     * @param time Total number of seconds to display
     * @return String formated to hours:minutes:seconds
     */
    String secsToHMS(long time) {

        long secs = time;
        long hours = secs / 3600;
        secs = secs % 3600;
        long mins = secs / 60;
        secs = secs % 60;

        return hours + ":" + mins + ":" + secs;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        //   this.getMenuInflater().inflate(R.menu.menu_compassion_meditation, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.settings:
            Intent intent2 = new Intent(this, BTServiceManager.class);
            this.startActivity(intent2);
            return true;

        case R.id.discover:
            mManager.discoveryWsn();

            return true;

        case R.id.about:
            String content = "National Center for Telehealth and Technology (T2)\n\n";
            content += "BioZen Application\n";
            content += "Application Version: " + mApplicationVersion + "\n";
            content += "Activity Version: " + mActivityVersion;

            AlertDialog.Builder alert = new AlertDialog.Builder(this);

            alert.setTitle("About");
            alert.setMessage(content);
            alert.show();
            return true;

        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * 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.i(TAG, "Received command : " + bfs.messageId + " to " + name);
            if (mShowToast)
                Toast.makeText(getApplicationContext(), "**** Connecting to Sensor Node ****", Toast.LENGTH_SHORT)
                        .show();
        } else if (bfs.messageId.equals("CONN_ANY_CONNECTED")) {
            Log.i(TAG, "Received command : " + bfs.messageId + " to " + name);
            // Something has connected - discover what it was
            mManager.discoveryWsn();
            if (mShowToast)
                Toast.makeText(getApplicationContext(), "**** Sensor Node Connected ****", Toast.LENGTH_SHORT)
                        .show();
        } else if (bfs.messageId.equals("CONN_CONNECTION_LOST")) {
            Log.i(TAG, "Received command : " + bfs.messageId + " to " + name);
            if (mShowToast)
                Toast.makeText(getApplicationContext(), "**** Sensor Node Connection lost ****", Toast.LENGTH_SHORT)
                        .show();
        } else if (bfs.messageId.equals("STATUS_PAIRED_DEVICES")) {
            Log.i(TAG, "Received command : " + bfs.messageId + " to " + name);
            Log.i(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 = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_4HZ_3M3;
                        byte startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_4HZ_AUTORANGE;
                        setup.setCommand(startShimmercommand);

                        mConfiguredGSRRange = Util.getGsrRangeFromShimmerCommand(startShimmercommand);

                        Log.i(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 {

                    }
                }
            }
        }
    }

    @Override
    public void newNodeDiscovered(Node newNode) {
    }

    @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) {

        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)
                            .setScaledValue((int) ((double) map(airFlow, 0, 360, 0, 255) * mAlphaGain));

                    mBioParameters.get(eHealthTempPos).rawValue = (int) temp;
                    mBioParameters.get(eHealthTempPos)
                            .setScaledValue((int) ((double) map(temp, 29, 40, 0, 255) * mAlphaGain));

                    mBioParameters.get(eHealthHeartRatePos).rawValue = BPM;
                    mBioParameters.get(eHealthHeartRatePos)
                            .setScaledValue((int) ((double) map(BPM, 30, 220, 0, 255) * mAlphaGain));

                    mBioParameters.get(eHealthSpO2Pos).rawValue = SPO2;
                    mBioParameters.get(eHealthSpO2Pos)
                            .setScaledValue((int) ((double) map(SPO2, 0, 100, 0, 255) * mAlphaGain));

                    mBioParameters.get(eHealthGSRPos).rawValue = (int) map(scaledConductance, 0, 65535, 0, 100);
                    mBioParameters.get(eHealthGSRPos)
                            .setScaledValue((int) ((double) map(scaledConductance, 0, 65535, 0, 255) * mAlphaGain));

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

                }

                break;
            }
            case SPINEFunctionConstants.SHIMMER: {
                Node node = data.getNode();
                numSecsWithoutData = 0;

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

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

                    mBioDataProcessor.processShimmerGSRData(shimmerData, mConfiguredGSRRange);

                    double scaled = MathExtra.scaleData((float) mBioDataProcessor.mGsrResistance, 2000000F, 0F, 255)
                            * mAlphaGain;
                    synchronized (mKeysLock) {
                        mBioParameters.get(gsrPos).rawValue = (int) scaled;
                        mBioParameters.get(gsrPos).setScaledValue((int) scaled);
                    }
                    break;

                case SPINESensorConstants.SHIMMER_EMG_SENSOR:

                    mBioDataProcessor.processShimmerEMGData(shimmerData);

                    scaled = MathExtra.scaleData((float) shimmerData.emg, 4000F, 0F, 255) * mAlphaGain;
                    synchronized (mKeysLock) {
                        mBioParameters.get(emgPos).rawValue = (int) scaled;
                        mBioParameters.get(emgPos).setScaledValue((int) scaled);
                    }
                    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 = MathExtra.scaleData((float) shimmerData.ecg, 4000F, 0F, 255) * mAlphaGain;
                    synchronized (mKeysLock) {
                        mBioParameters.get(ecgPos).rawValue = (int) scaled;
                        mBioParameters.get(ecgPos).setScaledValue((int) scaled);
                    }
                    break;

                } // End switch (shimmerData.sensorCode)

                break;
            }

            case SPINEFunctionConstants.ZEPHYR: {

                mBioDataProcessor.processZephyrData(data);

                Node source = data.getNode();
                Feature[] feats = ((FeatureData) data).getFeatures();
                Feature firsFeat = feats[0];

                currentZephyrData.heartRate = mBioDataProcessor.mZephyrHeartRate;
                currentZephyrData.respRate = (int) mBioDataProcessor.mRespRate;
                currentZephyrData.skinTemp = (int) mBioDataProcessor.mSkinTempF;

                synchronized (mKeysLock) {
                    double scaled = MathExtra.scaleData((float) mBioDataProcessor.mSkinTempF, 110F, 70F, 255)
                            * mAlphaGain;
                    mBioParameters.get(skinTempPos).rawValue = (int) mBioDataProcessor.mSkinTempF;
                    mBioParameters.get(skinTempPos).setScaledValue((int) scaled);

                    scaled = MathExtra.scaleData((float) mBioDataProcessor.mZephyrHeartRate, 250F, 20F, 255)
                            * mAlphaGain;
                    mBioParameters.get(heartRatePos).rawValue = mBioDataProcessor.mZephyrHeartRate;
                    mBioParameters.get(heartRatePos).setScaledValue((int) scaled);

                    scaled = MathExtra.scaleData((float) mBioDataProcessor.mRespRate, 120F, 5F, 255) * mAlphaGain;
                    mBioParameters.get(respRatePos).rawValue = (int) mBioDataProcessor.mRespRate;
                    mBioParameters.get(respRatePos).setScaledValue((int) scaled);
                }

                numSecsWithoutData = 0;

                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_POOR_SIG_QUALITY) {

                    synchronized (mKeysLock) {
                        currentMindsetData.poorSignalStrength = mindsetData.poorSignalStrength;
                    }

                    mSigQuality = mindsetData.poorSignalStrength & 0xff;

                    if (mShowingControls || mSigQuality == 200)
                        mSignalImage.setVisibility(View.VISIBLE);
                    else
                        mSignalImage.setVisibility(View.INVISIBLE);

                    if (mSigQuality == 200)
                        mSignalImage.setImageResource(R.drawable.signal_bars0);
                    else if (mSigQuality > 150)
                        mSignalImage.setImageResource(R.drawable.signal_bars1);
                    else if (mSigQuality > 100)
                        mSignalImage.setImageResource(R.drawable.signal_bars2);
                    else if (mSigQuality > 50)
                        mSignalImage.setImageResource(R.drawable.signal_bars3);
                    else if (mSigQuality > 25)
                        mSignalImage.setImageResource(R.drawable.signal_bars4);
                    else
                        mSignalImage.setImageResource(R.drawable.signal_bars5);

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

                }

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

                    synchronized (mKeysLock) {
                        if (mPaused == false) {
                            numSecsWithoutData = 0;

                            synchronized (mKeysLock) {
                                for (int i = 0; i < MindsetData.NUM_BANDS + 2; i++) { // 2 extra, for attention and meditation
                                    //                                float scaled = MathExtra.scaleData((float)currentMindsetData.getFeatureValue(i), 100F, 20F, 255, (float)mAlphaGain);
                                    float scaled = MathExtra.scaleData(
                                            (float) currentMindsetData.getFeatureValue(i), 100F, 20F, 255);
                                    scaled *= mAlphaGain;

                                    if (scaled > 255)
                                        scaled = 255;

                                    mBioParameters.get(i).rawValue = currentMindsetData.getFeatureValue(i);
                                    mBioParameters.get(i).setScaledValue((int) scaled);
                                }
                            }
                        } // End if (mPaused == false)
                    }
                }
                break;
            } // End case SPINEFunctionConstants.MINDSET:
            } // End switch (data.getFunctionCode())
        } // End if (data != null)
    }

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

    /**
     * Converts a byte array to an integer
     * @param bytes      Bytes to convert
     * @return         Integer representaion of byte array
     */
    public static int byteArrayToInt(byte[] bytes) {
        int val = 0;

        for (int i = 0; i < bytes.length; i++) {
            int n = (bytes[i] < 0 ? (int) bytes[i] + 256 : (int) bytes[i]) << (8 * i);
            val += n;
        }

        return val;
    }

    /**
     * Hansles UI button clicks
     * @param v
     */
    public void onButtonClick(View v) {
        final int id = v.getId();
        switch (id) {
        case R.id.buttonBack:
            finish();
            break;

        case R.id.buttonPause:
            if (mPaused) {
                mPaused = false;
                mPauseButton.setImageResource(R.drawable.quit);
                mTextViewInstructions.setVisibility(View.INVISIBLE);
                toggleControls();
                Toast.makeText(mInstance,
                        "You may toggle the screen controls back \non by pressing anywhere on the screen",
                        Toast.LENGTH_SHORT).show();

            } else {
                handlePause(mUserId + mSessionId + " Paused");
            }
            break;

        } // End switch      
    }

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

            // We get here every .01 second
            if (mPaused == true || currentMindsetData == null || currentZephyrData == null) {
                return;
            }

            if (mSubTimerClick-- > 0) {
                if (mIntroFade > 0) {

                    mBackgroundImage.setAlpha(mIntroFade--);
                    mForegroundImage.setAlpha(mIntroFade--);

                }
                return;
            } else {
                mSubTimerClick = 100;

            }

            // We get here every 1 second
            numSecsWithoutData++;

            if (mLoggingEnabled == true && numSecsWithoutData < 2) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
                String currentDateTimeString = DateFormat.getDateInstance().format(new Date());
                currentDateTimeString = sdf.format(new Date());

                String logData = currentDateTimeString + ", " + currentZephyrData.getLogDataLine();
                logData += currentMindsetData.getLogDataLine(currentMindsetData.exeCode, mSaveRawWave) + "\n";

            }

            // Background parameters
            // NOTE that this is a huge hack (and needs to be fixed.
            // mBackgroundControlParameter (which comes from preferences, is based on the list R.array.bands_of_interest_array
            // which FOR NOW, matches the assignments here when assigning mBioParameters itemID's.
            // These all must be changed to use the same list
            // To further complicate the matter mBioParameters itemID's are assigned based on MindsetData.NUM_BANDS
            // AND R.array.parameter_names
            GraphBioParameter backgroundParam = mBioParameters.get(mBackgroundControlParameter);
            String backgroundLogText = String.format("Background: %s (raw, scaled, filtered): %d, %d, %d",
                    backgroundParam.title1, backgroundParam.rawValue, backgroundParam.scaledValue,
                    backgroundParam.getFilteredScaledValue());
            mTextInfoView.setText(backgroundLogText);
            Log.d(TAG, backgroundLogText);

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
            String currentDateTimeString = DateFormat.getDateInstance().format(new Date());
            currentDateTimeString = sdf.format(new Date());
            try {
                mDataOutHandler.logNote(currentDateTimeString + ", " + currentDateTimeString);
            } catch (DataOutHandlerException e) {
                Log.e(TAG, e.toString());
                e.printStackTrace();
            }

            // Foreground parameters
            GraphBioParameter foregroundParam = mBioParameters.get(mForegroundControlParameter);
            mForegroundRawValue = foregroundParam.rawValue;
            //          int iBackgroundAlphaValue = (int) ((double) backgroundParam.getFilteredScaledValue() * mAlphaGain);
            int iBackgroundAlphaValue = backgroundParam.getFilteredScaledValue();

            // We want to update the rate of change once every second
            foregroundParam.updateRateOfChange();
            int iForegroundAlphaValue = 255 - foregroundParam.getRateOfChangeScaledValue();
            String foregroundLogText = String.format("Foreground: %s (scaled, ROC, ALPHA): %d, %d %d",
                    foregroundParam.title1, backgroundParam.getFilteredScaledValue(),
                    foregroundParam.getRateOfChangeScaledValue(), iForegroundAlphaValue);
            mTextBioHarnessView.setText(foregroundLogText);
            //         Log.d(TAG, foregroundLogText);

            if (mIntroFade <= 0) {
                mBackgroundImage.setAlpha(iBackgroundAlphaValue);

                if (mShowForeground) {
                    mForegroundImage.setAlpha(iForegroundAlphaValue);
                } else {
                    mForegroundImage.setAlpha(0);
                }
            }

            if (mSecondsRemaining-- > 0) {
                mCountdownTextView.setText(secsToHMS(mSecondsRemaining));
            } else {
                if (mMediaPlayer != null) {
                    mMediaPlayer.stop();
                }

                mMediaPlayer = MediaPlayer.create(mInstance, R.raw.wind_chime_1);
                if (mMediaPlayer != null) {
                    mMediaPlayer.start();
                    mMediaPlayer.setLooping(true);
                }

                handlePause("Session Complete"); // Allow opportinuty for a note
            }
        }
    };

    @Override
    protected void onPause() {
        Log.i(TAG, this.getClass().getSimpleName() + ".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);

        mDataUpdateTimer.purge();
        mDataUpdateTimer.cancel();
        currentMindsetData.saveScaleData();

        super.onPause();
    }

    @Override
    protected void onStop() {
        Log.i(TAG, this.getClass().getSimpleName() + ".onStop()");
        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);
        }

        super.onStop();
    }

    @Override
    protected void onRestart() {
        Log.i(TAG, this.getClass().getSimpleName() + ".onRestart()");

        super.onRestart();
    }

    @Override
    protected void onResume() {
        Log.i(TAG, this.getClass().getSimpleName() + ".onResume()");

        restoreState();

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

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

        super.onResume();
    }

    void restoreState() {
        String s = SharedPref.getString(this, BioZenConstants.PREF_BAND_OF_INTEREST, "0");
        mBackgroundControlParameter = Integer.parseInt(s);

        s = SharedPref.getString(this, BioZenConstants.PREF_BIOHARNESS_PARAMETER_OF_INTEREST,
                BioZenConstants.PREF_BIOHARNESS_PARAMETER_OF_INTEREST_DEFAULT);

        mForegroundControlParameter = Integer.parseInt(s);

    }

    void toggleControls() {
        // Toggle showing screen buttons/controls
        if (mShowingControls) {
            mShowingControls = false;
            mCountdownImageView.setVisibility(View.INVISIBLE);
            mCountdownTextView.setVisibility(View.INVISIBLE);
            mTextInfoView.setVisibility(View.INVISIBLE);
            mTextBioHarnessView.setVisibility(View.INVISIBLE);
            mPauseButton.setVisibility(View.INVISIBLE);
            mSeekBar.setVisibility(View.INVISIBLE);

        } else {
            mShowingControls = true;
            mCountdownImageView.setVisibility(View.VISIBLE);
            mCountdownTextView.setVisibility(View.VISIBLE);
            if (mDebug)
                mTextInfoView.setVisibility(View.VISIBLE);
            if (mDebug)
                mTextBioHarnessView.setVisibility(View.VISIBLE);
            mPauseButton.setVisibility(View.VISIBLE);
            mSeekBar.setVisibility(mShowAGain ? View.VISIBLE : View.INVISIBLE);

        }
    }

    @Override
    public boolean onTouch(View arg0, MotionEvent arg1) {
        if (!mPaused)
            toggleControls();

        return false;
    }

    @Override
    public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
        //      mAlphaGain = arg1/10;
        //      if (mAlphaGain <= 0) mAlphaGain = 1;

        // Scale such that values to the right of center are scaled 1 - 10
        // and values to the left of center are scaled 0 = .99999
        if (arg1 > 50) {
            float farg1 = (float) arg1;
            mAlphaGain = (farg1 - 50) / 5;
        } else {
            mAlphaGain = (float) arg1 / (float) 50;
        }

    }

    @Override
    public void onStartTrackingTouch(SeekBar arg0) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar arg0) {
        SharedPref.putString(this, BioZenConstants.PREF_ALPHA_GAIN, new Float(mAlphaGain).toString());

        Toast.makeText(this, " AlphaGain changed to " + mAlphaGain, Toast.LENGTH_SHORT).show();

    }

    /**
     * Handles the pause button press
     *   Brings up a dialog that allows the user to either restart, or quit
     *   Note that in any case the text entered by the user is saved to the log file
     */
    public void handlePause(String message) {

        mPaused = true;

        //         if (mMediaPlayer != null) {
        //            mMediaPlayer.pause();
        //         }

        Intent intent1 = new Intent(mInstance, EndSessionActivity.class);
        mInstance.startActivityForResult(intent1, BioZenConstants.END_SESSION_ACTIVITY);
    }

    /**
     * Writes a specific note to the log - adding a time stamp
     * @param note Note to save to log
     */
    void addNoteToLog(String note) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);

        String currentDateTimeString = DateFormat.getDateInstance().format(new Date());
        currentDateTimeString = sdf.format(new Date());
        try {
            mDataOutHandler.logNote(currentDateTimeString + ", " + note + "\n");
        } catch (DataOutHandlerException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
        case BioZenConstants.END_SESSION_ACTIVITY:
            if (data != null) {
                int action = data.getIntExtra(BioZenConstants.END_SESSION_ACTIVITY_RESULT,
                        BioZenConstants.END_SESSION_RESTART);

                switch (action) {

                default:
                case BioZenConstants.END_SESSION_RESTART:
                    break;

                case BioZenConstants.END_SESSION_SAVE:
                    EndAndSaveSession(data);
                    break;

                case BioZenConstants.END_SESSION_QUIT:
                    if (mLogFile != null)
                        mLogFile.delete();
                    Analytics.onEndSession(this);
                    finish();
                    break;
                }
            } else {
                if (mLogFile != null)
                    mLogFile.delete();
                Analytics.onEndSession(this);
                finish();
            }

            break;
        }
    }

    void EndAndSaveSession(Intent data) {

        String notes = "";
        String categoryName = "";
        if (data != null) {
            notes = data.getStringExtra(BioZenConstants.END_SESSION_ACTIVITY_NOTES);

            categoryName = data.getStringExtra(BioZenConstants.END_SESSION_ACTIVITY_CATEGORY);

            if (categoryName == null)
                categoryName = "";
            if (notes == null)
                notes = "";
        }

        addNoteToLog(notes);

        // -----------------------------
        // Save stats for session
        // -----------------------------
        // Create a session data point for this session (to put in data
        mCurrentBioSession = new BioSession(mCurrentBioUser, System.currentTimeMillis());
        if (mCurrentBioSession != null) {
            mCurrentBioSession.comments += notes;
            mCurrentBioSession.category = categoryName;

            for (int i = 0; i < BioZenConstants.MAX_KEY_ITEMS; i++) {
                mCurrentBioSession.maxFilteredValue[i] = mBioParameters.get(i).getMaxFilteredValue();
                mCurrentBioSession.minFilteredValue[i] = mBioParameters.get(i).getMinFilteredValue() != 9999
                        ? mBioParameters.get(i).getMinFilteredValue()
                        : 0;
                mCurrentBioSession.avgFilteredValue[i] = mBioParameters.get(i).getAvgFilteredValue();
                mCurrentBioSession.keyItemNames[i] = mBioParameters.get(i).title1;
            }

            int secondsCompleted = mSecondsTotal - mSecondsRemaining;
            float precentComplete = (float) secondsCompleted / (float) mSecondsTotal;
            mCurrentBioSession.precentComplete = (int) (precentComplete * 100);
            mCurrentBioSession.secondsCompleted = secondsCompleted;
            mCurrentBioSession.logFileName = mLogFileName;

            mCurrentBioSession.mindsetBandOfInterestIndex = mBackgroundControlParameter;
            mCurrentBioSession.bioHarnessParameterOfInterestIndex = mForegroundControlParameter;

            // Udpate the database with the current session
            try {
                mBioSessionDao.create(mCurrentBioSession);
            } catch (SQLException e1) {
                Log.e(TAG, "Error saving current session to database", e1);
            }

        }

        finish();

    }

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

    }

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

    }

    @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) {
        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(MeditationActivity.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;
    }

}