uk.org.openseizuredetector.SdServer.java Source code

Java tutorial

Introduction

Here is the source code for uk.org.openseizuredetector.SdServer.java

Source

/*
  Pebble_sd - a simple accelerometer based seizure detector that runs on a
  Pebble smart watch (http://getpebble.com).
    
  See http://openseizuredetector.org for more information.
    
  Copyright Graham Jones, 2015.
    
  This file is part of pebble_sd.
    
  Pebble_sd is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
      
  Pebble_sd is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
      
  You should have received a copy of the GNU General Public License
  along with pebble_sd.  If not, see <http://www.gnu.org/licenses/>.
    
*/

package uk.org.openseizuredetector;

import java.util.Map;
import fi.iki.elonen.NanoHTTPD;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.AssetFileDescriptor;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;
import java.util.Timer;
import java.util.TimerTask;
import java.io.*;
import java.util.*;
import java.util.UUID;
import java.util.StringTokenizer;
import java.net.URL;
import android.net.Uri;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.nio.IntBuffer;
import java.nio.ByteOrder;
import android.text.format.Time;
import org.json.JSONObject;
import org.json.JSONArray;

import com.getpebble.android.kit.Constants;
import com.getpebble.android.kit.PebbleKit;
import com.getpebble.android.kit.util.PebbleDictionary;

/**
 * Based on example at:
 * http://stackoverflow.com/questions/14309256/using-nanohttpd-in-android
 * and 
 * http://developer.android.com/guide/components/services.html#ExtendingService
 */
public class SdServer extends Service {
    private UUID SD_UUID = UUID.fromString("03930f26-377a-4a3d-aa3e-f3b19e421c9d");
    private int NSAMP = 512; // Number of samples in fft input dataset.

    private int KEY_DATA_TYPE = 1;
    private int KEY_ALARMSTATE = 2;
    private int KEY_MAXVAL = 3;
    private int KEY_MAXFREQ = 4;
    private int KEY_SPECPOWER = 5;
    private int KEY_SETTINGS = 6;
    private int KEY_ALARM_FREQ_MIN = 7;
    private int KEY_ALARM_FREQ_MAX = 8;
    private int KEY_WARN_TIME = 9;
    private int KEY_ALARM_TIME = 10;
    private int KEY_ALARM_THRESH = 11;
    private int KEY_POS_MIN = 12; // position of first data point in array
    private int KEY_POS_MAX = 13; // position of last data point in array.
    private int KEY_SPEC_DATA = 14; // Spectrum data
    private int KEY_ROIPOWER = 15;
    private int KEY_NMIN = 16;
    private int KEY_NMAX = 17;
    private int KEY_ALARM_RATIO_THRESH = 18;
    private int KEY_BATTERY_PC = 19;

    // Values of the KEY_DATA_TYPE entry in a message
    private int DATA_TYPE_RESULTS = 1; // Analysis Results
    private int DATA_TYPE_SETTINGS = 2; // Settings
    private int DATA_TYPE_SPEC = 3; // FFT Spectrum (or part of a spectrum)

    // Notification ID
    private int NOTIFICATION_ID = 1;

    private NotificationManager mNM;

    private WebServer webServer = null;
    private final static String TAG = "SdServer";
    private Looper mServiceLooper;
    public Time mPebbleStatusTime;
    private boolean mPebbleAppRunningCheck = false;
    private Timer statusTimer = null;
    private Timer settingsTimer = null;
    private Timer dataLogTimer = null;
    private HandlerThread thread;
    private WakeLock mWakeLock = null;
    public SdData sdData;
    private PebbleKit.PebbleDataReceiver msgDataHandler = null;
    private boolean mCancelAudible = false;
    private boolean mAudibleAlarm = false;
    private boolean mAudibleWarning = false;
    private boolean mAudibleFaultWarning = false;
    private boolean mSMSAlarm = false;
    private String[] mSMSNumbers;
    private String mSMSMsgStr = "default SMS Message";
    public Time mSMSTime = null; // last time we sent an SMS Alarm (limited to one per minute)
    private boolean mLogAlarms = true;
    private boolean mLogData = false;
    private File mOutFile;

    private final IBinder mBinder = new SdBinder();

