uk.org.openseizuredetector.client.SdClientService.java Source code

Java tutorial

Introduction

Here is the source code for uk.org.openseizuredetector.client.SdClientService.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.client;

import java.util.Map;

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.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Binder;
import android.os.CountDownTimer;
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.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.HttpURLConnection;
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 uk.org.openseizuredetector.SdData;

/**
 * Based on 
 * openseizure detector server SdServer.java
 */
public class SdClientService extends Service {
    // Notification ID
    private int NOTIFICATION_ID = 1;

    private NotificationManager mNM;

    private final static String TAG = "SdClientService";
    private Looper mServiceLooper;
    private Timer statusTimer = null;
    private int mCancelAudiblePeriod = 10; // Cancel Audible Period in minutes
    private int mFaultTimerPeriod = 30; // Cancel Audible Period in sec
    private long mCancelAudibleTimeRemaining = 0;
    private CountDownTimer mCancelAudibleTimer = null;
    private boolean mCancelAudible = false;
    private FaultTimer mFaultTimer = null;
    private boolean mFaultTimerCompleted = false;
    private HandlerThread thread;
    private WakeLock mWakeLock = null;
    public SdData mSdData;
    public boolean mAudibleFaultWarning = true;
    public boolean mAudibleAlarm = true;
    public boolean mAudibleWarning = true;
    public String mServerIP = "192.168.1.175";
    private int mDataUpdatePeriod = 2000;
    private Time mStatusTime = null;
    //private boolean mStatusOK = false;
    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 {
        SdClientService getService() {
            return SdClientService.this;
        }
    }

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

    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "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()");

        // FIXME - do we really need this for the client???
        // 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 regular download of status data from the server.
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(TAG, "SdClientService service starting");

        // Update preferences.
        Log.v(TAG, "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();

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

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

            // Cancel the notification.
            Log.v(TAG, "onDestroy(): cancelling notification");
            mNM.cancel(NOTIFICATION_ID);

            // 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 = "Alarm Client for OpenSeizureDetector Running";
        Notification notification = new Notification(R.drawable.star_of_life_24x24, text,
                System.currentTimeMillis());
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, ClientActivity.class), 0);
        notification.setLatestEventInfo(this, "Alarm Client for OpenSeizureDetector", text, contentIntent);
        notification.flags |= Notification.FLAG_NO_CLEAR;
        mNM = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNM.notify(NOTIFICATION_ID, notification);
    }

    /*
     * Inhibit fault alarm initiation for a period to avoid spurious warning 
     * beeps caused by short term network interruptions.
     */
    private class FaultTimer extends CountDownTimer {
        public long mFaultTimerRemaining = 0;

        public FaultTimer(long startTime, long interval) {
            super(startTime, interval);
        }

        @Override
        public void onFinish() {
            mFaultTimerCompleted = true;
            Log.v(TAG, "mFaultTimer - removing mFaultTimerRunning flag");
        }

        @Override
        public void onTick(long msRemaining) {
            mFaultTimerRemaining = msRemaining / 1000;
            Log.v(TAG, "mFaultTimer - onTick() - Time Remaining = " + mFaultTimerRemaining);
        }

    }

    /*
     * Temporary cancel audible alarms, for the period specified by the
     * CancelAudiblePeriod setting.
     */
    private class CancelAudibleTimer extends CountDownTimer {
        public CancelAudibleTimer(long startTime, long interval) {
            super(startTime, interval);
        }

        @Override
        public void onFinish() {
            mCancelAudible = false;
            Log.v(TAG, "mCancelAudibleTimer - removing cancelAudible flag");
        }

        @Override
        public void onTick(long msRemaining) {
            mCancelAudibleTimeRemaining = msRemaining / 1000;
            Log.v(TAG, "mCancelAudibleTimer - onTick() - Time Remaining = " + mCancelAudibleTimeRemaining);
        }

    }

    /** 
     * Start the fault timer that is used to require a fault to remain
     * standing for a period before raising fault beeps.
     */
    public void startFaultTimer() {
        if (mFaultTimer != null) {
            Log.v(TAG, "startFaultTimer(): fault timer already running - not doing anything.");
        } else {
            Log.v(TAG, "startFaultTimer(): starting fault timer.");
            mFaultTimerCompleted = false;
            mFaultTimer =
                    // conver to ms.
                    new FaultTimer(mFaultTimerPeriod * 1000, 1000);
            mFaultTimer.start();
        }
    }

    public void stopFaultTimer() {
        if (mFaultTimer != null) {
            Log.v(TAG, "stopFaultTimer(): fault timer already running - cancelling it.");
            mFaultTimer.cancel();
            mFaultTimer = null;
            mFaultTimerCompleted = false;
        } else {
            Log.v(TAG, "stopFaultTimer(): fault timer not running - not doing anything.");
        }
    }

    public void cancelAudible() {
        // Start timer to remove the cancel audible flag
        // after the required period.
        if (mCancelAudibleTimer != null) {
            Log.v(TAG, "onCreate(): cancel audible timer already running - cancelling it.");
            mCancelAudibleTimer.cancel();
            mCancelAudibleTimer = null;
            mCancelAudible = false;
        } else {
            Log.v(TAG, "cancelAudible(): starting cancel audible timer");
            mCancelAudible = true;
            mCancelAudibleTimer =
                    // conver to ms.
                    new CancelAudibleTimer(mCancelAudiblePeriod * 60 * 1000, 1000);
            mCancelAudibleTimer.start();
        }
    }

    public boolean isAudibleCancelled() {
        return mCancelAudible;
    }

    public long cancelAudibleTimeRemaining() {
        return mCancelAudibleTimeRemaining;
    }

    /* from http://stackoverflow.com/questions/12154940/how-to-make-a-beep-in-android */
    /**
     * beep for duration miliseconds.
     */
    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 (mFaultTimerCompleted) {
            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...");
                }
            }
        } else {
            startFaultTimer();
            Log.v(TAG, "faultWarningBeep() - starting Fault Timer");
        }
    }

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

    /**
     * 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());
        mAudibleFaultWarning = SP.getBoolean("AudibleFaultWarning", true);
        Log.v(TAG, "updatePrefs() - mAudibleFaultWarning = " + mAudibleFaultWarning);
        mAudibleAlarm = SP.getBoolean("AudibleAlarm", true);
        Log.v(TAG, "updatePrefs() - mAudibleAlarm = " + mAudibleAlarm);
        mAudibleWarning = SP.getBoolean("AudibleWarning", true);
        Log.v(TAG, "updatePrefs() - mAudibleWarning = " + mAudibleWarning);
        mServerIP = SP.getString("ServerIP", "192.168.1.175");
        Log.v(TAG, "updatePrefs() - mServerIP = " + mServerIP);
        try {
            String dataUpdatePeriodStr = SP.getString("DataUpdatePeriod", "2000");
            mDataUpdatePeriod = Integer.parseInt(dataUpdatePeriodStr);
            Log.v(TAG, "updatePrefs() - mDataUpdatePeriod = " + mDataUpdatePeriod);
        } catch (Exception ex) {
            Log.v(TAG, "onStart() - Problem parsing preferences!");
            Toast toast = Toast.makeText(getApplicationContext(),
                    "Problem Parsing Preferences - Something won't work", Toast.LENGTH_SHORT);
            toast.show();
        }

        // Parse the CancelAudible period setting.
        try {
            String cancelAudiblePeriodStr = SP.getString("CancelAudiblePeriod", "10");
            mCancelAudiblePeriod = Integer.parseInt(cancelAudiblePeriodStr);
            Log.v(TAG, "onStart() - mCancelAudiblePeriod = " + mCancelAudiblePeriod);
        } catch (Exception ex) {
            Log.v(TAG, "onStart() - Problem cancelAudiblePeriod preference!");
            Toast toast = Toast.makeText(getApplicationContext(), "Problem Parsing CancelAudiblePeriod Preference",
                    Toast.LENGTH_SHORT);
            toast.show();
        }

        // Parse the faultTimer period setting.
        try {
            String faultTimerPeriodStr = SP.getString("FaultTimerPeriod", "30");
            mFaultTimerPeriod = Integer.parseInt(faultTimerPeriodStr);
            Log.v(TAG, "onStart() - mFaultTimerPeriod = " + mFaultTimerPeriod);
        } catch (Exception ex) {
            Log.v(TAG, "onStart() - Problem with FaultTimerPeriod preference!");
            Toast toast = Toast.makeText(getApplicationContext(), "Problem Parsing FaultTimerPeriod Preference",
                    Toast.LENGTH_SHORT);
            toast.show();
        }

    }

    /**
     * Check the seizure detector data for alarm conditions and beep and
     * display the ClientActivity if an alarm condition is shown.
     */
    private void checkAlarms() {
        Log.v(TAG, "checkAlarms()");
        // Check the alarm state, and raise alarms if necessary.
        if (mSdData.alarmState == 0) {
            Log.v(TAG, "Status=OK");
        }
        if (mSdData.alarmState == 1) {
            Log.v(TAG, "Status=WARNING");
            warningBeep();
        }
        if (mSdData.alarmState == 2) {
            Log.v(TAG, "Status=ALARM");
            alarmBeep();
            Intent clientActivityIntent = new Intent(this, ClientActivity.class);
            clientActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(clientActivityIntent);
        }
        if ((mSdData.pebbleConnected == false) || (mSdData.pebbleAppRunning == false)) {
            faultWarningBeep();
        } else {
            stopFaultTimer();
        }
    }

    /**
     * Retrive the current Seizure Detector Data from the server.
     * Uses teh DownloadSdDataTask class to download the data in the
     * background.  The data is processed in DownloadSdDataTask.onPostExecute().
     */
    private void getSdData() {
        Log.v(TAG, "getSdData()");
        ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        //if (networkInfo != null && networkInfo.isConnected()) {
        if (true) {
            new DownloadSdDataTask().execute("http://" + mServerIP + ":8080/data");
        } else {
            Log.v(TAG, "No network connection available.");
        }
    }

    private class DownloadSdDataTask extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... urls) {
            // params comes from the execute() call: params[0] is the url.
            try {
                return downloadUrl(urls[0]);
            } catch (IOException e) {
                return "Unable to retrieve web page. URL may be invalid.";
            }
        }

        // onPostExecute displays the results of the AsyncTask.
        @Override
        protected void onPostExecute(String result) {
            //Log.v(TAG,"onPostExecute() - result="+result);
            if (result.startsWith("Unable to retrieve web page")) {
                Log.v(TAG, "onPostExecute - Unable to retrieve data");
                mSdData.serverOK = false;
                mSdData.pebbleConnected = false;
                mSdData.pebbleAppRunning = false;
                mSdData.alarmState = 0;
                mSdData.alarmPhrase = "Warning - No Connection to Server";
                //faultWarningBeep();
            } else {
                // Populate mSdData using the received data.
                mSdData.serverOK = true;
                mStatusTime.setToNow();
                mSdData.fromJSON(result);
                Log.v(TAG, "onPostExecute(): mSdData = " + mSdData.toString());
            }
            checkAlarms();
        }
    }

    // Given a URL, establishes an HttpUrlConnection and retrieves
    // the web page content as a InputStream, which it returns as
    // a string.
    private String downloadUrl(String myurl) throws IOException {
        InputStream is = null;
        // Only display the first 500 characters of the retrieved
        // web page content.
        int len = 500;

        try {
            URL url = new URL(myurl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(5000 /* milliseconds */);
            conn.setConnectTimeout(5000 /* milliseconds */);
            conn.setRequestMethod("GET");
            conn.setDoInput(true);
            // Starts the query
            conn.connect();
            int response = conn.getResponseCode();
            Log.d(TAG, "The response is: " + response);
            is = conn.getInputStream();

            // Convert the InputStream into a string
            String contentAsString = readInputStream(is, len);
            return contentAsString;

            // Makes sure that the InputStream is closed after the app is
            // finished using it.
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    // Reads an InputStream and converts it to a String.
    public String readInputStream(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
        Reader reader = null;
        reader = new InputStreamReader(stream, "UTF-8");
        char[] buffer = new char[len];
        reader.read(buffer);
        return new String(buffer);
    }

}