    /**
     * class to handle binding the MainApp activity to this service
     * so it can access sdData.
     */
    public class SdBinder extends Binder {
        SdServer getService() {
            return SdServer.this;
        }
    }

    /**
     * Constructor for SdServer class - does not do much!
     */
    public SdServer() {
        super();
        sdData = new SdData();
        Log.v(TAG, "SdServer Created");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "sdServer.onBind()");
        return mBinder;
    }

    /**
     * onCreate() - called when services is created.  Starts message
     * handler process to listen for messages from other processes.
     */
    @Override
    public void onCreate() {
        Log.v(TAG, "onCreate()");

        // Create a wake lock, but don't use it until the service is started.
        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");
    }

    /**
     * onStartCommand - start the web server and the message loop for
     * communications with other processes.
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(TAG, "onStartCommand() - SdServer service starting");

        // Update preferences.
        Log.v(TAG, "onStartCommand() - calling updatePrefs()");
        updatePrefs();

        // Display a notification icon in the status bar of the phone to
        // show the service is running.
        Log.v(TAG, "showing Notification");
        showNotification();

        // Record last time we sent an SMS so we can limit rate of SMS
        // sending to one per minute.
        mSMSTime = new Time(Time.getCurrentTimezone());

        // Start receiving data from the pebble watch
        startPebbleServer();

        // Start timer to check status of pebble regularly.
        mPebbleStatusTime = new Time(Time.getCurrentTimezone());
        //getPebbleStatus();
        if (statusTimer == null) {
            Log.v(TAG, "onCreate(): starting status timer");
            statusTimer = new Timer();
            statusTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    getPebbleStatus();
                }
            }, 0, 1000);
        } else {
            Log.v(TAG, "onCreate(): status timer already running.");
        }

        // Start timer to retrieve pebble settings regularly.
        getPebbleSdSettings();
        if (settingsTimer == null) {
            Log.v(TAG, "onCreate(): starting settings timer");
            settingsTimer = new Timer();
            settingsTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    getPebbleSdSettings();
                }
            }, 0, 1000 * 60);
        } else {
            Log.v(TAG, "onCreate(): settings timer already running.");
        }

        // Start timer to log data regularly..
        if (dataLogTimer == null) {
            Log.v(TAG, "onCreate(): starting dataLog timer");
            dataLogTimer = new Timer();
            dataLogTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    logData();
                }
            }, 0, 1000 * 60);
        } else {
            Log.v(TAG, "onCreate(): dataLog timer already running.");
        }

        // Start the web server
        startWebServer();

        // Apply the wake-lock to prevent CPU sleeping (very battery intensive!)
        if (mWakeLock != null) {
            mWakeLock.acquire();
            Log.v(TAG, "Applied Wake Lock to prevent device sleeping");
        } else {
            Log.d(TAG, "mmm...mWakeLock is null, so not aquiring lock.  This shouldn't happen!");
        }

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.v(TAG, "onDestroy(): SdServer Service stopping");
        // release the wake lock to allow CPU to sleep and reduce
        // battery drain.
        if (mWakeLock != null) {
            mWakeLock.release();
            Log.v(TAG, "Released Wake Lock to allow device to sleep.");
        } else {
            Log.d(TAG, "mmm...mWakeLock is null, so not releasing lock.  This shouldn't happen!");
        }

        try {
            // Stop the status timer
            if (statusTimer != null) {
                Log.v(TAG, "onDestroy(): cancelling status timer");
                statusTimer.cancel();
                statusTimer.purge();
                statusTimer = null;
            }
            // Stop the settings timer
            if (settingsTimer != null) {
                Log.v(TAG, "onDestroy(): cancelling settings timer");
                settingsTimer.cancel();
                settingsTimer.purge();
                settingsTimer = null;
            }
            // Cancel the notification.
            Log.v(TAG, "onDestroy(): cancelling notification");
            mNM.cancel(NOTIFICATION_ID);
            // Stop web server
            Log.v(TAG, "onDestroy(): stopping web server");
            stopWebServer();
            // Stop pebble message handler.
            Log.v(TAG, "onDestroy(): stopping pebble server");
            stopPebbleServer();
            // stop this service.
            Log.v(TAG, "onDestroy(): calling stopSelf()");
            stopSelf();

        } catch (Exception e) {
            Log.v(TAG, "Error in onDestroy() - " + e.toString());
        }
    }

    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        Log.v(TAG, "showNotification()");
        CharSequence text = "OpenSeizureDetector Server Running";
        Notification notification = new Notification(R.drawable.star_of_life_24x24, text,
                System.currentTimeMillis());
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
        notification.setLatestEventInfo(this, "OpenSeizureDetector Server", text, contentIntent);
        notification.flags |= Notification.FLAG_NO_CLEAR;
        mNM = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNM.notify(NOTIFICATION_ID, notification);
    }

    /* from http://stackoverflow.com/questions/12154940/how-to-make-a-beep-in-android */
    /**
     * beep for duration miliseconds, but only if mAudibleAlarm is set.
     */
    private void beep(int duration) {
        ToneGenerator toneG = new ToneGenerator(AudioManager.STREAM_ALARM, 100);
        toneG.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, duration);
        Log.v(TAG, "beep()");
    }

    /*
     * beep, provided mAudibleAlarm is set
     */
    public void faultWarningBeep() {
        if (mCancelAudible) {
            Log.v(TAG, "faultWarningBeep() - CancelAudible Active - silent beep...");
        } else {
            if (mAudibleFaultWarning) {
                beep(10);
                Log.v(TAG, "faultWarningBeep()");
            } else {
                Log.v(TAG, "faultWarningBeep() - silent...");
            }
        }
    }

    /*
     * beep, provided mAudibleAlarm is set
     */
    public void alarmBeep() {
        if (mCancelAudible) {
            Log.v(TAG, "alarmBeep() - CancelAudible Active - silent beep...");
        } else {
            if (mAudibleAlarm) {
                beep(1000);
                Log.v(TAG, "alarmBeep()");
            } else {
                Log.v(TAG, "alarmBeep() - silent...");
            }
        }
    }

    /*
     * beep, provided mAudibleWarning is set
     */
    public void warningBeep() {
        if (mCancelAudible) {
            Log.v(TAG, "warningBeep() - CancelAudible Active - silent beep...");
        } else {
            if (mAudibleWarning) {
                beep(100);
                Log.v(TAG, "warningBeep()");
            } else {
                Log.v(TAG, "warningBeep() - silent...");
            }
        }
    }

    /**
     * Sends SMS Alarms to the telephone numbers specified in mSMSNumbers[]
     */
    public void sendSMSAlarm() {
        if (mSMSAlarm) {
            Log.v(TAG, "sendSMSAlarm() - Sending to " + mSMSNumbers.length + " Numbers");
            Time tnow = new Time(Time.getCurrentTimezone());
            tnow.setToNow();
            String dateStr = tnow.format("%Y-%m-%d %H-%M-%S");
            SmsManager sm = SmsManager.getDefault();
            for (int i = 0; i < mSMSNumbers.length; i++) {
                Log.v(TAG, "sendSMSAlarm() - Sending to " + mSMSNumbers[i]);
                sm.sendTextMessage(mSMSNumbers[i], null, mSMSMsgStr + " - " + dateStr, null, null);
            }
        } else {
            Log.v(TAG, "sendSMSAlarm() - SMS Alarms Disabled - not doing anything!");
            Toast toast = Toast.makeText(getApplicationContext(), "SMS Alarms Disabled - not doing anything!",
                    Toast.LENGTH_SHORT);
            toast.show();
        }
    }

    /**
     * Set this server to receive pebble data by registering it as
     * A PebbleDataReceiver
     */
    private void startPebbleServer() {
        Log.v(TAG, "StartPebbleServer()");
        final Handler handler = new Handler();
        msgDataHandler = new PebbleKit.PebbleDataReceiver(SD_UUID) {
            @Override
            public void receiveData(final Context context, final int transactionId, final PebbleDictionary data) {
                Log.v(TAG,
                        "Received message from Pebble - data type=" + data.getUnsignedIntegerAsLong(KEY_DATA_TYPE));
                // If we have a message, the app must be running
                mPebbleAppRunningCheck = true;
                PebbleKit.sendAckToPebble(context, transactionId);
                //Log.v(TAG,"Message is: "+data.toJsonString());
                if (data.getUnsignedIntegerAsLong(KEY_DATA_TYPE) == DATA_TYPE_RESULTS) {
                    Log.v(TAG, "DATA_TYPE = Results");
                    sdData.dataTime.setToNow();
                    Log.v(TAG, "sdData.dataTime=" + sdData.dataTime);

                    sdData.alarmState = data.getUnsignedIntegerAsLong(KEY_ALARMSTATE);
                    sdData.maxVal = data.getUnsignedIntegerAsLong(KEY_MAXVAL);
                    sdData.maxFreq = data.getUnsignedIntegerAsLong(KEY_MAXFREQ);
                    sdData.specPower = data.getUnsignedIntegerAsLong(KEY_SPECPOWER);
                    sdData.roiPower = data.getUnsignedIntegerAsLong(KEY_ROIPOWER);
                    sdData.alarmPhrase = "Unknown";
                    if (sdData.alarmState == 0) {
                        sdData.alarmPhrase = "OK";
                    }
                    if (sdData.alarmState == 1) {
                        sdData.alarmPhrase = "WARNING";
                        if (mLogAlarms) {
                            Log.v(TAG, "WARNING - Loggin to SD Card");
                            writeAlarmToSD();
                            logData();
                        } else {
                            Log.v(TAG, "WARNING");
                        }
                        warningBeep();
                    }
                    if (sdData.alarmState == 2) {
                        sdData.alarmPhrase = "ALARM";
                        if (mLogAlarms) {
                            Log.v(TAG, "***ALARM*** - Loggin to SD Card");
                            writeAlarmToSD();
                            logData();
                        } else {
                            Log.v(TAG, "***ALARM***");
                        }
                        // Make alarm beep tone
                        alarmBeep();
                        // Send SMS Alarm.
                        if (mSMSAlarm) {
                            Time tnow = new Time(Time.getCurrentTimezone());
                            tnow.setToNow();
                            // limit SMS alarms to one per minute
                            if ((tnow.toMillis(false) - mSMSTime.toMillis(false)) > 60000) {
                                sendSMSAlarm();
                                mSMSTime = tnow;
                            }
                        }
                    }

                    // Read the data that has been sent, and convert it into
                    // an integer array.
                    byte[] byteArr = data.getBytes(KEY_SPEC_DATA);
                    IntBuffer intBuf = ByteBuffer.wrap(byteArr).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
                    int[] intArray = new int[intBuf.remaining()];
                    intBuf.get(intArray);
                    for (int i = 0; i < intArray.length; i++) {
                        sdData.simpleSpec[i] = intArray[i];
                    }

                }
                if (data.getUnsignedIntegerAsLong(KEY_DATA_TYPE) == DATA_TYPE_SETTINGS) {
                    Log.v(TAG, "DATA_TYPE = Settings");
                    sdData.alarmFreqMin = data.getUnsignedIntegerAsLong(KEY_ALARM_FREQ_MIN);
                    sdData.alarmFreqMax = data.getUnsignedIntegerAsLong(KEY_ALARM_FREQ_MAX);
                    sdData.nMin = data.getUnsignedIntegerAsLong(KEY_NMIN);
                    sdData.nMax = data.getUnsignedIntegerAsLong(KEY_NMAX);
                    sdData.warnTime = data.getUnsignedIntegerAsLong(KEY_WARN_TIME);
                    sdData.alarmTime = data.getUnsignedIntegerAsLong(KEY_ALARM_TIME);
                    sdData.alarmThresh = data.getUnsignedIntegerAsLong(KEY_ALARM_THRESH);
                    sdData.alarmRatioThresh = data.getUnsignedIntegerAsLong(KEY_ALARM_RATIO_THRESH);
                    sdData.batteryPc = data.getUnsignedIntegerAsLong(KEY_BATTERY_PC);
                    sdData.haveSettings = true;
                }
            }
        };
        PebbleKit.registerReceivedDataHandler(this, msgDataHandler);
    }

    /**
     * De-register this server from receiving pebble data
     */
    public void stopPebbleServer() {
        Log.v(TAG, "stopPebbleServer(): Stopping Pebble Server");
        Log.v(TAG, "stopPebbleServer(): msgDataHandler = " + msgDataHandler.toString());
        try {
            unregisterReceiver(msgDataHandler);
        } catch (Exception e) {
            Log.v(TAG, "stopPebbleServer() - error " + e.toString());
        }
    }

    /**
     * Attempt to start the pebble_sd watch app on the pebble watch.
     */
    public void startWatchApp() {
        PebbleKit.startAppOnPebble(getApplicationContext(), SD_UUID);

    }

    /**
     * stop the pebble_sd watch app on the pebble watch.
     */
    public void stopWatchApp() {
        PebbleKit.closeAppOnPebble(getApplicationContext(), SD_UUID);
    }

    /**
     * Start the web server (on port 8080)
     */
    protected void startWebServer() {
        Log.v(TAG, "startWebServer()");
        if (webServer == null) {
            webServer = new WebServer();
            try {
                webServer.start();
            } catch (IOException ioe) {
                Log.w(TAG, "startWebServer(): Error: " + ioe.toString());
            }
            Log.w(TAG, "startWebServer(): Web server initialized.");
        } else {
            Log.v(TAG, "startWebServer(): server already running???");
        }
    }

    /**
     * Stop the web server - FIXME - doesn't seem to do anything!
     */
    protected void stopWebServer() {
        Log.v(TAG, "stopWebServer()");
        if (webServer != null) {
            webServer.stop();
            if (webServer.isAlive()) {
                Log.v(TAG, "stopWebServer() - server still alive???");
            } else {
                Log.v(TAG, "stopWebServer() - server died ok");
            }
            webServer = null;
        }
    }

    /**
     * Log data to SD card if mLogData is set in preferences.
     */
    public void logData() {
        if (mLogData) {
            Log.v(TAG, "logData() - writing data to SD Card");
            writeToSD();
        }
    }

    /** 
     * Checks the status of the connection to the pebble watch,
     * and sets class variables for use by other functions.
     * If the watch app is not running, it attempts to re-start it.
     */
    public void getPebbleStatus() {
        Time tnow = new Time(Time.getCurrentTimezone());
        tnow.setToNow();
        // Check we are actually connected to the pebble.
        sdData.pebbleConnected = PebbleKit.isWatchConnected(this);
        // And is the pebble_sd app running?
        // set mPebbleAppRunningCheck has been false for more than 10 seconds
        // the app is not talking to us
        // mPebbleAppRunningCheck is set to true in the receiveData handler. 
        if (!mPebbleAppRunningCheck && ((tnow.toMillis(false) - mPebbleStatusTime.toMillis(false)) > 10000)) {
            Log.v(TAG, "tdiff = " + (tnow.toMillis(false) - mPebbleStatusTime.toMillis(false)));
            sdData.pebbleAppRunning = false;
            Log.v(TAG, "Pebble App Not Running - Attempting to Re-Start");
            startWatchApp();
            getPebbleSdSettings();
            if (mAudibleFaultWarning) {
                faultWarningBeep();
            }
        } else {
            sdData.pebbleAppRunning = true;
        }

        // if we have confirmation that the app is running, reset the
        // status time to now and initiate another check.
        if (mPebbleAppRunningCheck) {
            mPebbleAppRunningCheck = false;
            mPebbleStatusTime.setToNow();
        }

        if (!sdData.haveSettings) {
            Log.v(TAG, "getPebbleStatus() - no settings received yet - requesting");
            getPebbleSdSettings();
        }
    }

    /**
     * Request Pebble App to send us its latest settings.
     * Will be received as a message by the receiveData handler
     */
    public void getPebbleSdSettings() {
        Log.v(TAG, "getPebbleSdSettings() - requesting settings from pebble");
        PebbleDictionary data = new PebbleDictionary();
        data.addUint8(KEY_SETTINGS, (byte) 1);
        PebbleKit.sendDataToPebble(getApplicationContext(), SD_UUID, data);
    }

    /**
     * updatePrefs() - update basic settings from the SharedPreferences
     * - defined in res/xml/prefs.xml
     */
    public void updatePrefs() {
        Log.v(TAG, "updatePrefs()");
        SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        try {
            mAudibleFaultWarning = SP.getBoolean("AudibleFaultWarning", true);
            Log.v(TAG, "updatePrefs() - mAuidbleFaultWarning = " + mAudibleFaultWarning);
            mAudibleAlarm = SP.getBoolean("AudibleAlarm", true);
            Log.v(TAG, "updatePrefs() - mAuidbleAlarm = " + mAudibleAlarm);
            mAudibleWarning = SP.getBoolean("AudibleWarning", true);
            Log.v(TAG, "updatePrefs() - mAuidbleWarning = " + mAudibleWarning);
            mSMSAlarm = SP.getBoolean("SMSAlarm", false);
            Log.v(TAG, "updatePrefs() - mSMSAlarm = " + mSMSAlarm);
            String SMSNumberStr = SP.getString("SMSNumbers", "");
            mSMSNumbers = SMSNumberStr.split(",");
            mSMSMsgStr = SP.getString("SMSMsg", "Seizure Detected!!!");
            Log.v(TAG, "updatePrefs() - SMSNumberStr = " + SMSNumberStr);
            Log.v(TAG, "updatePrefs() - mSMSNumbers = " + mSMSNumbers);
            mLogAlarms = SP.getBoolean("LogAlarms", true);
            Log.v(TAG, "updatePrefs() - mLogAlarms = " + mLogAlarms);
            mLogData = SP.getBoolean("LogData", false);
            Log.v(TAG, "updatePrefs() - mLogData = " + mLogData);

            // Watch Settings 
            PebbleDictionary setDict = new PebbleDictionary();
            short intVal;
            String prefStr;
            prefStr = SP.getString("AlarmFreqMin", "5");
            intVal = (short) Integer.parseInt(prefStr);
            Log.v(TAG, "updatePrefs() AlarmFreqMin = " + intVal);
            setDict.addInt16(KEY_ALARM_FREQ_MIN, intVal);

            prefStr = SP.getString("AlarmFreqMax", "10");
            intVal = (short) Integer.parseInt(prefStr);
            Log.v(TAG, "updatePrefs() AlarmFreqMax = " + intVal);
            setDict.addUint16(KEY_ALARM_FREQ_MAX, (short) intVal);

            prefStr = SP.getString("WarnTime", "5");
            intVal = (short) Integer.parseInt(prefStr);
            Log.v(TAG, "updatePrefs() WarnTime = " + intVal);
            setDict.addUint16(KEY_WARN_TIME, (short) intVal);

            prefStr = SP.getString("AlarmTime", "10");
            intVal = (short) Integer.parseInt(prefStr);
            Log.v(TAG, "updatePrefs() AlarmTime = " + intVal);
            setDict.addUint16(KEY_ALARM_TIME, (short) intVal);

            prefStr = SP.getString("AlarmThresh", "100");
            intVal = (short) Integer.parseInt(prefStr);
            Log.v(TAG, "updatePrefs() AlarmThresh = " + intVal);
            setDict.addUint16(KEY_ALARM_THRESH, (short) intVal);

            prefStr = SP.getString("AlarmRatioThresh", "30");
            intVal = (short) Integer.parseInt(prefStr);
            Log.v(TAG, "updatePrefs() AlarmRatioThresh = " + intVal);
            setDict.addUint16(KEY_ALARM_RATIO_THRESH, (short) intVal);
            // Send to Pebble
            Log.v(TAG, "updatePrefs() - setDict = " + setDict.toJsonString());
            PebbleKit.sendDataToPebble(getApplicationContext(), SD_UUID, setDict);
        } catch (Exception ex) {
            Log.v(TAG, "updatePrefs() - Problem parsing preferences!");
            Toast toast = Toast.makeText(getApplicationContext(),
                    "Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!",
                    Toast.LENGTH_SHORT);
            toast.show();
        }
    }

    /* Checks if external storage is available for read and write */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    public File getDataStorageDir() {
        // Get the directory for the user's public pictures directory. 
        File file = new File(Environment.getExternalStorageDirectory(), "OpenSeizureDetector");
        if (!file.mkdirs()) {
            Log.e(TAG, "Directory not created");
        }
        return file;
    }

    /**
     * Write data to SD card alarm log
     */
    public void writeAlarmToSD() {
        writeToSD(true);
    }

    /**
     * Write to data log file on SD Card
     */
    public void writeToSD() {
        writeToSD(false);
    }

    /**
     * Write data to SD card - writes to data log file unless alarm=true,
     * in which case writes to alarm log file.
     */
    public void writeToSD(boolean alarm) {
        Log.v(TAG, "writeToSD(" + alarm + ")");
        Time tnow = new Time(Time.getCurrentTimezone());
        tnow.setToNow();
        String dateStr = tnow.format("%Y-%m-%d");

        // Select filename depending on 'alarm' parameter.
        String fname;
        if (alarm)
            fname = "AlarmLog";
        else
            fname = "DataLog";

        fname = fname + "_" + dateStr + ".txt";
        // Open output directory on SD Card.
        if (isExternalStorageWritable()) {
            try {
                FileWriter of = new FileWriter(getDataStorageDir().toString() + "/" + fname, true);
                if (sdData != null) {
                    Log.v(TAG, "writing sdData.toString()");
                    of.append(sdData.toString() + "\n");
                }
                of.close();
            } catch (Exception ex) {
                Log.e(TAG, "writeAlarmToSD - error " + ex.toString());
            }
        } else {
            Log.e(TAG, "ERROR - Can not Write to External Folder");
        }
    }

    /**
     * Class describing the seizure detector web server - appears on port
     * 8080.
     */
    private class WebServer extends NanoHTTPD {
        private String TAG = "WebServer";

        public WebServer() {
            // Set the port to listen on (8080)
            super(8080);
        }

        @Override
        public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parameters,
                Map<String, String> files) {
            Log.v(TAG, "WebServer.serve() - uri=" + uri + " Method=" + method.toString());
            String answer = "Error - you should not see this message! - Something wrong in WebServer.serve()";

            Iterator it = parameters.keySet().iterator();
            while (it.hasNext()) {
                Object key = it.next();
                Object value = parameters.get(key);
                //Log.v(TAG,"Request parameters - key="+key+" value="+value);
            }

            if (uri.equals("/"))
                uri = "/index.html";
            switch (uri) {
            case "/data":
                //Log.v(TAG,"WebServer.serve() - Returning data");
                try {
                    //JSONObject jsonObj = new JSONObject();
                    //jsonObj.put("Time",mPebbleStatusTime.format("%H:%M:%S"));
                    //jsonObj.put("alarmState",sdData.alarmState);
                    //jsonObj.put("alarmPhrase",sdData.alarmPhrase);
                    //jsonObj.put("maxVal",sdData.maxVal);
                    //jsonObj.put("maxFreq",sdData.maxFreq);
                    //jsonObj.put("specPower",sdData.specPower);
                    //jsonObj.put("roiPower",sdData.roiPower);
                    //jsonObj.put("pebCon",mPebbleConnected);
                    //jsonObj.put("pebAppRun",mPebbleAppRunning);
                    answer = sdData.toString();
                } catch (Exception ex) {
                    Log.v(TAG, "Error Creating Data Object - " + ex.toString());
                    answer = "Error Creating Data Object";
                }
                break;

            case "/settings":
                //Log.v(TAG,"WebServer.serve() - Returning settings");
                try {
                    JSONObject jsonObj = new JSONObject();
                    jsonObj.put("alarmFreqMin", sdData.alarmFreqMin);
                    jsonObj.put("alarmFreqMax", sdData.alarmFreqMax);
                    jsonObj.put("nMin", sdData.nMin);
                    jsonObj.put("nMax", sdData.nMax);
                    jsonObj.put("warnTime", sdData.warnTime);
                    jsonObj.put("alarmTime", sdData.alarmTime);
                    jsonObj.put("alarmThresh", sdData.alarmThresh);
                    jsonObj.put("alarmRatioThresh", sdData.alarmRatioThresh);
                    jsonObj.put("batteryPc", sdData.batteryPc);
                    answer = jsonObj.toString();
                } catch (Exception ex) {
                    Log.v(TAG, "Error Creating Data Object - " + ex.toString());
                    answer = "Error Creating Data Object";
                }
                break;

            case "/spectrum":
                Log.v(TAG, "WebServer.serve() - Returning spectrum - 1");
                try {
                    JSONObject jsonObj = new JSONObject();
                    Log.v(TAG, "WebServer.serve() - Returning spectrum - 2");
                    // Initialised it this way because one phone was ok with JSONArray(sdData.simpleSpec), and the other crashed...
                    JSONArray arr = new JSONArray();
                    for (int i = 0; i < sdData.simpleSpec.length; i++) {
                        arr.put(sdData.simpleSpec[i]);
                    }

                    Log.v(TAG, "WebServer.serve() - Returning spectrum - 3");
                    jsonObj.put("simpleSpec", arr);
                    Log.v(TAG, "WebServer.serve() - Returning spectrum - 4");
                    answer = jsonObj.toString();
                    Log.v(TAG, "WebServer.serve() - Returning spectrum - 5" + answer);
                } catch (Exception ex) {
                    Log.v(TAG, "Error Creating Data Object - " + ex.toString());
                    answer = "Error Creating Data Object";
                }
                break;

            default:
                if (uri.startsWith("/index.html") || uri.startsWith("/favicon.ico") || uri.startsWith("/js/")
                        || uri.startsWith("/css/") || uri.startsWith("/img/")) {
                    //Log.v(TAG,"Serving File");
                    return serveFile(uri);
                } else if (uri.startsWith("/logs")) {
                    Log.v(TAG, "WebServer.serve() - serving data logs - uri=" + uri);
                    NanoHTTPD.Response resp = serveLogFile(uri);
                    Log.v(TAG, "WebServer.serve() - response = " + resp.toString());
                    return resp;
                } else {
                    Log.v(TAG, "WebServer.serve() - Unknown uri -" + uri);
                    answer = "Unknown URI: ";
                }
            }

            return new NanoHTTPD.Response(answer);
        }

        /**
         * Return a file from the external storage folder
         */
        NanoHTTPD.Response serveLogFile(String uri) {
            NanoHTTPD.Response res;
            InputStream ip = null;
            String uripart;
            Log.v(TAG, "serveLogFile(" + uri + ")");
            try {
                if (ip != null)
                    ip.close();
                String storageDir = getDataStorageDir().toString();
                StringTokenizer uriParts = new StringTokenizer(uri, "/");
                Log.v(TAG, "serveExternalFile - number of tokens = " + uriParts.countTokens());
                while (uriParts.hasMoreTokens()) {
                    uripart = uriParts.nextToken();
                    Log.v(TAG, "uripart=" + uripart);
                }

                // If we have only given a "/logs" URI, return a list of
                // available files.
                // Re-start the StringTokenizer from the start.
                uriParts = new StringTokenizer(uri, "/");
                Log.v(TAG, "serveExternalFile - number of tokens = " + uriParts.countTokens());
                if (uriParts.countTokens() == 1) {
                    Log.v(TAG, "Returning list of files");

                    File dirs = getDataStorageDir();
                    try {
                        JSONObject jsonObj = new JSONObject();
                        if (dirs.exists()) {
                            String[] fileList = dirs.list();
                            JSONArray arr = new JSONArray();
                            for (int i = 0; i < fileList.length; i++)
                                arr.put(fileList[i]);
                            jsonObj.put("logFileList", arr);
                        }
                        res = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/html", jsonObj.toString());
                    } catch (Exception ex) {
                        res = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/html",
                                "ERROR - " + ex.toString());
                    }
                    return res;
                }

                uripart = uriParts.nextToken(); // This will just be /logs
                uripart = uriParts.nextToken(); // this is the requested file.
                String fname = storageDir + "/" + uripart;
                Log.v(TAG, "serveLogFile - uri=" + uri + ", fname=" + fname);
                ip = new FileInputStream(fname);
                String mimeStr = "text/html";
                res = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, mimeStr, ip);
                res.addHeader("Content-Length", "" + ip.available());
            } catch (IOException ex) {
                Log.v(TAG, "serveLogFile(): Error Opening File - " + ex.toString());
                res = new NanoHTTPD.Response("serveLogFile(): Error Opening file " + uri);
            }
            return (res);
        }

        /**
         * Return a file from the apps /assets folder
         */
        NanoHTTPD.Response serveFile(String uri) {
            NanoHTTPD.Response res;
            InputStream ip = null;
            try {
                if (ip != null)
                    ip.close();
                String assetPath = "www";
                String fname = assetPath + uri;
                //Log.v(TAG,"serveFile - uri="+uri+", fname="+fname);
                AssetManager assetManager = getResources().getAssets();
                ip = assetManager.open(fname);
                String mimeStr = "text/html";
                res = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, mimeStr, ip);
                res.addHeader("Content-Length", "" + ip.available());
            } catch (IOException ex) {
                Log.v(TAG, "serveFile(): Error Opening File - " + ex.toString());
                res = new NanoHTTPD.Response("serveFile(): Error Opening file " + uri);
            }
            return (res);
        }
    }
}