();
- Cursor cursor;
- LogEntryModel rowContactObj;
-
- String[] columns = new String[] { TABLE_ROW_ID, TABLE_ROW_ALARM_STATE,
- TABLE_ROW_DATE, TABLE_ROW_NOTE, TABLE_ROW_DATA_JSON };
-
- try {
-
- cursor = db
- .query(TABLE_NAME, columns, null, null, null, null, null);
- cursor.moveToFirst();
-
- if (!cursor.isAfterLast()) {
- do {
- rowContactObj = new LogEntryModel();
- rowContactObj.setId(cursor.getInt(0));
- prepareSendObject(rowContactObj, cursor);
- allRowsObj.add(rowContactObj);
-
- } while (cursor.moveToNext()); // try to move the cursor's
- // pointer forward one position.
- }
- } catch (SQLException e) {
- Log.e("DB ERROR", e.toString());
- e.printStackTrace();
- }
-
- return allRowsObj;
-
- }
-
- private void prepareSendObject(LogEntryModel rowObj, Cursor cursor) {
- rowObj.setId(cursor.getInt(cursor.getColumnIndexOrThrow(TABLE_ROW_ID)));
- rowObj.setAlarmState(cursor.getInt(cursor
- .getColumnIndexOrThrow(TABLE_ROW_ALARM_STATE)));
- String dateTimeStr = cursor.getString(cursor
- .getColumnIndexOrThrow(TABLE_ROW_DATE));
- Log.v(TAG,"dateTimeStr = "+dateTimeStr);
- Date dateVal;
- try { dateVal = new Date(dateTimeStr); }
- catch (IllegalArgumentException e) { dateVal = null; }
- rowObj.setDate(dateVal);
- rowObj.setNote(cursor.getString(cursor
- .getColumnIndexOrThrow(TABLE_ROW_NOTE)));
- rowObj.setDataJSON(cursor.getString(cursor
- .getColumnIndexOrThrow(TABLE_ROW_DATA_JSON)));
- }
-
- public void deleteRow(int rowID) {
- // ask the database manager to delete the row of given id
- try {
- db.delete(TABLE_NAME, TABLE_ROW_ID + "=" + rowID, null);
- } catch (Exception e) {
- Log.e("DB ERROR", e.toString());
- e.printStackTrace();
- }
- }
-
- public void updateRow(int rowId, LogEntryModel contactObj) {
-
- ContentValues values = prepareData(contactObj);
-
- String whereClause = TABLE_ROW_ID + "=?";
- String whereArgs[] = new String[] { String.valueOf(rowId) };
-
- db.update(TABLE_NAME, values, whereClause, whereArgs);
-
- }
-
-
-}
diff --git a/app/src/main/java/uk/org/openseizuredetector/EventLogManager/LogEntryModel.java b/app/src/main/java/uk/org/openseizuredetector/EventLogManager/LogEntryModel.java
deleted file mode 100644
index 9502239..0000000
--- a/app/src/main/java/uk/org/openseizuredetector/EventLogManager/LogEntryModel.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package uk.org.openseizuredetector.EventLogManager;
-
-import java.util.Date;
-
-/**
- * Our LogEntryModel class which will have fields like id, name, contact number
- * and email and corresponding getter and setter methods.
- * **/
-public class LogEntryModel {
-
- private int id;
- private Date date;
- private int alarmState;
- private String dataJSON;
- private String note;
-
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public int getAlarmState() {
- return alarmState;
- }
-
- public void setAlarmState(int alarmState) {
- this.alarmState = alarmState;
- }
-
- public String getNote() {
- return note;
- }
-
- public void setNote(String note) {
- this.note = note;
- }
-
- public Date getDate() {
- return date;
- }
-
- public void setDate(Date date) {
- this.date = date;
- }
-
- public String getDataJSON() { return dataJSON; }
-
- public void setDataJSON(String dataJSON) { this.dataJSON = dataJSON; }
-}
diff --git a/app/src/main/java/uk/org/openseizuredetector/DBQueryActivity.java b/app/src/main/java/uk/org/openseizuredetector/ExportDataActivity.java
similarity index 95%
rename from app/src/main/java/uk/org/openseizuredetector/DBQueryActivity.java
rename to app/src/main/java/uk/org/openseizuredetector/ExportDataActivity.java
index d653e6b..d8b82fe 100644
--- a/app/src/main/java/uk/org/openseizuredetector/DBQueryActivity.java
+++ b/app/src/main/java/uk/org/openseizuredetector/ExportDataActivity.java
@@ -2,9 +2,8 @@ package uk.org.openseizuredetector;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
-import android.content.Context;
import android.os.Handler;
-import android.support.v7.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@@ -15,9 +14,9 @@ import android.widget.TimePicker;
import java.util.Calendar;
-public class DBQueryActivity extends AppCompatActivity
+public class ExportDataActivity extends AppCompatActivity
implements View.OnClickListener {
- String TAG = "DBQueryActivity";
+ String TAG = "ExportDataActivity";
Button mDateBtn;
Button mTimeBtn;
Button mExportBtn;
diff --git a/app/src/main/java/uk/org/openseizuredetector/LogManager.java b/app/src/main/java/uk/org/openseizuredetector/LogManager.java
index 38c1d20..8d5c72f 100644
--- a/app/src/main/java/uk/org/openseizuredetector/LogManager.java
+++ b/app/src/main/java/uk/org/openseizuredetector/LogManager.java
@@ -2,7 +2,7 @@
Android_SD - Android host for Garmin or Pebble watch based seizure detectors.
See http://openseizuredetector.org for more information.
- Copyright Graham Jones, 2019.
+ Copyright Graham Jones, 2019, 2021.
This file is part of Android_SD.
@@ -22,6 +22,7 @@
*/
package uk.org.openseizuredetector;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -31,78 +32,245 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import android.os.CountDownTimer;
import android.os.Handler;
-import android.text.format.Time;
import android.util.Log;
-import org.apache.commons.codec.binary.Base64;
+import org.json.JSONArray;
+import org.json.JSONException;
import org.json.JSONObject;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
-import static android.database.sqlite.SQLiteDatabase.openOrCreateDatabase;
+//import static android.database.sqlite.SQLiteDatabase.openOrCreateDatabase;
/**
* LogManager is a class to handle all aspects of Data Logging within OpenSeizureDetector.
+ * It performs several functions:
+ * - It will store seizure detector data to a local database on demand (it is called by the SdServer background service)
+ * - It will store system log data to the local database on demand (called by any part of OSD via the osdUtil functions)
+ * - It will periodically attempt to upload the oldest logged data to the osdApi remote database - the interface to the
+ * remote database is handled by the WebApiConnection class. It only tries to do one transaction with the external database
+ * at a time - if the periodic timer times out and an upload is in progress it will not do anything and wait for the next timeout.*
+ *
+ * The data upload process is as follows:
+ * - Select the oldest non-uploaded datapoint that is marked as an alarm or warning state.
+ * - Create an Event in the remote database based on that datapoint date and alarm type, and note the Event ID.
+ * - Query the local database to return all datapoints within +/- EventDuration/2 minutes of the event.
+ * - Upload the datapoints, linking them to the new eventID.
+ * - Mark all the uploaded datapoints as uploaded.
*/
public class LogManager {
- private String TAG = "LogManager";
- private String mDbName = "osdData";
- private String mDbTableName = "datapoints";
+ static final private String TAG = "LogManager";
+ //private String mDbName = "osdData";
+ final static private String mDpTableName = "datapoints";
+ final static private String mEventsTableName = "events";
private boolean mLogRemote;
private boolean mLogRemoteMobile;
- private String mOSDUrl = "https://https://osd.dynu.net/";
- private String mApiToken;
- private OsdDbHelper mOSDDb;
+ private String mAuthToken;
+ static private SQLiteDatabase mOsdDb = null; // SQLite Database for data and log entries.
private RemoteLogTimer mRemoteLogTimer;
- private Context mContext;
+ private static Context mContext;
private OsdUtil mUtil;
+ public static WebApiConnection mWac;
+ public static final boolean USE_FIREBASE_BACKEND = false;
+ private boolean mUploadInProgress;
+ private long mEventDuration = 120; // event duration in seconds - uploads datapoints that cover this time range centred on the event time.
+ public long mDataRetentionPeriod = 1; // Prunes the local db so it only retains data younger than this duration (in days)
+ private long mRemoteLogPeriod = 60; // Period in seconds between uploads to the remote server.
+ private ArrayList mDatapointsToUploadList;
+ private String mCurrentEventRemoteId;
+ private long mCurrentEventLocalId = -1;
+ private int mCurrentDatapointId;
+ private long mAutoPrunePeriod = 3600; // Prune the database every hour
+ private boolean mAutoPruneDb;
+ private AutoPruneTimer mAutoPruneTimer;
- public LogManager(Context context) {
- mLogRemote = false;
- mLogRemoteMobile = false;
- mOSDUrl = null;
- mContext = context;
-
- Handler handler = new Handler();
- mUtil = new OsdUtil(mContext, handler);
-
- startRemoteLogTimer();
+ public interface CursorCallback {
+ void accept(Cursor retVal);
}
- private boolean openDb() {
- try {
- mOSDDb = new OsdDbHelper(mDbTableName, mContext);
- if (!checkTableExists(mOSDDb, mDbTableName)) {
- Log.e(TAG,"ERROR - Table does not exist");
- return false;
+ public interface ArrayListCallback {
+ void accept(ArrayList> retVal);
+ }
+
+ public LogManager(Context context,
+ boolean logRemote, boolean logRemoteMobile, String authToken,
+ long eventDuration, long remoteLogPeriod,
+ boolean autoPruneDb, long dataRetentionPeriod) {
+ Log.d(TAG, "LogManger Constructor");
+ mContext = context;
+ Handler handler = new Handler();
+
+ mLogRemote = logRemote;
+ mLogRemoteMobile = logRemoteMobile;
+ mAuthToken = authToken;
+ mEventDuration = eventDuration;
+ mAutoPruneDb = autoPruneDb;
+ mDataRetentionPeriod = dataRetentionPeriod;
+ mRemoteLogPeriod = remoteLogPeriod;
+ Log.v(TAG, "mLogRemote=" + mLogRemote);
+ Log.v(TAG, "mLogRemoteMobile=" + mLogRemoteMobile);
+ Log.v(TAG, "mEventDuration=" + mEventDuration);
+ Log.v(TAG, "mAutoPruneDb=" + mAutoPruneDb);
+ Log.v(TAG, "mDataRetentionPeriod=" + mDataRetentionPeriod);
+ Log.v(TAG, "mRemoteLogPeriod=" + mRemoteLogPeriod);
+
+ mUtil = new OsdUtil(mContext, handler);
+ openDb();
+ Log.i(TAG, "Starting Remote Database Interface");
+ if (USE_FIREBASE_BACKEND) {
+ mWac = new WebApiConnection_firebase(mContext);
+ } else {
+ mWac = new WebApiConnection_osdapi(mContext);
+ }
+
+ mWac.setStoredToken(mAuthToken);
+
+ if (mLogRemote) {
+ Log.i(TAG, "Starting Remote Log Timer");
+ startRemoteLogTimer();
+ } else {
+ Log.i(TAG, "mLogRemote is false - not starting remote log timer");
+ }
+
+ if (mAutoPruneDb) {
+ Log.i(TAG, "Starting Auto Prune Timer");
+ startAutoPruneTimer();
+ } else {
+ Log.i(TAG, "AutoPruneDB is not set - not starting Auto Prune Timer");
+ }
+
+ }
+
+ /**
+ * Returns a JSON String representing an array of datapoints that are selected from sqlite cursor c.
+ *
+ * @param c sqlite cursor pointing to datapoints query result.
+ * @return JSON String.
+ * from https://stackoverflow.com/a/20488153/2104584
+ */
+ private String cursor2Json(Cursor c) {
+ StringBuilder cNames = new StringBuilder();
+ for (String n : c.getColumnNames()) {
+ cNames.append(", ").append(n);
+ }
+ //Log.v(TAG,"cursor2Json() - c="+c.toString()+", columns="+cNames+", number of rows="+c.getCount());
+ c.moveToFirst();
+ //JSONObject Root = new JSONObject();
+ JSONArray dataPointArray = new JSONArray();
+ int i = 0;
+ while (!c.isAfterLast()) {
+ JSONObject datapoint = new JSONObject();
+ try {
+ datapoint.put("id", c.getString(c.getColumnIndex("id")));
+ datapoint.put("dataTime", c.getString(c.getColumnIndex("dataTime")));
+ datapoint.put("status", c.getString(c.getColumnIndex("status")));
+ datapoint.put("dataJSON", c.getString(c.getColumnIndex("dataJSON")));
+ datapoint.put("uploaded", c.getString(c.getColumnIndex("uploaded")));
+ //Log.v(TAG,"cursor2json() - datapoint="+datapoint.toString());
+ c.moveToNext();
+ dataPointArray.put(i, datapoint);
+ i++;
+ } catch (JSONException e) {
+ Log.e(TAG, "cursor2Json(): error creating JSON Object");
+ e.printStackTrace();
+ }
+ }
+ return dataPointArray.toString();
+ }
+
+ /**
+ * Returns a JSON String representing an array of events that are selected from sqlite cursor c.
+ *
+ * @param c sqlite cursor pointing to events query result.
+ * @return JSON String.
+ * from https://stackoverflow.com/a/20488153/2104584
+ */
+ private String eventCursor2Json(Cursor c) {
+ StringBuilder cNames = new StringBuilder();
+ for (String n : c.getColumnNames()) {
+ cNames.append(", ").append(n);
+ }
+ c.moveToFirst();
+ Log.v(TAG, "eventCursor2Json: size of cursor=" + c.getCount());
+ JSONArray eventsArray = new JSONArray();
+ int i = 0;
+ while (!c.isAfterLast()) {
+ JSONObject event = new JSONObject();
+ try {
+ String val;
+ val = c.getString(c.getColumnIndex("id"));
+ // We replace null values with empty string, otherwise they are completely excluded from output JSON.
+ event.put("id", val==null ? "" : val );
+ val = c.getString(c.getColumnIndex("dataTime"));
+ event.put("dataTime", val==null ? "" : val);
+ val = c.getString(c.getColumnIndex("status"));
+ event.put("status", val==null ? "" : val);
+ val = c.getString(c.getColumnIndex("type"));
+ event.put("type", val==null ? "" : val);
+ val = c.getString(c.getColumnIndex("subType"));
+ event.put("subType", val==null ? "" : val);
+ val = c.getString(c.getColumnIndex("notes"));
+ event.put("desc", val==null ? "" : val);
+ val = c.getString(c.getColumnIndex("dataJSON"));
+ event.put("dataJSON", val==null ? "" : val);
+ val = c.getString(c.getColumnIndex("uploaded"));
+ event.put("uploaded", val==null ? "" : val);
+ c.moveToNext();
+ eventsArray.put(i, event);
+ i++;
+ } catch (JSONException e) {
+ Log.e(TAG, "eventCursor2Json(): error creating JSON Object");
+ e.printStackTrace();
+ }
+ }
+ Log.v(TAG, "eventCursor2JSON(): returning " + eventsArray.toString());
+ return eventsArray.toString();
+ }
+
+
+ private static boolean openDb() {
+ Log.d(TAG, "openDb");
+ try {
+ if (mOsdDb == null) {
+ Log.i(TAG, "openDb: mOsdDb is null - initialising");
+ mOsdDb = new OsdDbHelper(mContext).getWritableDatabase();
+ } else {
+ Log.i(TAG, "openDb: mOsdDb has been initialised already so not doing anything");
+ }
+ String[] tableNames = new String[]{mDpTableName, mEventsTableName};
+ for (String tableName : tableNames) {
+ if (!checkTableExists(mOsdDb, tableName)) {
+ Log.e(TAG, "ERROR - Table " + tableName + " does not exist");
+ return false;
+ } else {
+ Log.d(TAG, "table " + tableName + " exists ok");
+ }
}
- return true;
} catch (SQLException e) {
Log.e(TAG, "Failed to open Database: " + e.toString());
- mOSDDb = null;
return false;
}
+ return true;
}
- private boolean checkTableExists(OsdDbHelper osdDb, String osdTableName) {
+ private static boolean checkTableExists(SQLiteDatabase osdDb, String osdTableName) {
Cursor c = null;
boolean tableExists = false;
+ Log.d(TAG, "checkTableExists()");
try {
- c = osdDb.getWritableDatabase().query(osdTableName, null,
+ c = osdDb.query(osdTableName, null,
null, null, null, null, null);
tableExists = true;
- }
- catch (Exception e) {
- Log.d(TAG, osdTableName+" doesn't exist :(((");
+ c.close();
+ } catch (Exception e) {
+ Log.d(TAG, osdTableName + " doesn't exist :(((");
}
return tableExists;
}
@@ -112,261 +280,845 @@ public class LogManager {
* Write data to local database
* FIXME - I am sure we should not be using raw SQL Srings to do this!
*/
- public void writeToLocalDb(SdData sdData) {
- Log.v(TAG, "writeToLocalDb()");
- Time tnow = new Time(Time.getCurrentTimezone());
- tnow.setToNow();
- String dateStr = tnow.format("%Y-%m-%d");
+ public void writeDatapointToLocalDb(SdData sdData) {
+ //Log.v(TAG, "writeDatapointToLocalDb()");
+ Date curDate = new Date();
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ String dateStr = dateFormat.format(curDate);
String SQLStr = "SQLStr";
- try {
- SQLStr = "INSERT INTO "+ mDbTableName
- + "(dataTime, wearer_id, BattPC, specPow, roiRatio, avAcc, sdAcc, hr, status, dataJSON, uploaded)"
- + " VALUES("
- +"CURRENT_TIMESTAMP,"
- + -1 + ","
- + sdData.batteryPc + ","
- + sdData.specPower + ","
- + 10. * sdData.roiPower / sdData.specPower + ","
- + sdData.getAvAcc() + ","
- + sdData.getSdAcc() + ","
- + sdData.mHR + ","
- + sdData.alarmState + ","
- + DatabaseUtils.sqlEscapeString(sdData.toCSVString(true)) + ","
- + 0
- +")";
- mOSDDb.getWritableDatabase().execSQL(SQLStr);
-
- } catch (SQLException e) {
- Log.e(TAG,"writeToLocalDb(): Error Writing Data: " + e.toString());
- Log.e(TAG,"SQLStr was "+SQLStr);
+ if (mOsdDb == null) {
+ Log.e(TAG, "writeDatapointToLocalDb(): mOsdDb is null - doing nothing");
+ return;
}
+ try {
+ // Write Datapoint to database
+ SQLStr = "INSERT INTO " + mDpTableName
+ + "(dataTime, status, dataJSON, uploaded)"
+ + " VALUES("
+ + "'" + dateStr + "',"
+ + sdData.alarmState + ","
+ + DatabaseUtils.sqlEscapeString(sdData.toDatapointJSON()) + ","
+ + 0
+ + ")";
+ mOsdDb.execSQL(SQLStr);
+ Log.v(TAG, "writeDatapointToLocalDb(): datapoint written to database");
+ if (sdData.alarmState != 0) {
+ Log.i(TAG, "writeDatapointToLocalDb(): adding event to local DB");
+ createLocalEvent(dateStr,sdData.alarmState,null, null, null, sdData.toSettingsJSON());
+ }
+ } catch (SQLException e) {
+ Log.e(TAG, "writeToLocalDb(): Error Writing Data: " + e.toString());
+ Log.e(TAG, "SQLStr was " + SQLStr);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "writeToLocalDb(): Null Pointer Exception: " + e.toString());
+ }
}
+ public boolean createLocalEvent(String dataTime, long status) {
+ return (createLocalEvent(dataTime, status, null, null, null, null));
+ }
+
+ public boolean createLocalEvent(String dataTime, long status, String type, String subType, String desc, String dataJSON) {
+ // Expects dataTime to be in format: SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ Log.d(TAG, "createLocalEvent() - dataTime=" + dataTime + ", status=" + status + ", dataJSON="+dataJSON);
+ // Write Event to database
+ //String SQLStr = "INSERT INTO " + mEventsTableName
+ // + "(dataTime, status, type, subtype, notes, dataJSON)"
+ // + " VALUES("
+ // + "'" + dataTime + "',"
+ // + status + ","
+ // + "'" + type + "',"
+ // + "'" + subType + "',"
+ // + "'" + desc + "',"
+ // + "'" + dataJSON + "'"
+ // + ")";
+ //mOsdDb.execSQL(SQLStr);
+ ContentValues values = new ContentValues();
+ values.put("dataTime", dataTime);
+ values.put("status", status);
+ values.put("type", type);
+ values.put("subType",subType);
+ values.put("notes",desc);
+ values.put("dataJSON", dataJSON);
+
+ long newRowId = mOsdDb.insert(mEventsTableName, null, values);
+ Log.d(TAG, "Created Row ID"+newRowId);
+ return true;
+ }
+
+ /**
+ * Returns a json representation of locally stored event 'id'.
+ *
+ * @param id event id to return
+ * @return JSON representation of requested event (single element JSON array)
+ */
+ public String getLocalEventById(long id) {
+ Log.d(TAG, "getLocalEventById() - id=" + id);
+ Cursor c;
+ String retVal;
+ try {
+ String selectStr = "select * from " + mEventsTableName + " where id=" + id + ";";
+ c = mOsdDb.rawQuery(selectStr, null);
+ retVal = eventCursor2Json(c);
+ } catch (Exception e) {
+ Log.d(TAG, "getLocalEventById(): Error Querying Database: " + e.getLocalizedMessage());
+ retVal = null;
+ }
+ Log.d(TAG, "getLocalEventById() - returning " + retVal);
+ return (retVal);
+ }
+
+
+ /**
+ * Returns a json representation of datapoint 'id'.
+ *
+ * @param id datapoint id to return
+ * @return JSON representation of requested datapoint (single element JSON array)
+ */
+ public String getDatapointById(long id) {
+ Log.d(TAG, "getDatapointById() - id=" + id);
+ Cursor c;
+ String retVal;
+ try {
+ String selectStr = "select * from " + mDpTableName + " where id=" + id + ";";
+ c = mOsdDb.rawQuery(selectStr, null);
+ retVal = cursor2Json(c);
+ } catch (Exception e) {
+ Log.d(TAG, "getDatapointById(): Error Querying Database: " + e.getLocalizedMessage());
+ retVal = null;
+ }
+ return (retVal);
+ }
+
+ /**
+ * setDatapointToUploaded
+ *
+ * @param id - datapoint ID to change
+ * @param eventId - the eventId associated with the uploaded datapoint - the 'uploaded' field is set to this value.
+ * @return True on success or False on failure.
+ */
+ public boolean setDatapointToUploaded(int id, String eventId) {
+ Log.d(TAG, "setDatapointToUploaded() - id=" + id);
+ if (mOsdDb == null) {
+ Log.e(TAG, "setDatapointToUploaded() - mOsdDb is null - not doing anything");
+ return false;
+ }
+ ContentValues cv = new ContentValues();
+ cv.put("uploaded", eventId);
+ int nRowsUpdated = mOsdDb.update(mDpTableName, cv, "id = ?",
+ new String[]{String.format("%d", id)});
+ return (nRowsUpdated == 1);
+ }
+
+ /**
+ * setDatapointStatus() - Update the status of data point id.
+ *
+ * @param id datapont id
+ * @param statusVal the status to set for the datapoint.
+ * @return true on success or false on failure
+ */
+ public boolean setDatapointStatus(Long id, int statusVal) {
+ Log.d(TAG, "setDatapointStatus() - id=" + id + ", statusVal=" + statusVal);
+ //Cursor c = null;
+ ContentValues cv = new ContentValues();
+ cv.put("status", statusVal);
+ int nRowsUpdated = mOsdDb.update(mDpTableName, cv, "id = ?",
+ new String[]{String.format("%d", id)});
+
+ return (nRowsUpdated == 1);
+ }
+
+
+ /**
+ * Return a JSON string representing all the datapoints between startDate and endDate
+ *
+ * @return True on successful start or false if call fails.
+ */
+ public boolean getDatapointsByDate(String startDateStr, String endDateStr, WebApiConnection.StringCallback callback) {
+ Log.d(TAG, "getDatapointsbyDate() - startDateStr=" + startDateStr + ", endDateStr=" + endDateStr);
+ String[] columns = {"*"};
+ String whereClause = "DataTime>? AND DataTime";
+ String[] whereArgs = {startDateStr, endDateStr};
+ new SelectQueryTask(mDpTableName, columns, whereClause, whereArgs,
+ null, null, "dataTime DESC", (Cursor cursor) -> {
+ Log.v(TAG, "getDataPointsByDate - returned " + cursor);
+ if (cursor != null) {
+ callback.accept(cursor2Json(cursor));
+ } else {
+ callback.accept(null);
+ }
+ }).execute();
+ return (true);
+ }
+
+
+ /**
+ * Return an array list of objects representing the events in the database by calling the specified callback function.
+ *
+ * @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions.
+ * @return True on successful start or false if call fails.
+ */
+ public boolean getEventsList(boolean includeWarnings, ArrayListCallback callback) {
+ Log.v(TAG, "getEventsList - includeWarnings=" + includeWarnings);
+ ArrayList> eventsList = new ArrayList<>();
+
+ String[] whereArgs = getEventWhereArgs(includeWarnings);
+ String whereClause = getEventWhereClause(includeWarnings);
+ //sqlStr = "SELECT * from " + mDbTableName + " where Status in (" + statusListStr + ") order by dataTime desc;";
+ String[] columns = {"*"};
+ new SelectQueryTask(mEventsTableName, columns, whereClause, whereArgs,
+ null, null, "dataTime DESC", (Cursor cursor) -> {
+ Log.v(TAG, "getEventsList - returned " + cursor);
+ if (cursor != null) {
+ Log.v(TAG, "getEventsList - returned " + cursor.getCount() + " records");
+ while (!cursor.isAfterLast()) {
+ HashMap event = new HashMap<>();
+ //event.put("id", cursor.getString(cursor.getColumnIndex("id")));
+ event.put("dataTime", cursor.getString(cursor.getColumnIndex("dataTime")));
+ int status = cursor.getInt(cursor.getColumnIndex("status"));
+ String statusStr = mUtil.alarmStatusToString(status);
+ event.put("status", statusStr);
+ event.put("uploaded", cursor.getString(cursor.getColumnIndex("uploaded")));
+ //event.put("dataJSON", cursor.getString(cursor.getColumnIndex("dataJSON")));
+ eventsList.add(event);
+ cursor.moveToNext();
+ }
+ }
+ callback.accept(eventsList);
+ }).execute();
+ return (true);
+ }
+
+
+ /**
+ * pruneLocalDb() removes data that is older than mLocalDbMaxAgeDays days
+ */
+ public int pruneLocalDb() {
+ Log.d(TAG, "pruneLocalDb()");
+ int retVal = 0;
+ long currentDateMillis = new Date().getTime();
+ long endDateMillis = currentDateMillis - 24 * 3600 * 1000 * mDataRetentionPeriod;
+ //long endDateMillis = currentDateMillis - 3600*1000* mDataRetentionPeriod; // Using hours rather than days for testing
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String endDateStr = dateFormat.format(new Date(endDateMillis));
+ String[] tableNames = new String[]{mDpTableName, mEventsTableName};
+ for (String tableName : tableNames) {
+ Log.i(TAG, "pruneLocalDb - pruning table " + tableName);
+ try {
+ String selectStr = "DataTime<=?";
+ String[] selectArgs = {endDateStr};
+ retVal = mOsdDb.delete(tableName, selectStr, selectArgs);
+ } catch (Exception e) {
+ Log.d(TAG, "Error deleting data " + e.toString());
+ retVal = 0;
+ }
+ Log.d(TAG, String.format("pruneLocalDb() - deleted %d records from table %s", retVal, tableName));
+ }
+ return (retVal);
+ }
+
+ /**
+ * setEventToUploaded
+ *
+ * @param localEventId - local Event ID to change
+ * @param remoteEventId - the remote eventId associated with the uploaded datapoint - the 'uploaded' field is set to this value.
+ * @return True on success or False on failure.
+ */
+ public boolean setEventToUploaded(long localEventId, String remoteEventId) {
+ Log.d(TAG, "setEventToUploaded() - local id=" + localEventId + " remote id=" + remoteEventId);
+ if (mOsdDb == null) {
+ Log.e(TAG, "setEventToUploaded() - mOsdDb is null - not doing anything");
+ return false;
+ }
+ ContentValues cv = new ContentValues();
+ cv.put("uploaded", remoteEventId);
+ int nRowsUpdated = mOsdDb.update(mEventsTableName, cv, "id = ?",
+ new String[]{String.format("%d", localEventId)});
+ return (nRowsUpdated == 1);
+ }
+
+
+ /**
+ * Return the ID of the next event (alarm, warning, fall etc that needs to be uploaded (alarm or warning condition and has not yet been uploaded.
+ *
+ * @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions.
+ * @return True on successful start or false if call fails.
+ */
+ public boolean getNextEventToUpload(boolean includeWarnings, WebApiConnection.LongCallback callback) {
+ Log.v(TAG, "getNextEventToUpload - includeWarnings=" + includeWarnings);
+
+ String[] whereArgsStatus = getEventWhereArgs(includeWarnings);
+ String whereClauseStatus = getEventWhereClause(includeWarnings);
+ String[] columns = {"*"};
+
+ // Do not try to upload very recent events so that we have chance to record the post-event data before uploading it.
+ long currentDateMillis = new Date().getTime();
+ long endDateMillis = currentDateMillis - 1000 * mEventDuration;
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String endDateStr = dateFormat.format(new Date(endDateMillis));
+ String whereClauseUploaded = "uploaded is null";
+ String whereClauseDate = "DataTime";
+ String whereClause = whereClauseStatus + " AND " + whereClauseUploaded + " AND " + whereClauseDate;
+
+ String[] whereArgs = new String[whereArgsStatus.length + 1];
+ for (int i = 0; i < whereArgsStatus.length; i++) {
+ whereArgs[i] = whereArgsStatus[i];
+ }
+ whereArgs[whereArgsStatus.length] = endDateStr;
+ new SelectQueryTask(mEventsTableName, columns, whereClause, whereArgs,
+ null, null, "dataTime DESC", (Cursor cursor) -> {
+ Long recordId = new Long(-1);
+ if (cursor != null) {
+ Log.v(TAG, "getNextEventToUpload - returned " + cursor.getCount() + " records");
+ cursor.moveToFirst();
+ if (cursor.getCount() == 0) {
+ Log.v(TAG, "getNextEventToUpload() - no events to Upload - exiting");
+ recordId = new Long(-1);
+ } else {
+ recordId = cursor.getLong(0);
+ Log.d(TAG, "getNextEventToUpload(): id=" + recordId);
+ }
+ }
+ callback.accept(recordId);
+ }).execute();
+ return (true);
+ }
+
+
+ /**
+ * Return the ID of the datapoint that is closest to date/time string dateStr
+ * Based on https://stackoverflow.com/questions/45749046/sql-get-nearest-date-record
+ *
+ * @return True on successful start or false if call fails.
+ */
+ public boolean getNearestDatapointToDate(String dateStr, WebApiConnection.LongCallback callback) {
+ Log.v(TAG, "getNextEventToDate - dateStr=" + dateStr);
+ String[] columns = {"*", "(julianday(dataTime)-julianday(datetime('" + dateStr + "'))) as ddiff"};
+ //SQLStr = "SELECT *, (julianday(dataTime)-julianday(datetime('" + dateStr + "'))) as ddiff from " + mDbTableName + " order by ABS(ddiff) asc;";
+ String orderByStr = "ABS(ddiff) asc";
+ new SelectQueryTask(mDpTableName, columns, null, null,
+ null, null, orderByStr, (Cursor cursor) -> {
+ Log.v(TAG, "getEventsNearestDatapointToDate - returned " + cursor);
+ Long recordId = new Long(-1);
+ if (cursor != null) {
+ Log.v(TAG, "getNearestDatapointToDate - returned " + cursor.getCount() + " records");
+ cursor.moveToFirst();
+ if (cursor.getCount() == 0) {
+ Log.v(TAG, "getNearestDatapointToDate() - no events to Upload - exiting");
+ recordId = new Long(-1);
+ } else {
+ String recordStr = cursor.getString(3);
+ recordId = cursor.getLong(0);
+ Log.d(TAG, "getNearestDatapointToDate(): id=" + recordId + ", recordStr=" + recordStr);
+ }
+ }
+ callback.accept(recordId);
+ }).execute();
+ return (true);
+ }
+
+
+ /**
+ * Return the number of events stored in the local database (via a callback).
+ *
+ * @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions.
+ * @return True on successful start or false if call fails.
+ */
+ public boolean getLocalEventsCount(boolean includeWarnings, WebApiConnection.LongCallback callback) {
+ //Log.v(TAG, "getLocalEventsCount- includeWarnings=" + includeWarnings);
+ String[] whereArgs = getEventWhereArgs(includeWarnings);
+ String whereClause = getEventWhereClause(includeWarnings);
+ String[] columns = {"*"};
+ new SelectQueryTask(mEventsTableName, columns, whereClause, whereArgs,
+ null, null, null, (Cursor cursor) -> {
+ //Log.v(TAG, "getLocalEventsCount - returned " + cursor);
+ Long eventCount = Long.valueOf(0);
+ if (cursor != null) {
+ eventCount = Long.valueOf(cursor.getCount());
+ Log.v(TAG, "getLocalEventsCount - returned " + eventCount + " records");
+ }
+ callback.accept(eventCount);
+ }).execute();
+ return (true);
+ }
+
+ /**
+ * Return the number of datapoints stored in the local database (via a callback).
+ *
+ * @return True on successful start or false if call fails.
+ */
+ public boolean getLocalDatapointsCount(WebApiConnection.LongCallback callback) {
+ //Log.v(TAG, "getLocalDatapointsCount");
+ String[] whereArgs = null;
+ String whereClause = null;
+ String[] columns = {"*"};
+ new SelectQueryTask(mDpTableName, columns, whereClause, whereArgs,
+ null, null, null, (Cursor cursor) -> {
+ //Log.v(TAG, "getLocalDatapointsCount - returned " + cursor);
+ Long eventCount = Long.valueOf(0);
+ if (cursor != null) {
+ eventCount = Long.valueOf(cursor.getCount());
+ Log.v(TAG, "getLocalDatapointsCount - returned " + eventCount + " records");
+ }
+ callback.accept(eventCount);
+ }).execute();
+ return (true);
+ }
+
+
+ /**
+ * Executes the sqlite query (=SELECT statement)
+ * Use as new SelectQueryTask(xxx,xxx,xx,xxxx).execute()
+ */
+ static private class SelectQueryTask extends AsyncTask {
+ // Based on https://stackoverflow.com/a/21120199/2104584
+ String mTable;
+ String[] mColumns;
+ String mSelection;
+ String[] mSelectionArgs;
+ String mGroupBy;
+ String mHaving;
+ String mOrderBy;
+ CursorCallback mCallback;
+
+ //query(String table, String[] columns, String selection, String[] selectionArgs,
+ // String groupBy, String having, String orderBy)
+ SelectQueryTask(String table, String[] columns, String selection, String[] selectionArgs,
+ String groupBy, String having, String orderBy, CursorCallback callback) {
+ // list all the parameters like in normal class define
+ this.mTable = table;
+ this.mColumns = columns;
+ this.mSelection = selection;
+ this.mSelectionArgs = selectionArgs;
+ this.mGroupBy = groupBy;
+ this.mHaving = having;
+ this.mOrderBy = orderBy;
+ this.mCallback = callback;
+
+ }
+
+ @Override
+ protected Cursor doInBackground(Void... params) {
+ //Log.v(TAG, "runSelect.doInBackground()");
+ Log.v(TAG, "SelectQueryTask.doInBackground: mTable=" + mTable + ", mColumns=" + Arrays.toString(mColumns)
+ + ", mSelection=" + mSelection + ", mSelectionArgs=" + Arrays.toString(mSelectionArgs) + ", mGroupBy=" + mGroupBy
+ + ", mHaving =" + mHaving + ", mOrderBy=" + mOrderBy);
+
+ try {
+ Cursor resultSet = mOsdDb.query(mTable, mColumns, mSelection,
+ mSelectionArgs, mGroupBy, mHaving, mOrderBy);
+ resultSet.moveToFirst();
+ return (resultSet);
+ } catch (SQLException e) {
+ Log.e(TAG, "SelectQueryTask.doInBackground(): Error selecting Data: " + e.toString());
+ return (null);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "SelectQueryTask.doInBackground(): Illegal Argument Exception: " + e.toString());
+ return (null);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "SelectQueryTask.doInBackground(): Null Pointer Exception: " + e.toString());
+ return (null);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final Cursor result) {
+ mCallback.accept(result);
+ }
+ }
+
+
+ private String getEventWhereClause(boolean includeWarnings) {
+ String whereClause;
+ if (includeWarnings) {
+ whereClause = "Status in (?, ?, ?, ?)";
+ } else {
+ whereClause = "Status in (?, ?, ?)";
+ }
+ return (whereClause);
+ }
+
+ private String[] getEventWhereArgs(boolean includeWarnings) {
+ String[] whereArgs;
+ if (includeWarnings) {
+ whereArgs = new String[]{"1", "2", "3", "5"};
+ } else {
+ whereArgs = new String[]{"2", "3", "5"};
+ }
+ return (whereArgs);
+ }
+
+
+ /***************************************************************************************
+ * Remote Database Part
+ */
public void writeToRemoteServer() {
- Log.v(TAG,"writeToRemoteServer()");
+ Log.v(TAG, "writeToRemoteServer()");
if (!mLogRemote) {
- Log.v(TAG,"mLogRemote not set, not doing anything");
+ Log.v(TAG, "writeToRemoteServer(): mLogRemote not set, not doing anything");
return;
}
if (!mLogRemoteMobile) {
// Check network state - are we using mobile data?
if (mUtil.isMobileDataActive()) {
- Log.v(TAG,"Using mobile data, so not doing anything");
+ Log.v(TAG, "writeToRemoteServer(): Using mobile data, so not doing anything");
return;
}
}
if (!mUtil.isNetworkConnected()) {
- Log.v(TAG,"No network connection - doing nothing");
+ Log.v(TAG, "writeToRemoteServer(): No network connection - doing nothing");
return;
}
- Log.v(TAG,"Requirements for remote logging met!");
+ if (mUploadInProgress) {
+ Log.v(TAG, "writeToRemoteServer(): Upload already in progress, not starting another upload");
+ return;
+ }
+
+ Log.d(TAG, "writeToRemoteServer(): calling UploadSdData()");
uploadSdData();
}
- /**
- * Authenticate using the WebAPI to obtain a token for future API requests.
- * @param uname - user name
- * @param passwd - password
- */
- public void authenticate(String uname, String passwd) {
- Log.v(TAG, "authenticate()");
- // FIXME - this does not work!!!!
- String dataStr = "{'login':"+uname+", 'password':"+passwd+"}";
- //new PostDataTask().execute("http://" + mOSDUrl + ":8080/data", dataStr, mOSDUname, mOSDPasswd);
- String urlStr = mOSDUrl+"/api/accounts/login/";
- Log.v(TAG,"authenticate: url="+urlStr+", data="+dataStr);
- new PostDataTask().execute(
- urlStr, dataStr);
- }
-
/**
* Upload a batch of seizure detector data records to the server..
- * Uses the UploadSdDataTask class to upload the data in the
- * background. DownloadSdDataTask.onPostExecute() is called on completion.
+ * Uses the webApiConnection class to upload the data in the background.
+ * It searches the local database for the oldest event that has not been uploaded and uploads it.
+ * eventCallback is called when the event is created.
*/
public void uploadSdData() {
- Log.v(TAG, "uploadSdData()");
- String dataStr = "data string to upload";
- //new PostDataTask().execute("http://" + mOSDUrl + ":8080/data", dataStr, mOSDUname, mOSDPasswd);
- //new PostDataTask().execute("http://192.168.43.175:8765/datapoints/add", dataStr, mOSDUname, mOSDPasswd);
+ //int eventId = -1;
+ //Log.v(TAG, "uploadSdData()");
+ // First try uploading full alarms, and only if we do not have any of those, upload warnings.
+ //boolean warningsArr[] = {false, true};
+ // Upload everything - alarms and warnings - we can sort it out in post-processing the data!
+ boolean warningsArr[] = {true};
+ for (int n = 0; n < warningsArr.length; n++) {
+ boolean warningsVal = warningsArr[n];
+ Log.i(TAG, "uploadSdData(): warningsVal=" + warningsVal);
+ if (mUploadInProgress) {
+ Log.d(TAG, "uploadSdData - upload already in progress - not doing anything");
+ return;
+ }
+ mUploadInProgress = true;
+ getNextEventToUpload(warningsVal, (Long eventId) -> {
+ if (eventId != -1) {
+ Log.i(TAG, "uploadSdData() - next Event to Upload eventId=" + eventId);
+ String eventJsonStr = getLocalEventById(eventId);
+ Log.v(TAG, "uploadSdData() - event to upload eventJsonStr=" + eventJsonStr);
+ //int eventType;
+ JSONObject eventObj;
+ int eventAlarmStatus;
+ String eventDateStr;
+ Date eventDate;
+ String eventType;
+ String eventSubType;
+ String eventDesc;
+ String eventDataJSON;
+ try {
+ JSONArray datapointJsonArr = new JSONArray(eventJsonStr);
+ eventObj = datapointJsonArr.getJSONObject(0); // We only look at the first (and hopefully only) item in the array.
+ eventAlarmStatus = Integer.parseInt(eventObj.getString("status"));
+ eventDateStr = eventObj.getString("dataTime");
+ eventType = eventObj.getString("type");
+ eventSubType = eventObj.getString("subType");
+ if (eventObj.has("desc"))
+ eventDesc = eventObj.getString("desc");
+ else
+ eventDesc = "";
+ eventDataJSON = eventObj.getString("dataJSON");
+ Log.d(TAG, "uploadSdData - data from local DB is:" + eventJsonStr + ", eventAlarmStatus="
+ + eventAlarmStatus + ", eventDateStr=" + eventDateStr);
+ } catch (JSONException e) {
+ Log.e(TAG, "uploadSdData(): ERROR parsing event JSON Data" + eventJsonStr);
+ e.printStackTrace();
+ mUploadInProgress = false;
+ return;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "uploadSdData(): ERROR null pointer exception parsing event JSON Data: " + eventJsonStr);
+ e.printStackTrace();
+ mUploadInProgress = false;
+ return;
+ }
+ try {
+ eventDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(eventDateStr);
+ } catch (ParseException e) {
+ Log.e(TAG, "UploadSdData(): Error parsing date " + eventDateStr);
+ mUploadInProgress = false;
+ return;
+ }
+
+ Log.i(TAG, "uploadSdData - calling mWac.createEvent");
+ mCurrentEventLocalId = eventId;
+ mWac.createEvent(eventAlarmStatus, eventDate, eventType, eventSubType, eventDesc, eventDataJSON, this::createEventCallback);
+ } else {
+ Log.v(TAG, "uploadSdData - no data to upload "); //(warnings="+warningsVal+")");
+ mUploadInProgress = false;
+ }
+ });
+ }
}
- private class PostDataTask extends AsyncTask {
- @Override
- protected String doInBackground(String... params) {
- // params comes from the execute() call:
- // params[0] is the url,
- // params[1] is the data to send.
- // params[2] is the user name (not used)
- // params[3] is the password (not used)
- int MAXLEN = 500; // Maximum length of response that we will accept (bytes)
- InputStream is = null;
- String urlStr = params[0];
- String dataStr = params[1];
- String resultStr = "Not Initialised";
- Log.v(TAG,"doInBackgound(): url="+urlStr+" data="+dataStr);
- try {
- URL url = new URL(urlStr);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setReadTimeout(2000 /* milliseconds */);
- conn.setConnectTimeout(5000 /* milliseconds */);
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Content-Type", "application/json; utf-8");
- conn.setRequestProperty("Accept", "application/json");
- //String auth = uname + ":" + passwd;
- //byte[] encodedAuth = Base64.encodeBase64(auth.getBytes("utf-8"));
- //String authHeaderValue = "Basic " + new String(encodedAuth);
- //conn.setRequestProperty("Authorization", authHeaderValue);
- conn.setDoInput(true);
- // Put our data into the outputstream associated with the connection.
- OutputStream os = conn.getOutputStream();
- byte[] input = dataStr.getBytes("utf-8");
- os.write(input, 0, input.length);
+ // Mark the relevant member variables to show we are not cuurrently doing an upload, so a new one can be
+ // started if necessary.
+ public void finishUpload() {
+ mCurrentEventRemoteId = null;
+ mCurrentEventLocalId = -1;
+ mCurrentDatapointId = -1;
+ mDatapointsToUploadList = null;
+ mUploadInProgress = false;
+ }
- // Starts the query
- conn.connect();
- int response = conn.getResponseCode();
- Log.d(TAG, "The response code is: " + response);
- is = conn.getInputStream();
-
- // Convert the InputStream into a string
- Reader reader = new InputStreamReader(is, "UTF-8");
- char[] buffer = new char[MAXLEN];
- reader.read(buffer);
- resultStr = new String(buffer);
-
- } catch (IOException e) {
- Log.v(TAG,"doInBackground(): IOException - "+e.toString());
- resultStr = "Error"+e.toString();
-
- // Makes sure that the InputStream is closed after the app is
- // finished using it.
- } finally {
- if (is != null) {
+ // Called by WebApiConnection when a new event record is created.
+ // Once the event is created it queries the local database to find the datapoints associated with the event
+ // and uploads those as a batch of data points.
+ public void createEventCallback(String eventId) {
+ Log.v(TAG, "createEventCallback(): " + eventId);
+ Log.v(TAG, "createEventCallback(): Retrieving remote event details");
+ mWac.getEvent(eventId, new WebApiConnection.JSONObjectCallback() {
+ @Override
+ public void accept(JSONObject eventObj) {
+ if (eventObj == null) {
+ Log.e(TAG, "createEventCallback() - eventObj is null - failed to create event");
+ mUtil.showToast("Error Creating Remote Event");
+ } else {
+ Log.v(TAG, "createEventCallback() - eventObj=" + eventObj.toString());
+ Date eventDate;
+ String eventDateStr = "";
try {
- is.close();
- } catch (IOException e) {
- Log.v(TAG,"doInBackground(): IOException - "+e.toString());
- resultStr = "Error"+e.toString();
+ String dateStr = eventObj.getString("dataTime");
+ eventDate = mUtil.string2date(dateStr);
+ } catch (JSONException e) {
+ Log.e(TAG, "createEventCallback() - Error parsing JSONObject: " + eventObj.toString());
+ finishUpload();
+ return;
+ }
+ if (eventDate != null) {
+ Log.v(TAG, "createEventCallback() EventId=" + eventId + ", eventDateStr=" + eventDateStr + ", eventDate=" + eventDate);
+ mUploadInProgress = true;
+ long eventDateMillis = eventDate.getTime();
+ long startDateMillis = eventDateMillis - 1000 * mEventDuration / 2;
+ long endDateMillis = eventDateMillis + 1000 * mEventDuration / 2;
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ getDatapointsByDate(
+ dateFormat.format(new Date(startDateMillis)),
+ dateFormat.format(new Date(endDateMillis)),
+ (String datapointsJsonStr) -> {
+ //Log.v(TAG, "createEventCallback() - datapointsJsonStr=" + datapointsJsonStr);
+ JSONArray dataObj;
+ mDatapointsToUploadList = new ArrayList();
+ try {
+ //DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ dataObj = new JSONArray(datapointsJsonStr);
+ Log.v(TAG, "createEventCallback() - datapointsObj length=" + dataObj.length());
+ for (int i = 0; i < dataObj.length(); i++) {
+ mDatapointsToUploadList.add(dataObj.getJSONObject(i));
+ }
+ } catch (JSONException e) {
+ Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string " + datapointsJsonStr);
+ dataObj = null;
+ finishUpload();
+ }
+ // This starts the process of uploading the datapoints, one at a time.
+ mCurrentEventRemoteId = eventId;
+ Log.v(TAG, "createEventCallback() - starting datapoints upload with eventId " + mCurrentEventRemoteId +
+ " Uploading " + mDatapointsToUploadList.size() + " datapoints");
+ uploadNextDatapoint();
+
+ });
+ } else {
+ Log.e(TAG, "createEventCallback() - Error - event date is null - not doing anything");
+ mUtil.showToast("Error uploading event - date is null");
+ finishUpload();
}
}
}
+ });
+ }
+
+ // takes the next datapoint of the list mDatapointsToUploadList and uploads it to the remote server.
+ // datapointCallback is called when the upload is complete.
+ public void uploadNextDatapoint() {
+ //Log.v(TAG, "uploadNextDatapoint()");
+ if (mDatapointsToUploadList != null) {
+ if (mDatapointsToUploadList.size() > 0) {
+ mUploadInProgress = true;
+ try {
+ mCurrentDatapointId = mDatapointsToUploadList.get(0).getInt("id");
+ } catch (JSONException e) {
+ Log.e(TAG, "uploadNextDatapoint(): Error reading currentDatapointID from mDatapointsToUploadList[0]" + e.getMessage());
+ Log.e(TAG, "uploadNextDatapoint(): Removing mDatapointsToUploadList[0] and trying the next datapoint");
+ mDatapointsToUploadList.remove(0);
+ uploadNextDatapoint();
+ }
+
+ Log.v(TAG, "uploadNextDatapoint() - " + mDatapointsToUploadList.size() + " datapoints to upload. Uploading datapoint ID:" + mCurrentDatapointId);
+ mWac.createDatapoint(mDatapointsToUploadList.get(0), mCurrentEventRemoteId, this::datapointCallback);
- if (resultStr.startsWith("Unable to retrieve web page")) {
- Log.v(TAG,"doInBackground() - Unable to retrieve data");
} else {
- Log.v(TAG,"doInBackground(): result = "+resultStr);
+ Log.i(TAG, "uploadNextDatapoint() - All datapoints uploaded!");
+ setEventToUploaded(mCurrentEventLocalId, mCurrentEventRemoteId);
+ finishUpload();
}
- return (resultStr);
-
- }
- // onPostExecute displays the results of the AsyncTask.
- @Override
- protected void onPostExecute(String result) {
- Log.v(TAG,"onPostExecute() - result = "+result);
+ } else {
+ Log.w(TAG, "uploadNextDatapoint - mDatapointsToUploadList is null - I don't thin this should have happened!");
}
}
+ // Called by WebApiConnection when a new datapoint is created. It assumes that we have just created
+ // a datapoint based on mDatapointsToUploadList(0) so removes that from the list and calls UploadDatapoint()
+ // to upload the next one.
+ public void datapointCallback(String datapointStr) {
+ Log.v(TAG, "datapointCallback() dataPointId=" + mCurrentDatapointId + " remote datapointID=" + datapointStr + ", mCurrentEventId=" + mCurrentEventRemoteId);
+ if (mDatapointsToUploadList != null) {
+ if (mDatapointsToUploadList.size() > 0) {
+ mDatapointsToUploadList.remove(0);
+ }
+ } else {
+ Log.w(TAG, "datapointCallback - mDatapointsToUploadList is null - I don't thin this should have happened!");
+ }
+ setDatapointToUploaded(mCurrentDatapointId, mCurrentEventRemoteId);
+ uploadNextDatapoint();
+ }
+ /**
+ * close() - shut down the logging system
+ * WARNING - this should only be called by the final destructor of the app (not individual class destructors)
+ * because it will close the DB for all instances of LogManger, not just the one on which it is called.
+ * FIXME: If I was keen I would keep a count of how many instances of LogManager there are, and have this function do nothing
+ * unless it was the last instance.
+ */
+ public static void close() {
+ mOsdDb.close();
+ mOsdDb = null;
+ if (mWac != null) {
+ Log.i(TAG, "Stopping Remote Database Interface");
+ mWac.close();
+ }
+ }
- public void close() {
- mOSDDb.close();
+ public void stop() {
+ // Stop the timers and shutdown the remote API connection.
stopRemoteLogTimer();
- }
-
-
- public JSONObject queryDatapoints(String endDateStr, Double duration) {
- Log.d(TAG,"queryDatapoints() - endDateStr="+endDateStr);
- Cursor c = null;
- try {
- c = mOSDDb.getWritableDatabase().query(mDbTableName, null,
- null, null, null, null, null);
- //c.query("Select * from ? where DataTime < ?", mDbTableName, endDateStr);
- }
- catch (Exception e) {
- Log.d(TAG, mDbTableName+" doesn't exist :(((");
- }
- return(null);
- }
-
- public class OsdDbHelper extends SQLiteOpenHelper {
- // If you change the database schema, you must increment the database version.
- public static final int DATABASE_VERSION = 1;
- public static final String DATABASE_NAME = "OsdData.db";
- private String mOsdTableName;
- private String TAG = "OsdDbHelper";
-
- public OsdDbHelper(String osdTableName, Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- mOsdTableName = osdTableName;
- }
- public void onCreate(SQLiteDatabase db) {
- Log.v(TAG,"onCreate - TableName="+mOsdTableName);
- String SQLStr = "CREATE TABLE IF NOT EXISTS "+mOsdTableName+"("
- + "id INT AUTO_INCREMENT PRIMARY KEY,"
- + "dataTime DATETIME,"
- + "wearer_id INT NOT NULL,"
- + "BattPC FLOAT,"
- + "specPow FLOAT,"
- + "roiRatio FLOAT,"
- + "avAcc FLOAT,"
- + "sdAcc FLOAT,"
- + "HR FLOAT,"
- + "Status INT,"
- + "dataJSON TEXT,"
- + "uploaded INT"
- + ");";
-
- db.execSQL(SQLStr);
- }
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // This database is only a cache for online data, so its upgrade policy is
- // to simply to discard the data and start over
- db.execSQL("Drop table if exists " + mOsdTableName + ";");
- onCreate(db);
- }
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- onUpgrade(db, oldVersion, newVersion);
- }
+ stopAutoPruneTimer();
}
/*
- * Start the timer that will send and SMS alert after a given period.
+ * Start the timer that will upload data to the remote server after a given period.
*/
private void startRemoteLogTimer() {
if (mRemoteLogTimer != null) {
- Log.v(TAG, "startRemoteLogTimer -timer already running - cancelling it");
+ Log.i(TAG, "startRemoteLogTimer -timer already running - cancelling it");
mRemoteLogTimer.cancel();
mRemoteLogTimer = null;
}
- Log.v(TAG, "startRemoteLogTimer() - starting RemoteLogTimer");
+ Log.i(TAG, "startRemoteLogTimer() - starting RemoteLogTimer");
mRemoteLogTimer =
- new RemoteLogTimer(10 * 1000, 1000);
+ new RemoteLogTimer(mRemoteLogPeriod * 1000, 1000);
mRemoteLogTimer.start();
}
/*
- * Cancel the SMS timer to prevent the SMS message being sent..
+ * Cancel the remote logging timer to prevent attempts to upload to remote database.
*/
public void stopRemoteLogTimer() {
if (mRemoteLogTimer != null) {
- Log.v(TAG, "stopRemoteLogTimer(): cancelling Remote Log timer");
+ Log.i(TAG, "stopRemoteLogTimer(): cancelling Remote Log timer");
mRemoteLogTimer.cancel();
mRemoteLogTimer = null;
}
}
+
+
+ /*
+ * Start the timer that will Auto Prune the database
+ */
+ private void startAutoPruneTimer() {
+ if (mAutoPruneTimer != null) {
+ Log.i(TAG, "startAutoPruneTimer -timer already running - cancelling it");
+ mAutoPruneTimer.cancel();
+ mAutoPruneTimer = null;
+ }
+ Log.i(TAG, "startAutoPruneTimer() - starting AutoPruneTimer");
+ mAutoPruneTimer =
+ new AutoPruneTimer(mAutoPrunePeriod * 1000, 1000);
+ mAutoPruneTimer.start();
+ }
+
+
+ /*
+ * Cancel the auto prune timer to prevent attempts to upload to remote database.
+ */
+ public void stopAutoPruneTimer() {
+ if (mAutoPruneTimer != null) {
+ Log.i(TAG, "stopAutoPruneTimer(): cancelling Auto Prune timer");
+ mAutoPruneTimer.cancel();
+ mAutoPruneTimer = null;
+ }
+ }
+
+
+ public static class OsdDbHelper extends SQLiteOpenHelper {
+ // If you change the database schema, you must increment the database version.
+ public static final int DATABASE_VERSION = 1;
+ public static final String DATABASE_NAME = "OsdData.db";
+ private static final String TAG = "LogManager.OsdDbHelper";
+
+ public OsdDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ Log.d(TAG, "OsdDbHelper constructor");
+ }
+
+ public void onCreate(SQLiteDatabase db) {
+ Log.i(TAG, "onCreate - TableName=" + mDpTableName);
+ String SQLStr = "CREATE TABLE IF NOT EXISTS " + mDpTableName + "("
+ + "id INTEGER PRIMARY KEY,"
+ + "dataTime DATETIME,"
+ + "status INT,"
+ + "dataJSON TEXT,"
+ + "uploaded TEXT" // Stores the ID of the datapoint in the remote database if uploaded, otherwise empty
+ + ");";
+ db.execSQL(SQLStr);
+ Log.i(TAG, "onCreate - TableName=" + mEventsTableName);
+ SQLStr = "CREATE TABLE IF NOT EXISTS " + mEventsTableName + "("
+ + "id INTEGER PRIMARY KEY,"
+ + "dataTime DATETIME,"
+ + "status INT,"
+ + "type TEXT,"
+ + "subType TEXT,"
+ + "notes TEXT," // avoiding using 'desc' as that is an sql name.
+ + "dataJSON TEXT,"
+ + "uploaded TEXT" // stores the id of the event in the remote dabase if uploaded, otherwise empty
+ + ");";
+ db.execSQL(SQLStr);
+ }
+
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // This database is only a cache for online data, so its upgrade policy is
+ // to simply to discard the data and start over
+ Log.i(TAG, "onUpgrade()");
+ db.execSQL("Drop table if exists " + mDpTableName + ";");
+ onCreate(db);
+ }
+
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.i(TAG, "onDowngrade()");
+ onUpgrade(db, oldVersion, newVersion);
+ }
+ }
+
+
/**
- * Inhibit fault alarm initiation for a period to avoid spurious warning
- * beeps caused by short term network interruptions.
+ * Upload recorded data to the remote database periodically.
*/
private class RemoteLogTimer extends CountDownTimer {
public RemoteLogTimer(long startTime, long interval) {
@@ -380,12 +1132,35 @@ public class LogManager {
@Override
public void onFinish() {
- //FIXME - make this do something!
- //Log.v(TAG, "mRemoteLogTimer - onFinish");
- //writeToRemoteServer();
+ Log.d(TAG, "mRemoteLogTimer - onFinish - uploading data to remote database");
+ writeToRemoteServer();
+ // Restart this timer.
start();
}
}
+ /**
+ * Prune the database periodically.
+ */
+ private class AutoPruneTimer extends CountDownTimer {
+ public AutoPruneTimer(long startTime, long interval) {
+ super(startTime, interval);
+ }
+
+ @Override
+ public void onTick(long l) {
+ }
+
+ @Override
+ public void onFinish() {
+ Log.d(TAG, "mAutoPruneTimer - onFinish - Pruning Local Database");
+ pruneLocalDb();
+ // Restart this timer.
+ start();
+ }
+
+ }
+
+
}
diff --git a/app/src/main/java/uk/org/openseizuredetector/LogManagerActivity.java b/app/src/main/java/uk/org/openseizuredetector/LogManagerActivity.java
deleted file mode 100644
index c45888c..0000000
--- a/app/src/main/java/uk/org/openseizuredetector/LogManagerActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package uk.org.openseizuredetector;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ListView;
-
-import uk.org.openseizuredetector.EventLogManager.EventLogListAdapter;
-import uk.org.openseizuredetector.EventLogManager.EventLogManager;
-import uk.org.openseizuredetector.EventLogManager.LogEntryModel;
-
-
-public class LogManagerActivity extends FragmentActivity
-implements AuthDialogInterface {
- private String TAG = "LogManagerActivity";
- private EventLogListAdapter mEventLogListAdapter;
- private ListView mEventLogListView;
- private EventLogManager mElm;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_log_manager);
-
- LogEntryModel lem = new LogEntryModel();
- //lem.setDate(new Date());
- lem.setNote("Test Entry");
- lem.setDataJSON("[]");
- lem.setAlarmState(1);
-
- //mElm = new EventLogManager(this);
- //mElm.addRow(lem);
-
- //mEventLogListAdapter = new EventLogListAdapter(this);
- //mEventLogListView = (ListView) findViewById(R.id.eventLogListView);
- //mEventLogListView.setAdapter(mEventLogListAdapter);
-
- Button b;
-
- b = (Button) findViewById(R.id.authenticate_button);
- b.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Log.v(TAG, "authenticate button clicked");
- AuthDialog authDialog = new AuthDialog();
- authDialog.show(getSupportFragmentManager(),"authDialog");
- }
- });
- }
-
- public void updateUi() {
- Log.v(TAG, "updateUi");
- }
-
- public void onDialogDone(boolean State) {
-
- Log.v(TAG,"onDialogDone()");
- LogManager lm = new LogManager(this);
- lm.authenticate("test", "testpw");
-
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java b/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java
new file mode 100644
index 0000000..82a25f1
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java
@@ -0,0 +1,653 @@
+package uk.org.openseizuredetector;
+
+//import androidx.appcompat.app.AppCompatActivity;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.os.IBinder;
+
+import androidx.core.view.MenuCompat;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.Field;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LogManagerControlActivity extends AppCompatActivity {
+ private String TAG = "LogManagerControlActivity";
+ private LogManager mLm;
+ private Context mContext;
+ private UiTimer mUiTimer;
+ private ArrayList> mEventsList;
+ private ArrayList> mRemoteEventsList;
+ private ArrayList> mSysLogList;
+ private SdServiceConnection mConnection;
+ private OsdUtil mUtil;
+ final Handler serverStatusHandler = new Handler();
+ private Integer mUiTimerPeriodFast = 2000; // 2 seconds - we use fast updating while UI is blank and we are waiting for first data
+ private Integer mUiTimerPeriodSlow = 60000; // 60 seconds - once data has been received and UI populated we only update once per minute.
+ private boolean mUpdateSysLog = true;
+ //private Integer UI_MODE_LOCAL = 0;
+ //private Integer UI_MODE_SHARED = 1;
+ //private Integer mUiMode = UI_MODE_SHARED;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+ mContext = this;
+ mUtil = new OsdUtil(getApplicationContext(), serverStatusHandler);
+
+ if (!mUtil.isServerRunning()) {
+ mUtil.showToast(getString(R.string.error_server_not_running));
+ finish();
+ return;
+ }
+
+ mConnection = new SdServiceConnection(getApplicationContext());
+
+ setContentView(R.layout.activity_log_manager_control);
+
+ /* Force display of overflow menu - from stackoverflow
+ * "how to force use of..."
+ */
+ try {
+ Log.v(TAG, "trying menubar fiddle...");
+ ViewConfiguration config = ViewConfiguration.get(this);
+ Field menuKeyField =
+ ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
+ if (menuKeyField != null) {
+ Log.v(TAG, "menuKeyField is not null - configuring....");
+ menuKeyField.setAccessible(true);
+ menuKeyField.setBoolean(config, false);
+ } else {
+ Log.v(TAG, "menuKeyField is null - doing nothing...");
+ }
+ } catch (Exception e) {
+ Log.v(TAG, "menubar fiddle exception: " + e.toString());
+ }
+
+ Button authBtn =
+ (Button) findViewById(R.id.auth_button);
+ authBtn.setOnClickListener(onAuth);
+ //Button pruneBtn =
+ // (Button) findViewById(R.id.pruneDatabaseBtn);
+ //pruneBtn.setOnClickListener(onPruneBtn);
+ //Button reportSeizureBtn =
+ // (Button) findViewById(R.id.reportSeizureBtn);
+ //reportSeizureBtn.setOnClickListener(onReportSeizureBtn);
+ Button remoteDbBtn =
+ (Button) findViewById(R.id.refresh_button);
+ remoteDbBtn.setOnClickListener(onRefreshBtn);
+
+ ListView lv = (ListView) findViewById(R.id.eventLogListView);
+ lv.setOnItemClickListener(onEventListClick);
+
+ lv = (ListView) findViewById(R.id.remoteEventsLv);
+ lv.setOnItemClickListener(onRemoteEventListClick);
+ }
+
+ /**
+ * Create Action Bar
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ Log.i(TAG, "onCreateOptionsMenu()");
+ getMenuInflater().inflate(R.menu.log_manager_activity_menu, menu);
+ MenuCompat.setGroupDividerEnabled(menu, true);
+ return true;
+ }
+
+
+ @Override
+ protected void onStart() {
+ Log.v(TAG, "onStart()");
+ super.onStart();
+ mUtil.bindToServer(getApplicationContext(), mConnection);
+ waitForConnection();
+ startUiTimer(mUiTimerPeriodFast);
+ }
+
+ @Override
+ protected void onStop() {
+ Log.v(TAG, "onStop()");
+ super.onStop();
+ stopUiTimer();
+ mUtil.unbindFromServer(getApplicationContext(), mConnection);
+ }
+
+ @Override
+ protected void onPause() {
+ Log.v(TAG, "onPause()");
+ super.onPause();
+ //stopUiTimer();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.v(TAG, "onResume()");
+ super.onResume();
+ //startUiTimer();
+ }
+
+ private void waitForConnection() {
+ // We want the UI to update as soon as it is displayed, but it takes a finite time for
+ // the mConnection to bind to the service, so we delay half a second to give it chance
+ // to connect before trying to update the UI for the first time (it happens again periodically using the uiTimer)
+ if (mConnection.mBound) {
+ Log.v(TAG, "waitForConnection - Bound!");
+ initialiseServiceConnection();
+ } else {
+ Log.v(TAG, "waitForConnection - waiting...");
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ waitForConnection();
+ }
+ }, 100);
+ }
+ }
+
+ // FIXME - for some reason this never gets called, which is why we have the 'waitForConnection()'
+ // function that polls the connection until it is connected.
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.w(TAG, "onServiceConnected()");
+ initialiseServiceConnection();
+ }
+
+ private void initialiseServiceConnection() {
+ mLm = mConnection.mSdServer.mLm;
+ startUiTimer(mUiTimerPeriodFast);
+ getRemoteEvents();
+ // Populate events list - we only do it once when the activity is created because the query might slow down the UI.
+ // We could try this code in updateUI() and see though.
+ // Based on https://www.tutlane.com/tutorial/android/android-sqlite-listview-with-examples
+ mLm.getEventsList(true, (ArrayList> eventsList) -> {
+ mEventsList = eventsList;
+ Log.v(TAG, "initialiseServiceConnection() - set mEventsList - Updating UI");
+ updateUi();
+ });
+ mUtil.getSysLogList((ArrayList> syslogList) -> {
+ mSysLogList = syslogList;
+ Log.v(TAG, "initialiseServiceConnection() - set mSysLogList - Updating UI");
+ updateUi();
+ });
+ }
+
+
+ private void getRemoteEvents() {
+ // Retrieve events from remote database
+ mLm.mWac.getEvents((JSONObject remoteEventsObj) -> {
+ Log.v(TAG, "getRemoteEvents()");
+ if (remoteEventsObj == null) {
+ Log.e(TAG, "getRemoteEvents Callback: Error Retrieving events");
+ mUtil.showToast("Error Retrieving Remote Events from Server - Please Try Again Later!");
+ } else {
+ //Log.v(TAG, "remoteEventsObj = " + remoteEventsObj.toString());
+ try {
+ JSONArray eventsArray = remoteEventsObj.getJSONArray("events");
+ mRemoteEventsList = new ArrayList>();
+ // A bit of a hack to display in reverse chronological order
+ for (int i = eventsArray.length() - 1; i >= 0; i--) {
+ JSONObject eventObj = eventsArray.getJSONObject(i);
+ Log.v(TAG, "getRemoteEvents() - " + eventObj.toString());
+ String id = null;
+ if (!eventObj.isNull("id")) {
+ id = eventObj.getString("id");
+ }
+ int osdAlarmState = -1;
+ if (!eventObj.isNull("osdAlarmState")) {
+ osdAlarmState = eventObj.getInt("osdAlarmState");
+ }
+ String dataTime = "null";
+ if (!eventObj.isNull("dataTime")) {
+ dataTime = eventObj.getString("dataTime");
+ Log.v(TAG, "getRemoteEvents() - dataTime=" + dataTime);
+ }
+ String typeStr = "null";
+ if (!eventObj.isNull("type")) {
+ typeStr = eventObj.getString("type");
+ }
+ String subType = "null";
+ if (!eventObj.isNull("subType")) {
+ subType = eventObj.getString("subType");
+ }
+ String desc = "null";
+ if (!eventObj.isNull("desc")) {
+ desc = eventObj.getString("desc");
+ }
+ HashMap eventHashMap = new HashMap();
+ eventHashMap.put("id", id);
+ eventHashMap.put("osdAlarmState", String.valueOf(osdAlarmState));
+ eventHashMap.put("osdAlarmStateStr", mUtil.alarmStatusToString(osdAlarmState));
+ eventHashMap.put("dataTime", dataTime);
+ eventHashMap.put("type", typeStr);
+ eventHashMap.put("subType", subType);
+ eventHashMap.put("desc", desc);
+ mRemoteEventsList.add(eventHashMap);
+ }
+ Log.v(TAG, "getRemoteEvents() - set mRemoteEventsList(). Updating UI");
+ updateUi();
+ } catch (JSONException e) {
+ Log.e(TAG, "getRemoteEvents(): Error Parsing remoteEventsObj: " + e.getMessage());
+ mUtil.showToast("Error Parsing remoteEventsObj - this should not happen!!!");
+ mRemoteEventsList = null;
+ }
+ //Log.v(TAG, "getRemoteEvents(): mRemoteEventsList = " + mRemoteEventsList.toString());
+ }
+ });
+ }
+
+
+ private void updateUi() {
+ Log.i(TAG, "updateUi()");
+ boolean stopUpdating = true;
+ TextView tv;
+ Button btn;
+ // Local Database Information
+ if (mLm != null) {
+ mLm.getLocalEventsCount(true, (Long eventCount) -> {
+ TextView tv1 = (TextView) findViewById(R.id.num_local_events_tv);
+ tv1.setText(String.format("%d", eventCount));
+ });
+ mLm.getLocalDatapointsCount((Long datapointsCount) -> {
+ TextView tv2 = (TextView) findViewById(R.id.num_local_datapoints_tv);
+ tv2.setText(String.format("%d", datapointsCount));
+ });
+ } else {
+ stopUpdating = false;
+ }
+ // Local Database ListView
+ if (mEventsList != null) {
+ ListView lv = (ListView) findViewById(R.id.eventLogListView);
+ ListAdapter adapter = new SimpleAdapter(LogManagerControlActivity.this, mEventsList, R.layout.log_entry_layout,
+ new String[]{"dataTime", "status", "uploaded"},
+ new int[]{R.id.event_date, R.id.event_alarmState, R.id.event_uploaded});
+ lv.setAdapter(adapter);
+ //Log.v(TAG,"eventsList="+mEventsList);
+ } else {
+ stopUpdating = false;
+ }
+ // SysLog ListView
+ if (mSysLogList != null && mUpdateSysLog) {
+ ListView lv = (ListView) findViewById(R.id.sysLogListView);
+ ListAdapter adapter = new SimpleAdapter(LogManagerControlActivity.this, mSysLogList, R.layout.syslog_entry_layout,
+ new String[]{"dataTime", "logLevel", "dataJSON"},
+ new int[]{R.id.syslog_entry_date_tv, R.id.syslog_level_tv, R.id.syslog_entry_text_tv});
+ lv.setAdapter(adapter);
+ //Log.v(TAG,"eventsList="+mEventsList);
+ mUpdateSysLog = false;
+ }
+ // Remote Database List View
+ if (mRemoteEventsList != null) {
+ ListView lv = (ListView) findViewById(R.id.remoteEventsLv);
+ ListAdapter adapter = new RemoteEventsAdapter(LogManagerControlActivity.this, mRemoteEventsList, R.layout.log_entry_layout_remote,
+ new String[]{"id", "dataTime", "type", "subType", "osdAlarmStateStr", "desc"},
+ new int[]{R.id.event_id_remote_tv, R.id.event_date_remote_tv, R.id.event_type_remote_tv, R.id.event_subtype_remote_tv,
+ R.id.event_alarmState_remote_tv, R.id.event_notes_remote_tv});
+ lv.setAdapter(adapter);
+ //Log.i(TAG,"adapter[0]="+adapter.getItem(0));
+ //Log.i(TAG,"adapter[3]="+adapter.getItem(3));
+ } else {
+ //mUtil.showToast("No Remote Events");
+ Log.i(TAG, "UpdateUi: No Remote Events");
+ stopUpdating = false;
+ }
+
+
+ // Remote Database Information
+ if (mLm != null) {
+ tv = (TextView) findViewById(R.id.authStatusTv);
+ btn = (Button) findViewById(R.id.auth_button);
+ if (mLm.mWac.isLoggedIn()) {
+ tv.setText(getString(R.string.logged_in_with_token));
+ btn.setText(getString(R.string.logout));
+ } else {
+ tv.setText(getString(R.string.not_authenticated));
+ btn.setText(getString(R.string.login));
+ }
+ } else {
+ stopUpdating = false;
+ }
+
+ // Note we do not really stop updating the UI, just change from the fast update period to the slow update period
+ // to save hammering the databases once the UI has been populated once.
+ if (stopUpdating) {
+ stopUiTimer();
+ startUiTimer(mUiTimerPeriodSlow);
+ }
+ } //updateUi();
+
+ public void onRadioButtonClicked(View view) {
+ LinearLayout localDataLl = (LinearLayout) findViewById(R.id.local_data_ll);
+ LinearLayout sharedDataLl = (LinearLayout) findViewById(R.id.shared_data_ll);
+ LinearLayout syslogLl = (LinearLayout) findViewById(R.id.syslog_ll);
+ // Is the button now checked?
+ boolean checked = ((RadioButton) view).isChecked();
+
+ // Check which radio button was clicked
+ switch (view.getId()) {
+ case R.id.local_data_rb:
+ if (checked) {
+ // Switch to the local data view
+ localDataLl.setVisibility(View.VISIBLE);
+ sharedDataLl.setVisibility(View.GONE);
+ syslogLl.setVisibility(View.GONE);
+ }
+ break;
+ case R.id.shared_data_rb:
+ if (checked) {
+ // Switch to the local data view
+ localDataLl.setVisibility(View.GONE);
+ sharedDataLl.setVisibility(View.VISIBLE);
+ syslogLl.setVisibility(View.GONE);
+ }
+ break;
+ case R.id.syslog_rb:
+ if (checked) {
+ // Switch to the local data view
+ localDataLl.setVisibility(View.GONE);
+ sharedDataLl.setVisibility(View.GONE);
+ syslogLl.setVisibility(View.VISIBLE);
+ }
+ break;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Log.i(TAG, "onOptionsItemSelected() : " + item.getItemId() + " selected");
+ switch (item.getItemId()) {
+ case R.id.action_authenticate_api:
+ Log.i(TAG, "action_autheticate_api");
+ try {
+ Intent i = new Intent(
+ getApplicationContext(),
+ AuthenticateActivity.class);
+ this.startActivity(i);
+ } catch (Exception ex) {
+ Log.i(TAG, "exception starting export activity " + ex.toString());
+ }
+ return true;
+ case R.id.pruneDatabaseMenuItem:
+ Log.i(TAG, "action_pruneDatabase");
+ onPruneBtn.onClick(null);
+ return true;
+ case R.id.action_report_seizure:
+ Log.i(TAG, "action_report_seizure");
+ try {
+ Intent intent = new Intent(
+ getApplicationContext(),
+ ReportSeizureActivity.class);
+ this.startActivity(intent);
+ } catch (Exception ex) {
+ Log.i(TAG, "exception starting Report Seizure activity " + ex.toString());
+ }
+ return true;
+ case R.id.action_settings:
+ Log.i(TAG, "action_settings");
+ try {
+ Intent prefsIntent = new Intent(
+ getApplicationContext(),
+ PrefActivity.class);
+ this.startActivity(prefsIntent);
+ } catch (Exception ex) {
+ Log.i(TAG, "exception starting settings activity " + ex.toString());
+ }
+ return true;
+ case R.id.action_mark_unknown:
+ Log.i(TAG, "action_mark_unknown");
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.mark_unverified_events_unknown_dialog_title)
+ .setMessage(R.string.mark_unverified_events_unknown_dialog_message)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mLm.mWac.markUnverifiedEventsAsUnknown();
+ }
+ })
+ .setNegativeButton(android.R.string.no, null)
+ .show();
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+
+ View.OnClickListener onAuth =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onAuth");
+ Intent i;
+ i = new Intent(mContext, AuthenticateActivity.class);
+ startActivity(i);
+ }
+ };
+ View.OnClickListener onPruneBtn =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onPruneBtn");
+ // Confirmation dialog based on: https://stackoverflow.com/a/12213536/2104584
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle("Prune Database");
+ builder.setMessage(String.format("This will remove all data from the database that is more than %d days old."
+ + "\nThis can NOT be undone.\nAre you sure?", mLm.mDataRetentionPeriod));
+ builder.setPositiveButton("YES", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mLm.pruneLocalDb();
+ dialog.dismiss();
+ }
+ });
+ builder.setNegativeButton("NO", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+ };
+
+ View.OnClickListener onReportSeizureBtn =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onReportSeizureBtn");
+ Intent i;
+ i = new Intent(mContext, ReportSeizureActivity.class);
+ startActivity(i);
+ }
+ };
+
+ View.OnClickListener onRemoteDbBtn =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onRemoteDbBtn");
+ Intent i;
+ i = new Intent(mContext, RemoteDbActivity.class);
+ startActivity(i);
+ }
+ };
+
+ View.OnClickListener onRefreshBtn =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onRefreshBtn");
+ initialiseServiceConnection();
+ }
+ };
+
+
+ AdapterView.OnItemClickListener onEventListClick =
+ new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView> adapter, View v, int position, long id) {
+ Log.v(TAG, "onItemClicKListener() - Position=" + position + ", id=" + id);// Confirmation dialog based on: https://stackoverflow.com/a/12213536/2104584
+ HashMap eventObj = (HashMap) adapter.getItemAtPosition(position);
+ String eventId = eventObj.get("uploaded");
+ Log.d(TAG, "onItemClickListener(): eventId=" + eventId + ", eventObj=" + eventObj);
+ if (eventId != null) {
+ Intent i = new Intent(getApplicationContext(), EditEventActivity.class);
+ i.putExtra("eventId", eventId);
+ startActivity(i);
+ } else {
+ mUtil.showToast("You Must Wait for Event to Upload before Editing it");
+ }
+ }
+ };
+
+ AdapterView.OnItemClickListener onRemoteEventListClick =
+ new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView> adapter, View v, int position, long id) {
+ Log.v(TAG, "onRemoteEventList Click() - Position=" + position + ", id=" + id);// Confirmation dialog based on: https://stackoverflow.com/a/12213536/2104584
+ HashMap eventObj = (HashMap) adapter.getItemAtPosition(position);
+ String eventId = eventObj.get("id");
+ Log.d(TAG, "onItemClickListener(): eventId=" + eventId + ", eventObj=" + eventObj);
+ Intent i = new Intent(getApplicationContext(), EditEventActivity.class);
+ i.putExtra("eventId", eventId);
+ startActivity(i);
+ }
+ };
+
+
+ /*
+ * Start the timer that will update the user interface every 5 seconds..
+ */
+ private void startUiTimer(Integer uiTimerPeriod) {
+ if (mUiTimer != null) {
+ Log.v(TAG, "startUiTimer -timer already running - cancelling it");
+ mUiTimer.cancel();
+ mUiTimer = null;
+ }
+ Log.v(TAG, "startUiTimer() - starting UiTimer");
+ mUiTimer =
+ new UiTimer(uiTimerPeriod, 1000);
+ mUiTimer.start();
+ }
+
+
+ /*
+ * Cancel the remote logging timer to prevent attempts to upload to remote database.
+ */
+ public void stopUiTimer() {
+ if (mUiTimer != null) {
+ Log.v(TAG, "stopUiTimer(): cancelling UI timer");
+ mUiTimer.cancel();
+ mUiTimer = null;
+ }
+ }
+
+ /**
+ * Upload recorded data to the remote database periodically.
+ */
+ private class UiTimer extends CountDownTimer {
+ public UiTimer(long startTime, long interval) {
+ super(startTime, interval);
+ }
+
+ @Override
+ public void onTick(long l) {
+ // Do Nothing
+ }
+
+ @Override
+ public void onFinish() {
+ //Log.v(TAG, "UiTimer - onFinish - Updating UI");
+ updateUi();
+ // Restart this timer.
+ if (mUiTimer != null)
+ start();
+ }
+
+ }
+
+
+ private class RemoteEventsAdapter extends SimpleAdapter {
+
+ /**
+ * Constructor
+ *
+ * @param context The context where the View associated with this SimpleAdapter is running
+ * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The
+ * Maps contain the data for each row, and should include all the entries specified in
+ * "from"
+ * @param resource Resource identifier of a view layout that defines the views for this list
+ * item. The layout file should include at least those named views defined in "to"
+ * @param from A list of column names that will be added to the Map associated with each
+ * item.
+ * @param to The views that should display column in the "from" parameter. These should all be
+ * TextViews. The first N views in this list are given the values of the first N columns
+ */
+ public RemoteEventsAdapter(Context context, List extends Map> data, int resource, String[] from, int[] to) {
+ super(context, data, resource, from, to);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v = super.getView(position, convertView, parent);
+ Map dataItem = (Map) getItem(position);
+ Log.v(TAG, "getView() " + dataItem.toString());
+ switch (dataItem.get("type").toString()) {
+ case "null":
+ case "":
+ v.setBackgroundColor(Color.parseColor("#ffaaaa"));
+ break;
+ case "Seizure":
+ v.setBackgroundColor(Color.parseColor("#ff6060"));
+ break;
+ default:
+ v.setBackgroundColor(Color.TRANSPARENT);
+ }
+
+ // Convert date format to something more readable.
+ TextView tv = (TextView) v.findViewById(R.id.event_date_remote_tv);
+ Date dataTime = null;
+ String dateStr = (String) dataItem.get("dataTime");
+ dataTime = mUtil.string2date(dateStr);
+ if (dataTime != null) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
+ tv.setText(dateFormat.format(dataTime));
+ } else {
+ tv.setText("---");
+ }
+ return (v);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java
index 04bd053..bb3a5a9 100644
--- a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java
+++ b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java
@@ -25,7 +25,8 @@
package uk.org.openseizuredetector;
-import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
@@ -36,24 +37,20 @@ import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.preference.PreferenceManager;
-import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
-import android.widget.Button;
-import java.lang.reflect.Field;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Timer;
-import java.util.TimerTask;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.MenuCompat;
-//MPAndroidChart
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
@@ -63,6 +60,14 @@ import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.utils.ValueFormatter;
import com.rohitss.uceh.UCEHandler;
+import java.lang.reflect.Field;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Timer;
+import java.util.TimerTask;
+
+//MPAndroidChart
+
public class MainActivity extends AppCompatActivity {
static final String TAG = "MainActivity";
private int okColour = Color.BLUE;
@@ -80,6 +85,7 @@ public class MainActivity extends AppCompatActivity {
final Handler serverStatusHandler = new Handler();
Messenger messenger = new Messenger(new ResponseHandler());
Timer mUiTimer;
+ private Context mContext;
/**
* Called when the activity is first created.
@@ -87,7 +93,7 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Log.i(TAG,"onCreate()");
+ Log.i(TAG, "onCreate()");
// Set our custom uncaught exception handler to report issues.
//Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(MainActivity.this));
@@ -96,18 +102,19 @@ public class MainActivity extends AppCompatActivity {
.build();
//int i = 5/0; // Force exception to test handler.
- mUtil = new OsdUtil(this,serverStatusHandler);
- mConnection = new SdServiceConnection(this);
+ mUtil = new OsdUtil(getApplicationContext(), serverStatusHandler);
+ mConnection = new SdServiceConnection(getApplicationContext());
mUtil.writeToSysLogFile("");
mUtil.writeToSysLogFile("* MainActivity Started *");
mUtil.writeToSysLogFile("MainActivity.onCreate()");
+ mContext = this;
// Initialise the User Interface
setContentView(R.layout.main);
- /* Force display of overflow menu - from stackoverflow
- * "how to force use of..."
- */
+ /* Force display of overflow menu - from stackoverflow
+ * "how to force use of..."
+ */
try {
Log.v(TAG, "trying menubar fiddle...");
ViewConfiguration config = ViewConfiguration.get(this);
@@ -134,15 +141,14 @@ public class MainActivity extends AppCompatActivity {
Log.v(TAG, "acceptAlarmButton.onClick()");
if (mConnection.mBound) {
if ((mConnection.mSdServer.mSmsTimer != null)
- && (mConnection.mSdServer.mSmsTimer.mTimeLeft > 0 )){
- Log.v(TAG, "acceptAlarmButton.onClick() - Stopping SMS Timer");
+ && (mConnection.mSdServer.mSmsTimer.mTimeLeft > 0)) {
+ Log.i(TAG, "acceptAlarmButton.onClick() - Stopping SMS Timer");
mUtil.showToast(getString(R.string.SMSAlarmCancelledMsg));
mConnection.mSdServer.stopSmsTimer();
- }
- else {
+ } else {
Log.v(TAG, "acceptAlarmButton.onClick() - Accepting Alarm");
mConnection.mSdServer.acceptAlarm();
- }
+ }
}
}
});
@@ -158,7 +164,74 @@ public class MainActivity extends AppCompatActivity {
}
});
+ // Deal with the 'Raise Alarm'
+ button = (Button) findViewById(R.id.manualAlarmButton);
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ Log.v(TAG, "manualAlarmButton.onClick()");
+ // Confirmation dialog based on: https://stackoverflow.com/a/12213536/2104584
+ //AlertDialog.Builder builder = new AlertDialog.Builder(getBaseContext());
+ //builder.setTitle("Raise Alarm");
+ //builder.setMessage(String.format("Raise a Seizure Detected Alarm NOW?"));
+ //builder.setPositiveButton("YES", new DialogInterface.OnClickListener() {
+ // @Override
+ // public void onClick(DialogInterface dialog, int which) {
+ if (mConnection.mBound) {
+ mConnection.mSdServer.raiseManualAlarm();
+ }
+ // dialog.dismiss();
+ // }
+ //});
+ //builder.setNegativeButton("NO", new DialogInterface.OnClickListener() {
+ // @Override
+ // public void onClick(DialogInterface dialog, int which) {
+ // dialog.dismiss();
+ // }
+ //});
+ //AlertDialog alert = builder.create();
+ //if (!(this).isFinishing()) {
+ // alert.show();
+ //}
+
+ }
+ });
+ // The background service might ask us to show the data sharing dialog if data sharing is not working correctly
+ String actionStr = getIntent().getAction();
+ if (actionStr != null) {
+ Log.i(TAG, "onCreate() - action=" + actionStr);
+ if (actionStr.equals("showDataSharingDialog")) {
+ showDataSharingDialog();
+ }
+ } else {
+ Log.i(TAG, "onCreate - action is null - starting normally");
+ }
+ }
+
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ String actionStr;
+ Log.i(TAG, "onNewIntent");
+ Bundle extras = intent.getExtras();
+ // The background service might ask us to show the data sharing dialog if data sharing is not working correctly
+ actionStr = getIntent().getAction();
+ if (actionStr != null) {
+ Log.i(TAG, "onNewIntent() - action=" + actionStr);
+ if (actionStr.equals("showDataSharingDialog")) {
+ showDataSharingDialog();
+ }
+ } else {
+ if (extras != null) {
+ actionStr = extras.getString("action");
+ if (actionStr.equals("showDataSharingDialog")) {
+ showDataSharingDialog();
+ }
+ Log.i(TAG, "onNewIntent - extra actionstr is "+actionStr);
+ } else {
+ Log.i(TAG, "onNewIntent - extra actionstr is null - starting normally");
+ }
+ }
}
/**
@@ -168,6 +241,7 @@ public class MainActivity extends AppCompatActivity {
public boolean onCreateOptionsMenu(Menu menu) {
Log.i(TAG, "onCreateOptionsMenu()");
getMenuInflater().inflate(R.menu.main_activity_actions, menu);
+ MenuCompat.setGroupDividerEnabled(menu, true);
//mOptionsMenu = menu;
//if (mConnection.mSdServer.mSdDataSourceName != "Pebble") {
// Log.v(TAG,"Disabling Pebble Specific Menu Items");
@@ -191,7 +265,7 @@ public class MainActivity extends AppCompatActivity {
mConnection.mSdServer.mSdDataSource.installWatchApp();
return true;
- case R.id.action_accept_alarm:
+ case R.id.action_accept_alarm:
Log.i(TAG, "action_accept_alarm");
if (mConnection.mBound) {
mConnection.mSdServer.acceptAlarm();
@@ -202,14 +276,14 @@ public class MainActivity extends AppCompatActivity {
Log.i(TAG, "action_sart_stop");
if (mConnection.mBound) {
Log.i(TAG, "Stopping Server");
- mUtil.unbindFromServer(this, mConnection);
+ mUtil.unbindFromServer(getApplicationContext(), mConnection);
stopServer();
} else {
Log.i(TAG, "Starting Server");
startServer();
// and bind to it so we can see its data
Log.i(TAG, "Binding to Server");
- mUtil.bindToServer(this, mConnection);
+ mUtil.bindToServer(getApplicationContext(), mConnection);
}
return true;
/* fault beep test does not work with fault timer, so disable test option.
@@ -238,24 +312,44 @@ public class MainActivity extends AppCompatActivity {
mConnection.mSdServer.sendSMSAlarm();
}
return true;
- case R.id.action_test_phone_alarm:
+
+ /*case R.id.action_test_phone_alarm:
Log.i(TAG, "action_test_phone_alarm");
if (mConnection.mBound) {
mConnection.mSdServer.sendPhoneAlarm();
}
return true;
- case R.id.action_export:
- Log.i(TAG, "action_export");
+ */
+
+ case R.id.action_authenticate_api:
+ Log.i(TAG, "action_autheticate_api");
try {
Intent i = new Intent(
MainActivity.this,
- DBQueryActivity.class);
+ AuthenticateActivity.class);
this.startActivity(i);
} catch (Exception ex) {
Log.i(TAG, "exception starting export activity " + ex.toString());
}
return true;
- case R.id.action_logs:
+ case R.id.action_about_datasharing:
+ Log.i(TAG, "action_about_datasharing");
+ showDataSharingDialog();
+ return true;
+ /*
+ case R.id.action_export:
+ Log.i(TAG, "action_export");
+ try {
+ Intent i = new Intent(
+ MainActivity.this,
+ ExportDataActivity.class);
+ this.startActivity(i);
+ } catch (Exception ex) {
+ Log.i(TAG, "exception starting export activity " + ex.toString());
+ }
+ return true;
+ */
+ /* case R.id.action_logs:
Log.i(TAG, "action_logs");
try {
String url = "http://"
@@ -272,17 +366,29 @@ public class MainActivity extends AppCompatActivity {
Log.i(TAG, "exception starting log manager activity " + ex.toString());
}
return true;
+ */
case R.id.action_logmanager:
Log.i(TAG, "action_logmanager");
try {
Intent intent = new Intent(
MainActivity.this,
- LogManagerActivity.class);
+ LogManagerControlActivity.class);
this.startActivity(intent);
} catch (Exception ex) {
Log.i(TAG, "exception starting log manager activity " + ex.toString());
}
return true;
+ case R.id.action_report_seizure:
+ Log.i(TAG, "action_report_seizure");
+ try {
+ Intent intent = new Intent(
+ MainActivity.this,
+ ReportSeizureActivity.class);
+ this.startActivity(intent);
+ } catch (Exception ex) {
+ Log.i(TAG, "exception starting Report Seizure activity " + ex.toString());
+ }
+ return true;
case R.id.action_settings:
Log.i(TAG, "action_settings");
try {
@@ -307,7 +413,7 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
- Log.i(TAG,"onStart()");
+ Log.i(TAG, "onStart()");
mUtil.writeToSysLogFile("MainActivity.onStart()");
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
@@ -321,9 +427,9 @@ public class MainActivity extends AppCompatActivity {
if (mUtil.isServerRunning()) {
mUtil.writeToSysLogFile("MainActivity.onStart - Binding to Server");
- mUtil.bindToServer(this, mConnection);
+ mUtil.bindToServer(getApplicationContext(), mConnection);
} else {
- Log.i(TAG,"onStart() - Server Not Running");
+ Log.i(TAG, "onStart() - Server Not Running");
mUtil.writeToSysLogFile("MainActivity.onStart - Server Not Running");
}
// start timer to refresh user interface every second.
@@ -341,9 +447,9 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onStop() {
super.onStop();
- Log.i(TAG,"onStop() - unbinding from server");
+ Log.i(TAG, "onStop() - unbinding from server");
mUtil.writeToSysLogFile("MainActivity.onStop()");
- mUtil.unbindFromServer(this, mConnection);
+ mUtil.unbindFromServer(getApplicationContext(), mConnection);
mUiTimer.cancel();
}
@@ -402,7 +508,7 @@ public class MainActivity extends AppCompatActivity {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv = (TextView) findViewById(R.id.serverIpTv);
- tv.setText(getString(R.string.AccessServerAt)+" http://"
+ tv.setText(getString(R.string.AccessServerAt) + " http://"
+ mUtil.getLocalIpAddress()
+ ":8080");
tv.setBackgroundColor(okColour);
@@ -460,11 +566,17 @@ public class MainActivity extends AppCompatActivity {
// Pebble Connected Phrase - use for HR if active instead.
tv = (TextView) findViewById(R.id.pebbleTv);
if (mConnection.mSdServer.mSdData.mHRAlarmActive) {
- tv.setText(getString(R.string.HR_Equals) + mConnection.mSdServer.mSdData.mHR);
- if (mConnection.mSdServer.mSdData.mHRAlarmStanding) {
+ if (mConnection.mSdServer.mSdData.mO2Sat>0) {
+ tv.setText(getString(R.string.HR_Equals) + mConnection.mSdServer.mSdData.mHR + " bpm\n"
+ + "O2 Sat = " + mConnection.mSdServer.mSdData.mO2Sat + "%");
+ } else {
+ tv.setText(getString(R.string.HR_Equals) + mConnection.mSdServer.mSdData.mHR + " bpm\n"
+ + "O2 Sat = ---%");
+ }
+ if (mConnection.mSdServer.mSdData.mHRAlarmStanding || mConnection.mSdServer.mSdData.mO2SatAlarmStanding) {
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
- } else if (mConnection.mSdServer.mSdData.mHRFaultStanding) {
+ } else if (mConnection.mSdServer.mSdData.mHRFaultStanding || mConnection.mSdServer.mSdData.mO2SatFaultStanding) {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
} else {
@@ -507,6 +619,65 @@ public class MainActivity extends AppCompatActivity {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
+
+ ////////////////////////////////////////////////////////////
+ // Populate the Data Sharing Status Box
+ // We start off with it set to OK, then check for several different abnormal conditions
+ // in turn - the last one that is active is the one that is displayed.
+ tv = (TextView) findViewById(R.id.remoteDbTv);
+ tv.setText(getString(R.string.data_sharing_status)
+ + ": "
+ + getString(R.string.data_sharing_setup_ok));
+ tv.setBackgroundColor(okColour);
+ tv.setTextColor(okTextColour);
+
+ if (!mConnection.mSdServer.mLm.mWac.checkServerConnection()) {
+ // Problem connecting to server
+ tv = (TextView) findViewById(R.id.remoteDbTv);
+ tv.setText(getString(R.string.data_sharing_status)
+ + ": "
+ + getString(R.string.error_connecting_to_server));
+ tv.setBackgroundColor(warnColour);
+ tv.setTextColor(warnTextColour);
+ }
+
+ if (!mConnection.mSdServer.mLogDataRemoteMobile && mUtil.isMobileDataActive()) {
+ // We are on mobile internet but we are set to not upload over mobile data.
+ tv.setText(getString(R.string.data_sharing_status)
+ + ": "
+ + getString(R.string.not_updating_mobile));
+ tv.setBackgroundColor(warnColour);
+ tv.setTextColor(warnTextColour);
+ }
+
+ if (!mUtil.isNetworkConnected()) {
+ // No network connection
+ tv.setText(getString(R.string.data_sharing_status)
+ + ": "
+ + getString(R.string.not_updating_no_network));
+ tv.setBackgroundColor(warnColour);
+ tv.setTextColor(warnTextColour);
+ }
+
+ if (!mConnection.mSdServer.mLm.mWac.isLoggedIn()) {
+ // Not Logged In
+ tv.setText(getString(R.string.data_sharing_status)
+ + ": "
+ + getString(R.string.not_logged_in));
+ tv.setBackgroundColor(warnColour);
+ tv.setTextColor(warnTextColour);
+ }
+
+ if (!mConnection.mSdServer.mLogData) {
+ // Not set to share data
+ tv.setText(getString(R.string.data_sharing_status)
+ + ": "
+ + getString(R.string.not_sharing_logged_data));
+ tv.setBackgroundColor(warnColour);
+ tv.setTextColor(warnTextColour);
+ }
+
+ /////////////////////////////////////////////////////
// Set ProgressBars to show margin to alarm.
long powerPc;
if (mConnection.mSdServer.mSdData.alarmThresh != 0)
@@ -532,9 +703,9 @@ public class MainActivity extends AppCompatActivity {
specRatio = 0;
((TextView) findViewById(R.id.powerTv)).setText(getString(R.string.PowerEquals) + mConnection.mSdServer.mSdData.roiPower +
- " ("+ getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmThresh + ")");
+ " (" + getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmThresh + ")");
((TextView) findViewById(R.id.spectrumTv)).setText(getString(R.string.SpectrumRatioEquals) + specRatio +
- " ("+ getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmRatioThresh + ")");
+ " (" + getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmRatioThresh + ")");
ProgressBar pb;
Drawable pbDrawable;
@@ -589,21 +760,20 @@ public class MainActivity extends AppCompatActivity {
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.pebbleTv);
- tv.setText(getString(R.string.HR_Equals)+"---");
+ tv.setText(getString(R.string.HR_Equals) + " --- bpm\nO2 Sat = --- %");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.appTv);
- tv.setText(getString(R.string.WatchApp)+" ----");
+ tv.setText(getString(R.string.WatchApp) + " ----");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.battTv);
- tv.setText(getString(R.string.WatchBatteryEquals)+" ---%");
+ tv.setText(getString(R.string.WatchBatteryEquals) + " ---%");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
-
} else { // Not bound to server
tv = (TextView) findViewById(R.id.alarmTv);
tv.setText(R.string.Dashes);
@@ -620,17 +790,22 @@ public class MainActivity extends AppCompatActivity {
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.pebbleTv);
- tv.setText(getString(R.string.HR_Equals)+"---");
+ tv.setText(getString(R.string.HR_Equals) + "---");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.appTv);
- tv.setText(getString(R.string.WatchApp)+" -----");
+ tv.setText(getString(R.string.WatchApp) + " -----");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.battTv);
- tv.setText(getString(R.string.WatchBatteryEquals)+" ---%");
+ tv.setText(getString(R.string.WatchBatteryEquals) + " ---%");
+ tv.setBackgroundColor(warnColour);
+ tv.setTextColor(warnTextColour);
+
+ tv = (TextView) findViewById(R.id.remoteDbTv);
+ tv.setText("---");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
@@ -647,12 +822,12 @@ public class MainActivity extends AppCompatActivity {
&& (mConnection.mSdServer.mSmsTimer.mTimeLeft > 0)) {
acceptAlarmButton.setText(getString(R.string.SMSWillBeSentIn) + " " +
mConnection.mSdServer.mSmsTimer.mTimeLeft / 1000 +
- " s - "+getString(R.string.Cancel));
+ " s - " + getString(R.string.Cancel));
acceptAlarmButton.setBackgroundColor(alarmColour);
acceptAlarmButton.setEnabled(true);
} else {
acceptAlarmButton.setText(R.string.AcceptAlarm);
- acceptAlarmButton.setBackgroundColor(Color.DKGRAY);
+ acceptAlarmButton.setBackgroundColor(Color.GRAY);
if (mConnection.mBound)
if ((mConnection.mSdServer.isLatchAlarms())
|| mConnection.mSdServer.mSdData.mFallActive) {
@@ -673,15 +848,17 @@ public class MainActivity extends AppCompatActivity {
if (mConnection.mBound)
if (mConnection.mSdServer.isAudibleCancelled()) {
cancelAudibleButton.setText(getString(R.string.AudibleAlarmsCancelledFor)
- + mConnection.mSdServer.
+ + " " + mConnection.mSdServer.
cancelAudibleTimeRemaining()
- + " sec."
- + getString(R.string.PressToReEnable));
+ + " sec");
+ cancelAudibleButton.setEnabled(true);
} else {
if (mConnection.mSdServer.mAudibleAlarm) {
cancelAudibleButton.setText(R.string.CancelAudibleAlarms);
+ cancelAudibleButton.setEnabled(true);
} else {
cancelAudibleButton.setText(R.string.AudibleAlarmsOff);
+ cancelAudibleButton.setEnabled(false);
}
}
@@ -696,17 +873,16 @@ public class MainActivity extends AppCompatActivity {
ArrayList xVals = new ArrayList();
ArrayList yBarVals = new ArrayList();
for (int i = 0; i < 10; i++) {
- xVals.add(i+"-"+(i+1)+" Hz");
+ xVals.add(i + "-" + (i + 1) + " Hz");
if (mConnection.mSdServer != null) {
yBarVals.add(new BarEntry(mConnection.mSdServer.mSdData.simpleSpec[i], i));
- }
- else {
- yBarVals.add(new BarEntry(i,i));
+ } else {
+ yBarVals.add(new BarEntry(i, i));
}
}
// create a dataset and give it a type
- BarDataSet barDataSet = new BarDataSet(yBarVals,"Spectrum");
+ BarDataSet barDataSet = new BarDataSet(yBarVals, "Spectrum");
try {
int[] barColours = new int[10];
for (int i = 0; i < 10; i++) {
@@ -718,20 +894,20 @@ public class MainActivity extends AppCompatActivity {
}
}
barDataSet.setColors(barColours);
- } catch (NullPointerException e){
- Log.e(TAG,"Null pointer exception setting bar colours");
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Null pointer exception setting bar colours");
}
barDataSet.setBarSpacePercent(20f);
barDataSet.setBarShadowColor(Color.WHITE);
- BarData barData = new BarData(xVals,barDataSet);
+ BarData barData = new BarData(xVals, barDataSet);
barData.setValueFormatter(new ValueFormatter() {
- @Override
- public String getFormattedValue(float v) {
- DecimalFormat format = new DecimalFormat("####");
- return format.format(v);
- }
- });
- mChart.setData(barData);
+ @Override
+ public String getFormattedValue(float v) {
+ DecimalFormat format = new DecimalFormat("####");
+ return format.format(v);
+ }
+ });
+ mChart.setData(barData);
// format the axes
XAxis xAxis = mChart.getXAxis();
@@ -764,7 +940,7 @@ public class MainActivity extends AppCompatActivity {
try {
mChart.getLegend().setEnabled(false);
} catch (NullPointerException e) {
- Log.e(TAG,"Null Pointer Exception setting legend");
+ Log.e(TAG, "Null Pointer Exception setting legend");
}
mChart.invalidate();
@@ -775,14 +951,14 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onPause() {
super.onPause();
- Log.i(TAG,"onPause()");
+ Log.i(TAG, "onPause()");
mUtil.writeToSysLogFile("MainActivity.onPause()");
}
@Override
protected void onResume() {
super.onResume();
- Log.i(TAG,"onResume()");
+ Log.i(TAG, "onResume()");
mUtil.writeToSysLogFile("MainActivity.onResume()");
}
@@ -795,16 +971,69 @@ public class MainActivity extends AppCompatActivity {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(R.drawable.icon_24x24);
builder.setTitle("OpenSeizureDetector V" + versionName);
+ builder.setNeutralButton(getString(R.string.closeBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ builder.setPositiveButton("Privacy Policy", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ String url = OsdUtil.PRIVACY_POLICY_URL;
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ startActivity(i);
+ dialog.cancel();
+ }
+ });
+ builder.setNegativeButton("Data Sharing", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ String url = OsdUtil.DATA_SHARING_URL;
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ startActivity(i);
+ dialog.cancel();
+ }
+ });
builder.setView(aboutView);
builder.create();
builder.show();
}
+ private void showDataSharingDialog() {
+ mUtil.writeToSysLogFile("MainActivity.showDataSharingDialog()");
+ View aboutView = getLayoutInflater().inflate(R.layout.data_sharing_dialog_layout, null, false);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setIcon(R.drawable.datasharing_fault_24x24);
+ builder.setTitle("OpenSeizureDetector Data Sharing");
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.login), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.i(TAG, "dataSharingDialog.positiveButton.onClick()");
+ try {
+ Intent i = new Intent(
+ MainActivity.this,
+ AuthenticateActivity.class);
+ mContext.startActivity(i);
+ } catch (Exception ex) {
+ Log.i(TAG, "exception starting activity " + ex.toString());
+ }
+
+ }
+ });
+ builder.setView(aboutView);
+ builder.create();
+ builder.show();
+ }
+
+
static class ResponseHandler extends Handler {
@Override
public void handleMessage(Message message) {
Log.i(TAG, "Message=" + message.toString());
}
- }
+ }
}
diff --git a/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java b/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java
index 1a5f866..7d4cfed 100644
--- a/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java
+++ b/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java
@@ -1,6 +1,5 @@
package uk.org.openseizuredetector;
-import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -11,7 +10,8 @@ import android.os.Environment;
import android.os.Looper;
import android.os.StatFs;
import android.util.Log;
-import android.view.WindowManager;
+
+import androidx.appcompat.app.AlertDialog;
import java.io.File;
import java.io.PrintWriter;
diff --git a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java
index 6520b20..972e053 100644
--- a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java
+++ b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java
@@ -27,93 +27,75 @@ package uk.org.openseizuredetector;
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
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.ApplicationInfo;
-import android.content.pm.FeatureInfo;
-import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
-import android.os.IBinder;
-import android.os.UserHandle;
import android.preference.PreferenceManager;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
import android.text.format.Time;
import android.util.Log;
-import android.view.MenuItem;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
import org.apache.http.conn.util.InetAddressUtils;
import java.io.File;
import java.io.FileWriter;
-import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
-import java.util.AbstractList;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.Date;
import java.util.Enumeration;
-import java.util.List;
-import java.util.RandomAccess;
-import java.util.concurrent.RunnableFuture;
+import java.util.HashMap;
+import java.util.function.Consumer;
/**
* OsdUtil - OpenSeizureDetector Utilities
* Deals with starting and stopping the background service and binding to it to receive data.
*/
-public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallback {
+public class OsdUtil {
+ public final static String PRIVACY_POLICY_URL = "https://www.openseizuredetector.org.uk/?page_id=1415";
+ public final static String DATA_SHARING_URL = "https://www.openseizuredetector.org.uk/?page_id=1818";
+
private final String SYSLOG = "SysLog";
private final String ALARMLOG = "AlarmLog";
private final String DATALOG = "DataLog";
- private final String[] REQUIRED_PERMISSIONS = {
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.WAKE_LOCK,
- };
-
- private final String[] SMS_PERMISSIONS = {
- Manifest.permission.SEND_SMS,
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.READ_PHONE_STATE,
- };
-
-
/**
* Based on http://stackoverflow.com/questions/7440473/android-how-to-check-if-the-intent-service-is-still-running-or-has-stopped-running
*/
- private Context mContext;
+ private static Context mContext;
private Handler mHandler;
- private String TAG = "OsdUtil";
+ private static String TAG = "OsdUtil";
private boolean mLogAlarms = true;
private boolean mLogSystem = true;
private boolean mLogData = true;
private boolean mPermissionsRequested = false;
private boolean mSMSPermissionsRequested = false;
+ private static final String mSysLogTableName = "SysLog";
+ //private LogManager mLm;
+ static private SQLiteDatabase mSysLogDb = null; // SQLite Database for data and log entries.
+ private final static Long mMinPruneInterval = new Long(5 * 60 * 1000); // minimum time between syslog pruning is 5 minutes
+ private static Long mLastPruneMillis = new Long(0); // Record of the last time we pruned the syslog db.
private static int mNbound = 0;
@@ -121,6 +103,9 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
mContext = context;
mHandler = handler;
updatePrefs();
+ //Log.i(TAG,"Creating Log Manager instance");
+ //mLm = new LogManager(mContext,false,false,null,0,0,false,0);
+ openDb();
writeToSysLogFile("OsdUtil() - initialised");
}
@@ -136,8 +121,8 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
try {
mLogAlarms = SP.getBoolean("LogAlarms", true);
Log.v(TAG, "updatePrefs() - mLogAlarms = " + mLogAlarms);
- mLogData = SP.getBoolean("LogData", false);
- Log.v(TAG, "updatePrefs() - mLogData = " + mLogData);
+ mLogData = SP.getBoolean("LogData", true);
+ Log.v(TAG, "OsdUtil.updatePrefs() - mLogData = " + mLogData);
mLogSystem = SP.getBoolean("LogSystem", true);
Log.v(TAG, "updatePrefs() - mLogSystem = " + mLogSystem);
@@ -170,7 +155,7 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
}
}
if (nServers != 0) {
- Log.v(TAG, "isServerRunning() - " + nServers + " instances are running");
+ //Log.v(TAG, "isServerRunning() - " + nServers + " instances are running");
return true;
} else
return false;
@@ -199,7 +184,7 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
* Stop the SdServer service
*/
public void stopServer() {
- Log.d(TAG, "OsdUtil.stopServer() - stopping Server... - mNbound=" + mNbound);
+ Log.i(TAG, "OsdUtil.stopServer() - stopping Server... - mNbound=" + mNbound);
writeToSysLogFile("stopserver() - stopping server");
// then send an Intent to stop the service.
@@ -213,7 +198,7 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
/**
* bind an activity to to an already running server.
*/
- public void bindToServer(Activity activity, SdServiceConnection sdServiceConnection) {
+ public void bindToServer(Context activity, SdServiceConnection sdServiceConnection) {
Log.i(TAG, "OsdUtil.bindToServer() - binding to SdServer");
writeToSysLogFile("bindToServer() - binding to SdServer");
Intent intent = new Intent(sdServiceConnection.mContext, SdServer.class);
@@ -225,7 +210,7 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
/**
* unbind an activity from server
*/
- public void unbindFromServer(Activity activity, SdServiceConnection sdServiceConnection) {
+ public void unbindFromServer(Context activity, SdServiceConnection sdServiceConnection) {
// unbind this activity from the service if it is bound.
if (sdServiceConnection.mBound) {
Log.i(TAG, "unbindFromServer() - unbinding");
@@ -301,6 +286,7 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
// return true if we are using mobile data, otherwise return false
ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
+ if (activeNetwork == null) return false;
if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
return true;
} else {
@@ -312,7 +298,11 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
// return true if we have a network connection, otherwise false.
ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
- return (activeNetwork.isConnected());
+ if (activeNetwork != null) {
+ return (activeNetwork.isConnected());
+ } else {
+ return (false);
+ }
}
/**
@@ -331,16 +321,18 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
/**
- * Write a message to the system log file, provided mLogSystem is true.
+ * Write a message to the system log database.
*
* @param msgStr
*/
- public void writeToSysLogFile(String msgStr) {
- if (mLogSystem)
- writeToLogFile(SYSLOG, msgStr);
- else
- Log.v(TAG, "writeToSysLogFile - mLogSystem False so not writing");
+ public void writeToSysLogFile(String msgStr,String logType) {
+ writeLogEntryToLocalDb(msgStr,logType);
}
+ public void writeToSysLogFile(String msgStr) {
+ writeLogEntryToLocalDb(msgStr,"v");
+ }
+
+
/**
* Write a message to the alarm log file, provided mLogAlarms is true.
@@ -462,82 +454,289 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
}
}
- public boolean arePermissionsOK() {
- boolean allOk = true;
- Log.v(TAG, "arePermissionsOK");
- for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) {
- if (ContextCompat.checkSelfPermission(mContext, REQUIRED_PERMISSIONS[i])
- != PackageManager.PERMISSION_GRANTED) {
- Log.i(TAG, REQUIRED_PERMISSIONS[i] + " Permission Not Granted");
- allOk = false;
+ /**
+ * string2date - returns a Date object represented by string dateStr
+ * It first attempts to parse it as a long integer, in which case it is assumed to
+ * be a unix timestamp.
+ * If that fails it attempts to parse it as yyyy-MM-dd'T'HH:mm:ss'Z' format.
+ * @param dateStr String reprenting a date
+ * @return Date object or null if parsing fails.
+ */
+ public Date string2date(String dateStr) {
+ Date dataTime = null;
+ try {
+ Long tstamp = Long.parseLong(dateStr);
+ dataTime = new Date(tstamp);
+ } catch (NumberFormatException e) {
+ Log.v(TAG, "remoteEventsAdapter.getView: Error Parsing dataDate as Long: " + e.getLocalizedMessage()+" trying as string");
+ try {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ dataTime = dateFormat.parse(dateStr);
+ } catch (ParseException e2) {
+ Log.e(TAG, "remoteEventsAdapter.getView: Error Parsing dataDate " + e2.getLocalizedMessage());
+ dataTime = null;
}
}
- return allOk;
- }
-
- public boolean areSMSPermissionsOK() {
- boolean allOk = true;
- Log.v(TAG, "areSMSPermissionsOK()");
- for (int i = 0; i < SMS_PERMISSIONS.length; i++) {
- if (ContextCompat.checkSelfPermission(mContext, SMS_PERMISSIONS[i])
- != PackageManager.PERMISSION_GRANTED) {
- Log.i(TAG, SMS_PERMISSIONS[i] + " Permission Not Granted");
- allOk = false;
- }
- }
- return allOk;
+ return(dataTime);
}
- public void requestPermissions(Activity activity) {
- if (mPermissionsRequested) {
- Log.i(TAG, "requestPermissions() - request already sent - not doing anything");
- } else {
- Log.i(TAG, "requestPermissions() - requesting permissions");
- for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) {
- if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
- REQUIRED_PERMISSIONS[i])) {
- Log.i(TAG, "shouldShowRationale for permission" + REQUIRED_PERMISSIONS[i]);
+ public final int ALARM_STATUS_WARNING = 1;
+ public final int ALARM_STATUS_ALARM = 2;
+ public final int ALARM_STATUS_FALL = 3;
+ public final int ALARM_STATUS_MANUAL = 5;
+
+ public String alarmStatusToString(int eventAlarmStatus) {
+ String retVal = "Unknown";
+ switch (eventAlarmStatus) {
+ case ALARM_STATUS_WARNING: // Warning
+ retVal = "WARNING";
+ break;
+ case ALARM_STATUS_ALARM: // alarm
+ retVal = "ALARM";
+ break;
+ case ALARM_STATUS_FALL: // fall
+ retVal = "FALL";
+ break;
+ case ALARM_STATUS_MANUAL: // Manual alarm
+ retVal = "MANUAL ALARM";
+ break;
+
+ }
+ return(retVal);
+ }
+
+ private static boolean openDb() {
+ Log.d(TAG, "openDb");
+ try {
+ if (mSysLogDb == null) {
+ Log.i(TAG,"openDb: mSysLogDb is null - initialising");
+ mSysLogDb = new OsdSysLogHelper(mContext).getWritableDatabase();
+ } else {
+ Log.i(TAG,"openDb: mSysLogDb has been initialised already so not doing anything");
+ }
+ if (!checkTableExists(mSysLogDb, mSysLogTableName)) {
+ Log.e(TAG, "ERROR - Table "+mSysLogTableName+" does not exist");
+ return false;
+ } else {
+ Log.d(TAG, "table " + mSysLogTableName + " exists ok");
+ }
+ } catch (SQLException e) {
+ Log.e(TAG, "Failed to open Database: " + e.toString());
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean checkTableExists(SQLiteDatabase osdDb, String osdTableName) {
+ Cursor c = null;
+ boolean tableExists = false;
+ Log.d(TAG, "checkTableExists()");
+ try {
+ c = osdDb.query(osdTableName, null,
+ null, null, null, null, null);
+ tableExists = true;
+ c.close();
+ } catch (Exception e) {
+ Log.d(TAG, osdTableName + " doesn't exist :(((");
+ }
+ return tableExists;
+ }
+
+ /**
+ * Write syslog string to local database
+ * FIXME - I am sure we should not be using raw SQL Srings to do this!
+ */
+ public void writeLogEntryToLocalDb(String logText, String statusVal) {
+ Log.v(TAG, "writeLogEntryToLocalDb()");
+ Date curDate = new Date();
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ String dateStr = dateFormat.format(curDate);
+ String SQLStr = "SQLStr";
+
+ try {
+ SQLStr = "INSERT INTO " + mSysLogTableName
+ + "(dataTime, logLevel, dataJSON, uploaded)"
+ + " VALUES("
+ + "'" + dateStr + "',"
+ + DatabaseUtils.sqlEscapeString(statusVal) + ","
+ + DatabaseUtils.sqlEscapeString(logText) + ","
+ + 0
+ + ")";
+ mSysLogDb.execSQL(SQLStr);
+ Log.v(TAG, "syslog entry written to database: "+logText);
+ pruneSysLogDb();
+
+ } catch (SQLException e) {
+ Log.e(TAG, "writeLogEngryToLocalDb(): Error Writing Data: " + e.toString());
+ Log.e(TAG, "SQLStr was " + SQLStr);
+ }
+
+ }
+
+ /**
+ * Return an array list of objects representing the syslog entries in the database by calling the specified callback function.
+ *
+ * @return True on successful start or false if call fails.
+ */
+ public boolean getSysLogList(Consumer>> callback) {
+ Log.v(TAG, "getSysLogList");
+ ArrayList> eventsList = new ArrayList<>();
+
+ String whereClause = "";
+ String[] whereArgs = {};
+ String[] columns = {"*"};
+ new SelectQueryTask(mSysLogTableName, columns, null, null,
+ null, null, "dataTime DESC", (Cursor cursor) -> {
+ Log.v(TAG, "getSysLogList - returned " + cursor);
+ if (cursor != null) {
+ Log.v(TAG, "getSysLogList - returned " + cursor.getCount() + " records");
+ while (!cursor.isAfterLast()) {
+ HashMap event = new HashMap<>();
+ //event.put("id", cursor.getString(cursor.getColumnIndex("id")));
+ event.put("dataTime", cursor.getString(cursor.getColumnIndex("dataTime")));
+ String loglevel = cursor.getString(cursor.getColumnIndex("logLevel"));
+ event.put("loglevel", loglevel);
+ event.put("dataJSON", cursor.getString(cursor.getColumnIndex("dataJSON")));
+ //event.put("dataJSON", cursor.getString(cursor.getColumnIndex("dataJSON")));
+ eventsList.add(event);
+ cursor.moveToNext();
}
}
- ActivityCompat.requestPermissions(activity,
- REQUIRED_PERMISSIONS,
- 42);
- mPermissionsRequested = true;
+ callback.accept(eventsList);
+ }).execute();
+ return (true);
+ }
+
+ /**
+ * Executes the sqlite query (=SELECT statement)
+ * Use as new SelectQueryTask(xxx,xxx,xx,xxxx).execute()
+ *
+ */
+ static private class SelectQueryTask extends AsyncTask {
+ // Based on https://stackoverflow.com/a/21120199/2104584
+ String mTable;
+ String[] mColumns;
+ String mSelection;
+ String[] mSelectionArgs;
+ String mGroupBy;
+ String mHaving;
+ String mOrderBy;
+ Consumer mCallback;
+
+ //query(String table, String[] columns, String selection, String[] selectionArgs,
+ // String groupBy, String having, String orderBy)
+ SelectQueryTask(String table, String[] columns, String selection, String[] selectionArgs,
+ String groupBy, String having, String orderBy, Consumer callback) {
+ // list all the parameters like in normal class define
+ this.mTable = table;
+ this.mColumns = columns;
+ this.mSelection = selection;
+ this.mSelectionArgs = selectionArgs;
+ this.mGroupBy = groupBy;
+ this.mHaving = having;
+ this.mOrderBy = orderBy;
+ this.mCallback = callback;
+
+ }
+
+ @Override
+ protected Cursor doInBackground(Void... params) {
+ Log.v(TAG, "runSelect.doInBackground()");
+ Log.v(TAG, "SelectQueryTask.doInBackground: mTable=" + mTable + ", mColumns=" + Arrays.toString(mColumns)
+ + ", mSelection=" + mSelection + ", mSelectionArgs=" + Arrays.toString(mSelectionArgs) + ", mGroupBy=" + mGroupBy
+ + ", mHaving =" + mHaving + ", mOrderBy=" + mOrderBy);
+
+ try {
+ Cursor resultSet = mSysLogDb.query(mTable, mColumns, mSelection,
+ mSelectionArgs, mGroupBy, mHaving, mOrderBy);
+ resultSet.moveToFirst();
+ return (resultSet);
+ } catch (SQLException e) {
+ Log.e(TAG, "SelectQueryTask.doInBackground(): Error selecting Data: " + e.toString());
+ return (null);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "SelectQueryTask.doInBackground(): Illegal Argument Exception: " + e.toString());
+ return (null);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final Cursor result) {
+ mCallback.accept(result);
}
}
- public void requestSMSPermissions(Activity activity) {
- if (mSMSPermissionsRequested) {
- Log.i(TAG, "requestSMSPermissions() - request already sent - not doing anything");
+
+
+ /**
+ * pruneSysLogDb() removes data that is older than 7 days
+ */
+ public int pruneSysLogDb() {
+ //Log.v(TAG, "pruneSysLogDb()");
+ int retVal;
+ long currentDateMillis = new Date().getTime();
+ if (currentDateMillis > mLastPruneMillis + mMinPruneInterval) {
+ mLastPruneMillis = currentDateMillis;
+ // FIXME - change this to something sensible like 7 days after testing
+ long endDateMillis = currentDateMillis - 5 * 60 * 1000;
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String endDateStr = dateFormat.format(new Date(endDateMillis));
+ Log.v(TAG, "pruneSysLogDb - endDateStr=" + endDateStr);
+ try {
+ String selectStr = "DataTime<=?";
+ String[] selectArgs = {endDateStr};
+ retVal = mSysLogDb.delete(mSysLogTableName, selectStr, selectArgs);
+ } catch (Exception e) {
+ Log.e(TAG, "Error deleting log entries" + e.toString());
+ retVal = 0;
+ }
+ if (retVal > 0) {
+ Log.v(TAG, String.format("pruneSysLogDb() - deleted %d records", retVal));
+ }
+ return (retVal);
} else {
- Log.i(TAG, "requestSMSPermissions() - requesting permissions");
- for (int i = 0; i < SMS_PERMISSIONS.length; i++) {
- if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
- SMS_PERMISSIONS[i])) {
- Log.i(TAG, "shouldShowRationale for permission" + SMS_PERMISSIONS[i]);
- }
- }
- ActivityCompat.requestPermissions(activity,
- SMS_PERMISSIONS,
- 43);
- mSMSPermissionsRequested = true;
+ return (0);
}
}
- @Override
- public void onRequestPermissionsResult(int requestCode,
- String permissions[], int[] grantResults) {
- Log.i(TAG, "onRequestPermissionsResult - Permission" + permissions + " = " + grantResults);
- showToast(mContext.getString(R.string.RestartingServerMsg));
- stopServer();
- // Wait 0.1 second to give the server chance to shutdown, then re-start it
- mHandler.postDelayed(new Runnable() {
- public void run() {
- startServer();
- }
- }, 100);
+ public static class OsdSysLogHelper extends SQLiteOpenHelper {
+ // If you change the database schema, you must increment the database version.
+ public static final int DATABASE_VERSION = 1;
+ public static final String DATABASE_NAME = "OsdSysLog.db";
+ private static final String TAG = "LogManager.OsdSysLogHelper";
+ public OsdSysLogHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ Log.d(TAG, "OsdSysLogHelper constructor");
+ }
+
+ public void onCreate(SQLiteDatabase db) {
+ Log.i(TAG, "onCreate - TableName=" + mSysLogTableName);
+ String SQLStr = "CREATE TABLE IF NOT EXISTS " + mSysLogTableName + "("
+ + "id INTEGER PRIMARY KEY,"
+ + "dataTime DATETIME,"
+ + "logLevel TEXT,"
+ + "dataJSON TEXT,"
+ + "uploaded INT"
+ + ");";
+ db.execSQL(SQLStr);
+ }
+
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // This database is only a cache for online data, so its upgrade policy is
+ // to simply to discard the data and start over
+ Log.i(TAG,"onUpgrade()");
+ db.execSQL("Drop table if exists " + mSysLogTableName + ";");
+ onCreate(db);
+ }
+
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.i(TAG,"onDowngrade()");
+ onUpgrade(db, oldVersion, newVersion);
+ }
}
+
}
diff --git a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java
index 9620c7c..8d0227f 100644
--- a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java
+++ b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java
@@ -75,7 +75,8 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
.getDefaultSharedPreferences(this.getApplicationContext());
String dataSourceStr = SP.getString("DataSource", "Pebble");
Log.i(TAG, "onBuildHeaders DataSource = " + dataSourceStr);
- Boolean advancedMode = SP.getBoolean("advancedMode", false);
+ //Boolean advancedMode = SP.getBoolean("advancedMode", false);
+ Boolean advancedMode = true;
Log.i(TAG, "onBuildHeaders advancedMode = " + advancedMode);
if (advancedMode) {
@@ -120,7 +121,7 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
} else {
titleStr = getResources().getString(h.titleRes);
}
- Log.v(TAG, "i=" + i + ": found - " + titleStr + " looking for "+ getString(R.string.basic_settings_title));
+ Log.v(TAG, "i=" + i + ": found - " + titleStr + " looking for " + getString(R.string.basic_settings_title));
if (!titleStr.equals(getString(R.string.basic_settings_title))) {
Log.v(TAG, "an Advanced Mode Header, so removing it....");
target.remove(i);
@@ -143,18 +144,22 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
Log.i(TAG, "SharedPreference " + s + " Changed.");
+ // if we have enabled the SMS alarm, we may need extra permissions approving. This is handled in
+ // StartUpActivity, so we exit this activity and start start-up activity.
if (s.equals("SMSAlarm")) {
if (sharedPreferences.getBoolean("SMSAlarm", false) == true) {
- if (mUtil.areSMSPermissionsOK() == false) {
- Log.i(TAG, "onSharedPreferenceChanged(): SMS Alarm Enabled - Requesting Permissions");
- mUtil.requestSMSPermissions(this);
- } else {
- Log.i(TAG, "OnSharedPreferenceCHanged(): SMS Permissions already granted, doing nothing");
- }
+ Log.i(TAG, "onSharedPreferenceChanged(): SMS Alarm Enabled - Restarting start-up activity to check permissions");
+ Intent i;
+ i = new Intent(this, StartupActivity.class);
+ startActivity(i);
+ Log.i(TAG,"onSharedPreferenceChanged() - finishing PrefActivity");
+ finish();
+ return;
} else {
Log.i(TAG, "OnSharedPreferenceChanged(): SMS Alarm disabled so do not need permissions");
}
}
+ // For all other preference changes we just restart SdServer so it is not as alarming for the user!
//mUtil.showToast("Setting " + s + " Changed - restarting server");
mPrefChanged = true;
mUtil.stopServer();
@@ -213,6 +218,7 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
protected void onStop() {
super.onStop();
mUtil.writeToSysLogFile("PrefActvity.onStop()");
+ Log.i(TAG,"onStop()");
}
/**
@@ -276,6 +282,17 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
}
}
+ public static class LoggingPrefsFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.logging_prefs);
+ }
+ }
+
+
public static class SeizureDetectorPrefsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
diff --git a/app/src/main/java/uk/org/openseizuredetector/RemoteDbActivity.java b/app/src/main/java/uk/org/openseizuredetector/RemoteDbActivity.java
new file mode 100644
index 0000000..0060bbc
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/RemoteDbActivity.java
@@ -0,0 +1,226 @@
+package uk.org.openseizuredetector;
+
+//import androidx.appcompat.app.AppCompatActivity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import androidx.appcompat.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.HashMap;
+
+public class RemoteDbActivity extends AppCompatActivity {
+ private String TAG = "RemoteDbActivity";
+ private Context mContext;
+ private UiTimer mUiTimer;
+ private LogManager mLm;
+ private WebView mWebView;
+ private SdServiceConnection mConnection;
+ private OsdUtil mUtil;
+ final Handler serverStatusHandler = new Handler();
+ private String TOKEN_ID = "webApiAuthToken";
+ private String mRemtoteUrl = "https://osdapi.ddns.net/";
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+ mContext = this;
+ setContentView(R.layout.activity_remote_db);
+ mUtil = new OsdUtil(getApplicationContext(), serverStatusHandler);
+ mConnection = new SdServiceConnection(getApplicationContext());
+ mUtil.bindToServer(getApplicationContext(), mConnection);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ String remoteUrl = extras.getString("url");
+ mRemtoteUrl = remoteUrl;
+ Log.d(TAG, "onCreate - mRemoteUrl=" + mRemtoteUrl);
+ }
+
+ waitForConnection();
+
+ //mLm= new LogManager(mContext);
+
+ Button authBtn =
+ (Button) findViewById(R.id.auth_button);
+ authBtn.setOnClickListener(onAuth);
+ //Button pruneBtn =
+ // (Button) findViewById(R.id.pruneDatabaseBtn);
+ //pruneBtn.setOnClickListener(onPruneBtn);
+
+ mWebView = (WebView) findViewById(R.id.remote_db_webview);
+ WebSettings webSettings = mWebView.getSettings();
+ webSettings.setJavaScriptEnabled(true);
+
+ }
+
+ private void waitForConnection() {
+ // We want the UI to update as soon as it is displayed, but it takes a finite time for
+ // the mConnection to bind to the service, so we delay half a second to give it chance
+ // to connect before trying to update the UI for the first time (it happens again periodically using the uiTimer)
+ if (mConnection.mBound) {
+ Log.d(TAG, "waitForConnection - Bound!");
+ initialiseServiceConnection();
+ } else {
+ Log.v(TAG, "waitForConnection - waiting...");
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ waitForConnection();
+ }
+ }, 100);
+ }
+ }
+
+ private void initialiseServiceConnection() {
+ mLm = mConnection.mSdServer.mLm;
+ mWebView.loadUrl(mRemtoteUrl, getAuthHeaders());
+ //mWac = mConnection.mSdServer.mLm.mWac;
+ }
+
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ waitForConnection();
+ updateUi();
+ //startUiTimer();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ stopUiTimer();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ startUiTimer();
+ }
+
+ private HashMap getAuthHeaders() {
+ HashMap headersMap = new HashMap<>();
+ String authToken = getAuthToken();
+ headersMap.put("Authorization", "Token "+authToken);
+ return (headersMap);
+ }
+
+ public String getAuthToken() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ String authToken = prefs.getString(TOKEN_ID, null);
+ return authToken;
+ }
+
+ private void updateUi() {
+ Log.v(TAG,"updateUi()");
+ TextView tv;
+ Button btn;
+ // Local Database Information
+ //tv = (TextView)findViewById(R.id.num_local_events_tv);
+ //int eventCount = 0;
+ //tv.setText(String.format("%d",eventCount));
+ //tv = (TextView)findViewById(R.id.num_local_datapoints_tv);
+ //int datapointsCount = 0;
+ //tv.setText(String.format("%d",datapointsCount));
+
+
+
+ // Remote Database Information
+ tv = (TextView)findViewById(R.id.authStatusTv);
+ btn = (Button)findViewById(R.id.auth_button);
+ if (mLm != null) {
+ if (mLm.mWac.isLoggedIn()) {
+ tv.setText("Authenticated");
+ btn.setText("Log Out");
+ } else {
+ tv.setText("NOT AUTHENTICATED");
+ btn.setText("Log In");
+ }
+ }
+ }
+
+ View.OnClickListener onAuth =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onAuth");
+ Intent i;
+ i =new Intent(mContext, AuthenticateActivity.class);
+ startActivity(i);
+ }
+ };
+ View.OnClickListener onPruneBtn =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onPruneBtn");
+ mLm.pruneLocalDb();
+ }
+ };
+
+
+ /*
+ * Start the timer that will upload data to the remote server after a given period.
+ */
+ private void startUiTimer() {
+ if (mUiTimer != null) {
+ Log.v(TAG, "startRemoteLogTimer -timer already running - cancelling it");
+ mUiTimer.cancel();
+ mUiTimer = null;
+ }
+ Log.v(TAG, "startRemoteLogTimer() - starting RemoteLogTimer");
+ mUiTimer =
+ new UiTimer(1000, 1000);
+ mUiTimer.start();
+ }
+
+
+ /*
+ * Cancel the remote logging timer to prevent attempts to upload to remote database.
+ */
+ public void stopUiTimer() {
+ if (mUiTimer != null) {
+ Log.v(TAG, "stopRemoteLogTimer(): cancelling Remote Log timer");
+ mUiTimer.cancel();
+ mUiTimer = null;
+ }
+ }
+
+ /**
+ * Upload recorded data to the remote database periodically.
+ */
+ private class UiTimer extends CountDownTimer {
+ public UiTimer(long startTime, long interval) {
+ super(startTime, interval);
+ }
+
+ @Override
+ public void onTick(long l) {
+ // Do Nothing
+ }
+
+ @Override
+ public void onFinish() {
+ Log.v(TAG, "UiTimer - onFinish - Updating UI");
+ updateUi();
+ // Restart this timer.
+ start();
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java b/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java
new file mode 100644
index 0000000..c626114
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java
@@ -0,0 +1,427 @@
+package uk.org.openseizuredetector;
+
+//import androidx.appcompat.app.AppCompatActivity;
+
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.DatePicker;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.TimePicker;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * ReportSeizureActivity - Allows the user to report a seizure manually, which is saved in the database for
+ * future analysis - particularlly useful if OpenSeizureDetector did not detect the seizure automatically as this
+ * will ensure the data for the missed seizure is saved.
+ * Based on: https://www.journaldev.com/9976/android-date-time-picker-dialog
+ */
+public class ReportSeizureActivity extends AppCompatActivity {
+ private String TAG = "ReportSeizureActivity";
+ private Context mContext;
+ private UiTimer mUiTimer;
+ private LogManager mLm;
+ private WebApiConnection mWac;
+
+ private int mYear, mMonth, mDay, mHour, mMinute;
+ private String mMsg = "Messages";
+ private SdServiceConnection mConnection;
+ private OsdUtil mUtil;
+ final Handler serverStatusHandler = new Handler();
+ private List mEventTypesList = null;
+ private HashMap> mEventSubTypesHashMap = null;
+ private String mEventTypeStr = null;
+ private String mEventSubTypeStr = null;
+ private String mEventNotes = "";
+ private RadioGroup mEventTypeRg;
+ private boolean mRedrawEventSubTypesList = false;
+ private boolean mRedrawEventTypesList = false;
+ private RadioGroup mEventSubTypeRg;
+ private boolean mEventSubTypesListChanged = false;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+ mContext = this;
+ mUtil = new OsdUtil(this, serverStatusHandler);
+ if (!mUtil.isServerRunning()) {
+ mUtil.showToast(getString(R.string.error_server_not_running));
+ finish();
+ return;
+ }
+ mContext = this;
+ mConnection = new SdServiceConnection(getApplicationContext());
+
+ setContentView(R.layout.activity_report_seizure);
+
+ mEventTypeRg = findViewById(R.id.eventTypeRg);
+ mEventTypeRg.setOnCheckedChangeListener(onEventTypeChange);
+ mEventSubTypeRg = findViewById(R.id.eventSubTypeRg);
+ mEventSubTypeRg.setOnCheckedChangeListener(onEventSubTypeChange);
+
+ Button okBtn =
+ (Button) findViewById(R.id.loginBtn);
+ okBtn.setOnClickListener(onOk);
+
+ Button cancelBtn =
+ (Button) findViewById(R.id.cancelBtn);
+ cancelBtn.setOnClickListener(onCancel);
+
+ Button setDateBtn =
+ (Button) findViewById(R.id.select_date_button);
+ setDateBtn.setOnClickListener(onSelectDate);
+
+ Button setTimeBtn =
+ (Button) findViewById(R.id.select_time_button);
+ setTimeBtn.setOnClickListener(onSelectTime);
+
+ // Get Current Date
+ final Calendar c = Calendar.getInstance();
+ mYear = c.get(Calendar.YEAR);
+ mMonth = c.get(Calendar.MONTH);
+ mDay = c.get(Calendar.DAY_OF_MONTH);
+ mHour = c.get(Calendar.HOUR_OF_DAY);
+ mMinute = c.get(Calendar.MINUTE);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mUtil.bindToServer(getApplicationContext(), mConnection);
+ waitForConnection();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ stopUiTimer();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ //startUiTimer();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mUtil.unbindFromServer(getApplicationContext(), mConnection);
+ }
+
+ private void waitForConnection() {
+ // We want the UI to update as soon as it is displayed, but it takes a finite time for
+ // the mConnection to bind to the service, so we delay half a second to give it chance
+ // to connect before trying to update the UI for the first time (it happens again periodically using the uiTimer)
+ if (mConnection.mBound) {
+ Log.v(TAG, "waitForConnection - Bound!");
+ initialiseServiceConnection();
+ } else {
+ Log.v(TAG, "waitForConnection - waiting...");
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ waitForConnection();
+ }
+ }, 100);
+ }
+ }
+
+ private void initialiseServiceConnection() {
+ mLm = mConnection.mSdServer.mLm;
+ mWac = mConnection.mSdServer.mLm.mWac;
+
+ if (mWac.isLoggedIn()) {
+
+ // Retrieve the JSONObject containing the standard event types.
+ // Note this obscure syntax is to avoid having to create another interface, so it is worth it :)
+ // See https://medium.com/@pra4mesh/callback-function-in-java-20fa48b27797
+ mWac.getEventTypes(new WebApiConnection.JSONObjectCallback() {
+ @Override
+ public void accept(JSONObject eventTypesObj) {
+ Log.v(TAG, "initialiseServiceConnection().onEventTypesReceived");
+ if (eventTypesObj == null) {
+ Log.e(TAG, "initialiseServiceConnection().getEventTypes Callback: Error Retrieving event types");
+ mUtil.showToast("Error Retrieving Event Types from Server - Please Try Again Later!");
+ } else {
+ Iterator keys = eventTypesObj.keys();
+ mEventTypesList = new ArrayList();
+ mEventSubTypesHashMap = new HashMap>();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Log.v(TAG, "initialiseServiceConnection().getEventTypes Callback: key=" + key);
+ mEventTypesList.add(key);
+ try {
+ JSONArray eventSubTypes = eventTypesObj.getJSONArray(key);
+ ArrayList eventSubtypesList = new ArrayList();
+ for (int i = 0; i < eventSubTypes.length(); i++) {
+ eventSubtypesList.add(eventSubTypes.getString(i));
+ }
+ mEventSubTypesHashMap.put(key, eventSubtypesList);
+ mRedrawEventSubTypesList = true;
+ } catch (JSONException e) {
+ Log.e(TAG, "initialiseServiceConnection().getEventTypes Callback: Error parsing JSONObject" + e.getMessage() + e.toString());
+ }
+ }
+ mRedrawEventTypesList = true;
+ updateUi();
+ }
+ }
+ });
+ } else {
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.not_logged_in_dialog_title)
+ .setMessage(R.string.not_logged_in_dialog_message)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ finish();
+ }
+ })
+ .show();
+
+ }
+ }
+
+
+
+ private void updateUi() {
+ //Log.v(TAG,"updateUi()");
+ TextView tv;
+ Button btn;
+ RadioButton b;
+
+ tv = (TextView)findViewById(R.id.date_day_tv);
+ tv.setText(String.format("%02d",mDay));
+ tv = (TextView)findViewById(R.id.date_mon_tv);
+ tv.setText(String.format("%02d",mMonth+1)); // Month counted from zero
+ tv = (TextView)findViewById(R.id.date_year_tv);
+ tv.setText(String.format("%04d",mYear));
+ tv = (TextView)findViewById(R.id.time_hh_tv);
+ tv.setText(String.format("%02d",mHour));
+ tv = (TextView)findViewById(R.id.time_mm_tv);
+ tv.setText(String.format("%02d",mMinute));
+ tv = (TextView)findViewById(R.id.msg_tv);
+ tv.setText(mMsg);
+
+ // Populate event type button group if necessary
+ if (mEventTypesList != null && mRedrawEventTypesList) {
+ Log.v(TAG, "updateUi: " + mEventTypesList.toString());
+ mEventTypeRg.removeAllViews();
+ for (String eventTypeStr : mEventTypesList) {
+ b = new RadioButton(this);
+ b.setText(eventTypeStr);
+ mEventTypeRg.addView(b);
+ }
+ mRedrawEventTypesList = false;
+ }
+
+
+ String seizureTypeStr = null;
+ // Find which seizure type is selected
+ int checkedRadioButtonId = mEventTypeRg.getCheckedRadioButtonId();
+ //Log.i(TAG,"updateUi(): checkedRadioButtonId="+checkedRadioButtonId);
+ b = (RadioButton) findViewById(checkedRadioButtonId);
+ if (b != null) {
+ seizureTypeStr = b.getText().toString();
+ }
+ Log.i(TAG,"updateUi - SeizureType="+seizureTypeStr);
+
+ // Populate the event sub-types radio button list.
+ Log.v(TAG,"updateUi() - meventsubtypeshashmap="+mEventSubTypesHashMap+", mEventSubtypesListChanged="+mEventSubTypesListChanged);
+ if (mEventSubTypesHashMap != null && mRedrawEventSubTypesList) {
+ Log.v(TAG,"UpdateUi() - populating event sub types list");
+ if (seizureTypeStr != null) {
+ // based on https://androidexample.com/create-a-simple-listview
+ ArrayList subtypesArrayList = mEventSubTypesHashMap.get(seizureTypeStr);
+ Log.v(TAG, "updateUi() - eventType=" + seizureTypeStr + ", subtypes=" + subtypesArrayList);
+ mEventSubTypeRg.removeAllViews();
+ for (String eventSubTypeStr : subtypesArrayList) {
+ b = new RadioButton(this);
+ b.setText(eventSubTypeStr);
+ mEventSubTypeRg.addView(b);
+ }
+ mRedrawEventSubTypesList = false;
+ }
+ }
+ }
+
+ View.OnClickListener onOk =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ RadioButton b;
+ String seizureTypeStr = null;
+ String seizureSubTypeStr = null;
+ String notesStr = null;
+ Log.v(TAG, "onOk");
+ //SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String dateStr=String.format("%4d-%02d-%02d %02d:%02d:30",mYear,mMonth+1,mDay, mHour, mMinute);
+ Log.v(TAG, "onOk() - dateSTr="+dateStr);
+
+ // Read seizure type from radio buttons
+ int checkedRadioButtonId = mEventTypeRg.getCheckedRadioButtonId();
+ b = (RadioButton) findViewById(checkedRadioButtonId);
+ if (b != null) {
+ seizureTypeStr = b.getText().toString();
+ }
+ Log.i(TAG,"onOk() - SeizureType="+seizureTypeStr);
+
+ checkedRadioButtonId = mEventSubTypeRg.getCheckedRadioButtonId();
+ b = (RadioButton) findViewById(checkedRadioButtonId);
+ if (b != null) {
+ seizureSubTypeStr = b.getText().toString();
+ }
+ Log.i(TAG,"onOk() - SeizureSubType="+seizureSubTypeStr);
+
+ TextView tv = (TextView)findViewById(R.id.eventNotesTv);
+ notesStr = tv.getText().toString();
+
+ mLm.createLocalEvent(dateStr,5,seizureTypeStr, seizureSubTypeStr, notesStr,
+ mConnection.mSdServer.mSdData.toSettingsJSON());
+ mUtil.showToast("Seizure Event Created");
+ finish();
+ }
+ };
+ View.OnClickListener onCancel =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onCancel");
+ finish();
+ }
+ };
+ View.OnClickListener onSelectDate =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onSelectDate()");
+ DatePickerDialog datePickerDialog = new DatePickerDialog(mContext,
+ new DatePickerDialog.OnDateSetListener() {
+ @Override
+ public void onDateSet(DatePicker view, int year,
+ int monthOfYear, int dayOfMonth) {
+
+ mYear = year;
+ mMonth = monthOfYear;
+ mDay = dayOfMonth;
+ }
+ }, mYear, mMonth, mDay);
+ datePickerDialog.show();
+ }
+ };
+
+ View.OnClickListener onSelectTime =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.v(TAG, "onSelectTime()");
+ TimePickerDialog timePickerDialog = new TimePickerDialog(mContext,
+ new TimePickerDialog.OnTimeSetListener() {
+
+ @Override
+ public void onTimeSet(TimePicker view, int hourOfDay,
+ int minute) {
+
+ mHour = hourOfDay;
+ mMinute = minute;
+ }
+ }, mHour, mMinute, true);
+ timePickerDialog.show();
+ }
+ };
+
+
+ RadioGroup.OnCheckedChangeListener onEventTypeChange =
+ new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ mRedrawEventSubTypesList = true;
+ updateUi();
+ }
+ };
+ RadioGroup.OnCheckedChangeListener onEventSubTypeChange =
+ new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ updateUi();
+ }
+ };
+
+
+ /*
+ * Start the timer that will upload data to the remote server after a given period.
+ */
+ private void startUiTimer() {
+ if (mUiTimer != null) {
+ Log.v(TAG, "startUiTimer -timer already running - cancelling it");
+ mUiTimer.cancel();
+ mUiTimer = null;
+ }
+ Log.v(TAG, "startUiTimer() - starting UiTimer");
+ mUiTimer =
+ new UiTimer(1000, 1000);
+ mUiTimer.start();
+ }
+
+ /*
+ * Cancel the remote logging timer to prevent attempts to upload to remote database.
+ */
+ public void stopUiTimer() {
+ if (mUiTimer != null) {
+ Log.v(TAG, "stopUiTimer(): cancelling Ui timer");
+ mUiTimer.cancel();
+ mUiTimer = null;
+ }
+ }
+
+ /**
+ * Update User Interface Periodically
+ */
+ private class UiTimer extends CountDownTimer {
+ public UiTimer(long startTime, long interval) {
+ super(startTime, interval);
+ }
+
+ @Override
+ public void onTick(long l) {
+ // Do Nothing
+ }
+
+ @Override
+ public void onFinish() {
+ //Log.v(TAG, "UiTimer - onFinish - Updating UI");
+ updateUi();
+ // Restart this timer.
+ start();
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdData.java b/app/src/main/java/uk/org/openseizuredetector/SdData.java
index f24f7b0..fd73a29 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdData.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdData.java
@@ -38,6 +38,7 @@ public class SdData implements Parcelable {
private final static String TAG = "SdData";
private final static int N_RAW_DATA = 500; // 5 seconds at 100 Hz.
/* Analysis settings */
+ public String phoneAppVersion = "";
public boolean haveSettings = false; // flag to say if we have received settings or not.
public boolean haveData = false; // flag to say we have received data.
public short mDataUpdatePeriod;
@@ -65,7 +66,22 @@ public class SdData implements Parcelable {
public boolean mHRNullAsAlarm = false;
public double mHRThreshMin = 40.0;
public double mHRThreshMax = 150.0;
+
+ /* Oxygen Saturation Alarm Settings */
+ public boolean mO2SatAlarmActive = false;
+ public boolean mO2SatNullAsAlarm = false;
+ public double mO2SatThreshMin = 80.0;
+
+ /* Watch App Settings */
+ public String dataSourceName = "";
+ public String watchPartNo = "";
+ public String watchFwVersion = "";
+ public String watchSdVersion = "";
+ public String watchSdName = "";
+
+
public double rawData[];
+ public double rawData3D[];
int mNsamp = 0;
/* Analysis results */
@@ -87,14 +103,21 @@ public class SdData implements Parcelable {
public boolean mHRFaultStanding = false;
public double mHR = 0;
+ public boolean mO2SatAlarmStanding = false;
+ public boolean mO2SatFaultStanding = false;
+ public double mO2Sat = 0;
+
+
public SdData() {
simpleSpec = new int[10];
rawData = new double[N_RAW_DATA];
+ rawData3D = new double[N_RAW_DATA * 3];
dataTime = new Time(Time.getCurrentTimezone());
}
/*
* Intialise this SdData object from a JSON String
+ * FIXME - add O2saturation with checking in case it is not included in the data
*/
public boolean fromJSON(String jsonStr) {
Log.v(TAG, "fromJSON() - parsing jsonString - " + jsonStr);
@@ -120,12 +143,12 @@ public class SdData implements Parcelable {
alarmPhrase = jo.optString("alarmPhrase");
alarmThresh = jo.optInt("alarmThresh");
alarmRatioThresh = jo.optInt("alarmRatioThresh");
- mHRAlarmActive=jo.optBoolean("hrAlarmActive");
+ mHRAlarmActive = jo.optBoolean("hrAlarmActive");
mHRAlarmStanding = jo.optBoolean("hrAlarmStanding");
mHRThreshMin = jo.optDouble("hrThreshMin");
mHRThreshMax = jo.optDouble("hrThreshMax");
mHR = jo.optDouble("hr");
- if (mHR>=0.0) {
+ if (mHR >= 0.0) {
mHRAlarmActive = true;
}
JSONArray specArr = jo.optJSONArray("simpleSpec");
@@ -136,7 +159,7 @@ public class SdData implements Parcelable {
Log.v(TAG, "fromJSON(): sdData = " + this.toString());
return true;
} catch (Exception e) {
- Log.v(TAG, "fromJSON() - error parsing result"+e.toString());
+ Log.v(TAG, "fromJSON() - error parsing result" + e.toString());
haveData = false;
return false;
}
@@ -147,6 +170,106 @@ public class SdData implements Parcelable {
return toDataString(false);
}
+ public String toJSON(boolean includeRawData) {
+ return toDataString(includeRawData);
+ }
+
+ public String toDatapointJSON() {
+ String retval;
+ retval = "SdData.toDatapointJSON() Output";
+ try {
+ JSONObject jsonObj = new JSONObject();
+ if (dataTime != null) {
+ jsonObj.put("dataTime", dataTime.format("%d-%m-%Y %H:%M:%S"));
+ jsonObj.put("dataTimeStr", dataTime.format("%Y%m%dT%H%M%S"));
+ } else {
+ jsonObj.put("dataTimeStr", "00000000T000000");
+ jsonObj.put("dataTime", "00-00-00 00:00:00");
+ }
+ Log.v(TAG, "mSdData.dataTime = " + dataTime);
+ jsonObj.put("maxVal", maxVal);
+ jsonObj.put("maxFreq", maxFreq);
+ jsonObj.put("specPower", specPower);
+ jsonObj.put("roiPower", roiPower);
+ jsonObj.put("roiRatio", 10 * roiPower / specPower);
+ jsonObj.put("alarmState", alarmState);
+ jsonObj.put("alarmPhrase", alarmPhrase);
+ jsonObj.put("hr", mHR);
+ jsonObj.put("o2Sat", mO2Sat);
+ JSONArray arr = new JSONArray();
+ for (int i = 0; i < simpleSpec.length; i++) {
+ arr.put(simpleSpec[i]);
+ }
+ jsonObj.put("simpleSpec", arr);
+ JSONArray rawArr = new JSONArray();
+ for (int i = 0; i < rawData.length; i++) {
+ rawArr.put(rawData[i]);
+ }
+ //Log.v(TAG,"rawData[0]="+rawData[0]+", rawArr[0]="+rawArr.getDouble(0));
+ jsonObj.put("rawData", rawArr);
+
+ JSONArray raw3DArr = new JSONArray();
+ for (int i = 0; i < rawData3D.length; i++) {
+ raw3DArr.put(rawData3D[i]);
+ }
+ jsonObj.put("rawData3D", raw3DArr);
+
+ retval = jsonObj.toString();
+ Log.v(TAG,"retval rawData="+retval);
+ } catch (Exception ex) {
+ Log.v(TAG, "Error Creating Data Object - " + ex.toString());
+ retval = "Error Creating Data Object - " + ex.toString();
+ }
+
+ return (retval);
+ }
+
+
+ public String toSettingsJSON() {
+ String retval;
+ retval = "SdData.toSettingsJSON() Output";
+ try {
+ JSONObject jsonObj = new JSONObject();
+ if (dataTime != null) {
+ jsonObj.put("dataTime", dataTime.format("%d-%m-%Y %H:%M:%S"));
+ jsonObj.put("dataTimeStr", dataTime.format("%Y%m%dT%H%M%S"));
+ } else {
+ jsonObj.put("dataTimeStr", "00000000T000000");
+ jsonObj.put("dataTime", "00-00-00 00:00:00");
+ }
+ jsonObj.put("batteryPc", batteryPc);
+ jsonObj.put("alarmState", alarmState);
+ jsonObj.put("alarmPhrase", alarmPhrase);
+ jsonObj.put("sdMode", mSdMode);
+ jsonObj.put("sampleFreq", mSampleFreq);
+ jsonObj.put("analysisPeriod", analysisPeriod);
+ jsonObj.put("alarmFreqMin", alarmFreqMin);
+ jsonObj.put("alarmFreqMax", alarmFreqMax);
+ jsonObj.put("alarmThresh", alarmThresh);
+ jsonObj.put("alarmRatioThresh", alarmRatioThresh);
+ jsonObj.put("hrAlarmActive", mHRAlarmActive);
+ jsonObj.put("hrAlarmStanding", mHRAlarmStanding);
+ jsonObj.put("hrThreshMin", mHRThreshMin);
+ jsonObj.put("hrThreshMax", mHRThreshMax);
+ jsonObj.put("o2SatAlarmActive", mO2SatAlarmActive);
+ jsonObj.put("o2SatAlarmStanding", mO2SatAlarmStanding);
+ jsonObj.put("o2SatThreshMin", mO2SatThreshMin);
+ jsonObj.put("dataSourceName", dataSourceName);
+ Log.v(TAG,"phoneAppVersion="+phoneAppVersion);
+ jsonObj.put("phoneAppVersion", phoneAppVersion);
+ jsonObj.put("watchPartNo", watchPartNo);
+ jsonObj.put("watchSdName", watchSdName);
+ jsonObj.put("watchFwVersion", watchFwVersion);
+ jsonObj.put("watchSdVersion", watchSdVersion);
+
+ retval = jsonObj.toString();
+ } catch (Exception ex) {
+ Log.e(TAG, "toSettingsJSON(): Error Creating Data Object - " + ex.toString());
+ retval = "Error Creating Data Object - " + ex.toString();
+ }
+ return (retval);
+ }
+
public String toDataString(boolean includeRawData) {
String retval;
retval = "SdData.toDataString() Output";
@@ -155,7 +278,7 @@ public class SdData implements Parcelable {
if (dataTime != null) {
jsonObj.put("dataTime", dataTime.format("%d-%m-%Y %H:%M:%S"));
jsonObj.put("dataTimeStr", dataTime.format("%Y%m%dT%H%M%S"));
- }else{
+ } else {
jsonObj.put("dataTimeStr", "00000000T000000");
jsonObj.put("dataTime", "00-00-00 00:00:00");
}
@@ -170,18 +293,22 @@ public class SdData implements Parcelable {
jsonObj.put("haveSettings", haveSettings);
jsonObj.put("alarmState", alarmState);
jsonObj.put("alarmPhrase", alarmPhrase);
- jsonObj.put("sdMode",mSdMode);
- jsonObj.put("sampleFreq",mSampleFreq);
- jsonObj.put("analysisPeriod",analysisPeriod);
- jsonObj.put("alarmFreqMin",alarmFreqMin);
- jsonObj.put("alarmFreqMax",alarmFreqMax);
+ jsonObj.put("sdMode", mSdMode);
+ jsonObj.put("sampleFreq", mSampleFreq);
+ jsonObj.put("analysisPeriod", analysisPeriod);
+ jsonObj.put("alarmFreqMin", alarmFreqMin);
+ jsonObj.put("alarmFreqMax", alarmFreqMax);
jsonObj.put("alarmThresh", alarmThresh);
jsonObj.put("alarmRatioThresh", alarmRatioThresh);
jsonObj.put("hrAlarmActive", mHRAlarmActive);
jsonObj.put("hrAlarmStanding", mHRAlarmStanding);
- jsonObj.put("hrThreshMin",mHRThreshMin);
+ jsonObj.put("hrThreshMin", mHRThreshMin);
jsonObj.put("hrThreshMax", mHRThreshMax);
- jsonObj.put("hr",mHR);
+ jsonObj.put("hr", mHR);
+ jsonObj.put("o2SatAlarmActive", mO2SatAlarmActive);
+ jsonObj.put("o2SatAlarmStanding", mO2SatAlarmStanding);
+ jsonObj.put("o2SatThreshMin", mO2SatThreshMin);
+ jsonObj.put("o2Sat", mO2Sat);
JSONArray arr = new JSONArray();
for (int i = 0; i < simpleSpec.length; i++) {
arr.put(simpleSpec[i]);
@@ -189,9 +316,17 @@ public class SdData implements Parcelable {
jsonObj.put("simpleSpec", arr);
if (includeRawData) {
JSONArray rawArr = new JSONArray();
- for (int i = 0; i< rawData.length;i++) {
+ for (int i = 0; i < rawData.length; i++) {
rawArr.put(rawData[i]);
}
+ jsonObj.put("rawData", rawArr);
+
+ JSONArray raw3DArr = new JSONArray();
+ for (int i = 0; i < rawData3D.length; i++) {
+ raw3DArr.put(rawData3D[i]);
+ }
+ jsonObj.put("rawData3D", raw3DArr);
+
}
retval = jsonObj.toString();
@@ -209,7 +344,7 @@ public class SdData implements Parcelable {
retval = "";
if (dataTime != null) {
retval = dataTime.format("%d-%m-%Y %H:%M:%S");
- }else{
+ } else {
retval = "00-00-00 00:00:00";
}
for (int i = 0; i < simpleSpec.length; i++) {
@@ -220,32 +355,37 @@ public class SdData implements Parcelable {
retval = retval + ", " + mSampleFreq;
retval = retval + ", " + alarmPhrase;
retval = retval + ", " + mHR;
+ retval = retval + ", " + mO2Sat;
if (includeRawData) {
- for (int i = 0; i< mNsamp;i++) {
+ for (int i = 0; i < mNsamp; i++) {
retval = retval + ", " + rawData[i];
}
}
- return(retval);
+ return (retval);
}
- /** Return the average acceleration value in the dataset */
+ /**
+ * Return the average acceleration value in the dataset
+ */
public double getAvAcc() {
double sumAcc = 0.0;
- for (int i = 0; i< mNsamp;i++) {
+ for (int i = 0; i < mNsamp; i++) {
sumAcc += rawData[i];
}
- return(sumAcc/mNsamp);
+ return (sumAcc / mNsamp);
}
- /** Return the standard deviation of the acceleration values */
+ /**
+ * Return the standard deviation of the acceleration values
+ */
public double getSdAcc() {
double avAcc = 0.0;
double varAcc = 0.0;
avAcc = getAvAcc();
- for (int i = 0; i< mNsamp;i++) {
- varAcc += Math.pow(rawData[i]-avAcc,2);
+ for (int i = 0; i < mNsamp; i++) {
+ varAcc += Math.pow(rawData[i] - avAcc, 2);
}
- return(Math.sqrt(varAcc/(mNsamp-1)));
+ return (Math.sqrt(varAcc / (mNsamp - 1)));
}
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java
index 1218ee8..2b9a1ae 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java
@@ -254,6 +254,7 @@ public abstract class SdDataSource {
String sdVersion;
String sdName;
JSONArray accelVals = null;
+ JSONArray accelVals3D = null;
Log.v(TAG, "updateFromJSON - " + jsonStr);
try {
@@ -270,6 +271,12 @@ public abstract class SdDataSource {
// if we get 'null' HR (For example if the heart rate is not working)
mSdData.mHR = -1;
}
+ try {
+ mSdData.mO2Sat = dataObject.getDouble("O2sat");
+ } catch (JSONException e) {
+ // if we get 'null' O2 Saturation (For example if the oxygen sensor is not working)
+ mSdData.mO2Sat = -1;
+ }
try {
mMute = dataObject.getInt("Mute");
} catch (JSONException e) {
@@ -284,12 +291,31 @@ public abstract class SdDataSource {
}
int i;
for (i = 0; i < accelVals.length(); i++) {
- mSdData.rawData[i] = accelVals.getInt(i);
+ mSdData.rawData[i] = accelVals.getDouble(i);
}
mSdData.mNsamp = accelVals.length();
- //mNSamp = accelVals.length();
+ //Log.d(TAG,"accelVals[0]="+accelVals.getDouble(0)+", mSdData.rawData[0]="+mSdData.rawData[0]);
+ try {
+ accelVals3D = dataObject.getJSONArray("data3D");
+ Log.v(TAG, "Received " + accelVals3D.length() + " acceleration 3D values, rawData Length is " + mSdData.rawData3D.length);
+ if (accelVals3D.length() > mSdData.rawData3D.length) {
+ mUtil.writeToSysLogFile("ERROR: Received " + accelVals3D.length() + " 3D acceleration values, but rawData3D storage length is "
+ + mSdData.rawData3D.length);
+ }
+ for (i = 0; i < accelVals3D.length(); i++) {
+ mSdData.rawData3D[i] = accelVals3D.getDouble(i);
+ }
+ } catch (JSONException e) {
+ // If we get an error, just set rawData3D to zero
+ Log.i(TAG,"updateFromJSON - error parsing 3D data - setting it to zero");
+ for (i = 0; i < mSdData.rawData3D.length; i++) {
+ mSdData.rawData3D[i] = 0.;
+ }
+ }
+
mWatchAppRunningCheck = true;
doAnalysis();
+
if (mSdData.haveSettings == false) {
retVal = "sendSettings";
} else {
@@ -312,6 +338,10 @@ public abstract class SdDataSource {
sdName = dataObject.getString("sdName");
mUtil.writeToSysLogFile(" * sdName = " + sdName + " version " + sdVersion);
mUtil.writeToSysLogFile(" * watchPartNo = " + watchPartNo + " fwVersion " + watchFwVersion);
+ mSdData.watchPartNo = watchPartNo;
+ mSdData.watchFwVersion = watchFwVersion;
+ mSdData.watchSdVersion = sdVersion;
+ mSdData.watchSdName = sdName;
} catch (Exception e) {
Log.e(TAG, "updateFromJSON - Error Parsing V3.2 JSON String - " + e.toString());
mUtil.writeToSysLogFile("updateFromJSON - Error Parsing V3.2 JSON String - " + jsonStr + " - " + e.toString());
@@ -455,8 +485,10 @@ public abstract class SdDataSource {
// Check this data to see if it represents an alarm state.
alarmCheck();
hrCheck();
+ o2SatCheck();
fallCheck();
muteCheck();
+ Log.v(TAG,"after fallCheck, mSdData.fallAlarmStanding="+mSdData.fallAlarmStanding);
mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data.
}
@@ -470,9 +502,12 @@ public abstract class SdDataSource {
*/
private void alarmCheck() {
boolean inAlarm;
- Log.v(TAG, "alarmCheck()");
+ // Avoid potential divide by zero issue
+ if (mSdData.specPower == 0)
+ mSdData.specPower = 1;
+ Log.v(TAG, "alarmCheck() - roiPower="+mSdData.roiPower+" specPower="+ mSdData.specPower+" ratio="+10*mSdData.roiPower/ mSdData.specPower);
// Is the current set of data representing an alarm state?
- if ((mSdData.roiPower > mAlarmThresh) && (10 * (mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
+ if ((mSdData.roiPower > mAlarmThresh) && ((10 * mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
inAlarm = true;
} else {
inAlarm = false;
@@ -502,7 +537,7 @@ public abstract class SdDataSource {
}
}
- Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mAlarmTime=" + mAlarmTime);
+ Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mWarnTime=" + mWarnTime+ " mAlarmTime=" + mAlarmTime);
}
@@ -543,9 +578,39 @@ public abstract class SdDataSource {
mSdData.mHRAlarmStanding = false;
}
}
+ }
+
+ /**
+ * hrCheck - check the Heart rate data in mSdData to see if it represents an alarm condition.
+ * Sets mSdData.mHRAlarmStanding
+ */
+ public void o2SatCheck() {
+ Log.v(TAG, "o2SatCheck()");
+ /* Check Oxygen Saturation against alarm settings */
+ if (mSdData.mO2SatAlarmActive) {
+ if (mSdData.mO2Sat < 0) {
+ if (mSdData.mO2SatNullAsAlarm) {
+ Log.i(TAG, "Oxygen Saturation Null - Alarming");
+ mSdData.mO2SatFaultStanding = false;
+ mSdData.mO2SatAlarmStanding = true;
+ } else {
+ Log.i(TAG, "Oxygen Saturation Fault (O2Sat<0)");
+ mSdData.mO2SatFaultStanding = true;
+ mSdData.mO2SatAlarmStanding = false;
+ }
+ } else if (mSdData.mO2Sat < mSdData.mO2SatThreshMin) {
+ Log.i(TAG, "Oxygen Saturation Abnormal - " + mSdData.mO2Sat + " %");
+ mSdData.mO2SatFaultStanding = false;
+ mSdData.mO2SatAlarmStanding = true;
+ } else {
+ mSdData.mO2SatFaultStanding = false;
+ mSdData.mO2SatAlarmStanding = false;
+ }
+ }
}
+
/****************************************************************
* Simple threshold analysis to chech for fall.
* Called from clock_tick_handler()
@@ -570,8 +635,9 @@ public abstract class SdDataSource {
if (mSdData.rawData[i + j] < minAcc) minAcc = mSdData.rawData[i + j];
if (mSdData.rawData[i + j] > maxAcc) maxAcc = mSdData.rawData[i + j];
}
+ Log.d(TAG, "check_fall() - minAcc=" + minAcc +" (mFallThreshMin="+mFallThreshMin+ "), maxAcc=" + maxAcc+" (mFallThreshMax="+mFallThreshMax+")") ;
if ((minAcc < mFallThreshMin) && (maxAcc > mFallThreshMax)) {
- Log.d(TAG, "check_fall() - minAcc=" + minAcc + ", maxAcc=" + maxAcc);
+ Log.d(TAG, "check_fall() ****FALL DETECTED***** minAcc=" + minAcc + ", maxAcc=" + maxAcc);
Log.d(TAG, "check_fall() - ****FALL DETECTED****");
mSdData.fallAlarmStanding = true;
return;
@@ -649,11 +715,11 @@ public abstract class SdDataSource {
// get time since the last data was received from the watch.
tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false));
- Log.v(TAG, "faultCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout
- + ", combined = " + (mDataUpdatePeriod + mAppRestartTimeout) * 1000);
+ //Log.v(TAG, "faultCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout
+ // + ", combined = " + (mDataUpdatePeriod + mAppRestartTimeout) * 1000);
if (!mWatchAppRunningCheck &&
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
- Log.v(TAG, "faultCheck() - watch app not running so not doing anything");
+ //Log.v(TAG, "faultCheck() - watch app not running so not doing anything");
mAlarmCount = 0;
}
}
@@ -814,6 +880,19 @@ public abstract class SdDataSource {
Log.v(TAG, "updatePrefs() HRThreshMax = " + mSdData.mHRThreshMax);
mUtil.writeToSysLogFile( "updatePrefs() HRThreshMax = " + mSdData.mHRThreshMax);
+ mSdData.mO2SatAlarmActive = SP.getBoolean("O2SatAlarmActive", false);
+ Log.v(TAG, "updatePrefs() O2SatAlarmActive = " + mSdData.mO2SatAlarmActive);
+ mUtil.writeToSysLogFile( "updatePrefs() O2SatAlarmActive = " + mSdData.mO2SatAlarmActive);
+
+ mSdData.mO2SatNullAsAlarm = SP.getBoolean("O2SatNullAsAlarm", false);
+ Log.v(TAG, "updatePrefs() O2SatNullAsAlarm = " + mSdData.mO2SatNullAsAlarm);
+ mUtil.writeToSysLogFile( "updatePrefs() O2SatNullAsAlarm = " + mSdData.mO2SatNullAsAlarm);
+
+ prefStr = SP.getString("O2SatThreshMin", "SET_FROM_XML");
+ mSdData.mO2SatThreshMin = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() O2SatThreshMin = " + mSdData.mO2SatThreshMin);
+ mUtil.writeToSysLogFile( "updatePrefs() O2SatThreshMin = " + mSdData.mO2SatThreshMin);
+
} else {
Log.v(TAG, "updatePrefs() - prefStr is null - WHY????");
mUtil.writeToSysLogFile("SDDataSource.updatePrefs() - prefStr is null - WHY??");
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceAw.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceAw.java
index 60e1dde..e028c24 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceAw.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceAw.java
@@ -40,7 +40,7 @@ import java.nio.IntBuffer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
-import com.google.android.gms.wearable.MessageEvent;
+import com.google.android.gms.wearable .MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java
index b96d672..a56f059 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java
@@ -63,6 +63,7 @@ public class SdDataSourceBLE extends SdDataSource {
private int nRawData = 0;
private double[] rawData = new double[MAX_RAW_DATA];
+ private boolean waitForDescriptorWrite = false;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
@@ -82,13 +83,12 @@ public class SdDataSourceBLE extends SdDataSource {
public static String SERV_DEV_INFO = "0000180a-0000-1000-8000-00805f9b34fb";
public static String SERV_HEART_RATE = "0000180d-0000-1000-8000-00805f9b34fb";
- public static String SERV_OSD = "a19585e9-0001-39d0-015f-b3e2b9a0c854";
+ public static String SERV_OSD = "000085e9-0000-1000-8000-00805f9b34fb";
public static String CHAR_HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
public static String CHAR_MANUF_NAME = "00002a29-0000-1000-8000-00805f9b34fb";
public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
- public static String CHAR_OSD_ACC_DATA = "a19585e9-0002-39d0-015f-b3e2b9a0c854";
- public static String CHAR_OSD_BATT_DATA = "a19585e9-0004-39d0-015f-b3e2b9a0c854";
-
+ public static String CHAR_OSD_ACC_DATA = "000085ea-0000-1000-8000-00805f9b34fb";
+ public static String CHAR_OSD_BATT_DATA = "000085eb-0000-1000-8000-00805f9b34fb";
public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(CHAR_HEART_RATE_MEASUREMENT);
private BluetoothGatt mGatt;
@@ -264,7 +264,8 @@ public class SdDataSourceBLE extends SdDataSource {
}
else if (charUuidStr.equals(CHAR_OSD_BATT_DATA)) {
Log.v(TAG,"Saving battery characteristic for later");
- mBattChar = gattCharacteristic;
+ Log.v(TAG, "Subscribing to battery change Notifications");
+ setCharacteristicNotification(gattCharacteristic,true);
}
}
}
@@ -287,6 +288,8 @@ public class SdDataSourceBLE extends SdDataSource {
}
public void onDataReceived(BluetoothGattCharacteristic characteristic) {
+ Log.v(TAG, "onDataReceived uuid" + characteristic.getUuid().toString());
+
// FIXME - collect data until we have enough to do analysis, then use onDataReceived to process it.
//Log.v(TAG,"onDataReceived: Characteristic="+characteristic.getUuid().toString());
if (characteristic.getUuid().toString().equals(CHAR_HEART_RATE_MEASUREMENT)) {
@@ -294,12 +297,13 @@ public class SdDataSourceBLE extends SdDataSource {
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
- //Log.d(TAG, "Heart rate format UINT16.");
+ Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
- //Log.d(TAG, "Heart rate format UINT8.");
+ Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
+ mSdData.mHR = (double) heartRate;
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
}
else if (characteristic.getUuid().toString().equals(CHAR_OSD_ACC_DATA)) {
@@ -322,21 +326,15 @@ public class SdDataSourceBLE extends SdDataSource {
//mNSamp = accelVals.length();
mWatchAppRunningCheck = true;
mDataStatusTime = new Time(Time.getCurrentTimezone());
-
- if (mSdData.haveSettings == false) {
- Log.v(TAG,"Requesting Battery Data");
- mGatt.readCharacteristic(mBattChar);
- }
-
doAnalysis();
-
nRawData = 0;
}
}
}
else if (characteristic.getUuid().toString().equals(CHAR_OSD_BATT_DATA)) {
- mSdData.batteryPc = characteristic.getValue()[0];
- Log.v(TAG,"Received Battery Data");
+ byte batteryPc = characteristic.getValue()[0];
+ mSdData.batteryPc = batteryPc;
+ Log.v(TAG,"Received Battery Data" + String.format("%d", batteryPc));
mSdData.haveSettings = true;
}
else {
@@ -361,8 +359,13 @@ public class SdDataSourceBLE extends SdDataSource {
Log.v(TAG,"onCharacteristicChanged(): Characteristic "+characteristic.getUuid()+" changed");
onDataReceived(characteristic);
}
- };
+ @Override
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ Log.v(TAG,"onDescriptorWrite(): Characteristic " + descriptor.getUuid() + " changed");
+ waitForDescriptorWrite = false;
+ }
+ };
/**
* Enables or disables notification on a give characteristic.
@@ -370,12 +373,27 @@ public class SdDataSourceBLE extends SdDataSource {
* @param characteristic Characteristic to act on.
* @param enabled If true, enable notification. False otherwise.
*/
- public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
- boolean enabled) {
+ public void setCharacteristicNotification(final BluetoothGattCharacteristic characteristic, final boolean enabled) {
+ Log.w(TAG, "setCharacteristicNotification " + characteristic.getUuid());
+
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
+
+ if (waitForDescriptorWrite) {
+ // Apparently if you try to write multiple descriptors too quickly then only
+ // one is processed, hence why this waiting logic is necessary
+ Log.w(TAG, "waitForDescriptor " + characteristic.getUuid());
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ Log.w(TAG, "delayed");
+ setCharacteristicNotification(characteristic, enabled);
+ }
+ }, 500);
+ return;
+ }
+
if (enabled) {
Log.v(TAG, "setCharacteristicNotification - Requesting notifications");
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
@@ -396,8 +414,9 @@ public class SdDataSourceBLE extends SdDataSource {
UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
-
}
+
+ waitForDescriptorWrite = true;
}
/**
@@ -414,12 +433,3 @@ public class SdDataSourceBLE extends SdDataSource {
}
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePhone.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePhone.java
index 625a7c1..48abb03 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePhone.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePhone.java
@@ -129,6 +129,9 @@ public class SdDataSourcePhone extends SdDataSource implements SensorEventListen
float z = event.values[2];
//Log.v(TAG,"Accelerometer Data Received: x="+x+", y="+y+", z="+z);
mSdData.rawData[mSdData.mNsamp] = sqrt(x*x + y*y + z*z);
+ mSdData.rawData3D[3*mSdData.mNsamp] = x;
+ mSdData.rawData3D[3*mSdData.mNsamp+1] = y;
+ mSdData.rawData3D[3*mSdData.mNsamp+2] = z;
mSdData.mNsamp++;
if (mSdData.mNsamp==NSAMP) {
// Calculate the sample frequency for this sample, but do not change mSampleFreq, which is used for
@@ -142,6 +145,9 @@ public class SdDataSourcePhone extends SdDataSource implements SensorEventListen
// FIXME - we should really do this properly rather than assume we are really receiving data at 50Hz.
for (int i=0; i= 26) {
NotificationChannel channel = new NotificationChannel(mNotChId,
mNotChName,
@@ -289,15 +301,16 @@ public class SdServer extends Service implements SdDataReceiver {
Log.v(TAG, "showing Notification and calling startForeground (Android 8 and higher)");
mUtil.writeToSysLogFile("SdServer.onStartCommand() - showing Notification and calling startForeground (Android 8 and higher)");
showNotification(0);
- startForeground(NOTIFICATION_ID,mNotification);
+ startForeground(NOTIFICATION_ID, mNotification);
} else {
Log.v(TAG, "showing Notification");
mUtil.writeToSysLogFile("SdServer.onStartCommand() - showing Notification");
showNotification(0);
}
// Record last time we sent an SMS so we can limit rate of SMS
- // sending to one per minute.
+ // sending to one per minute. We set it to one minute ago (60000 milliseconds)
mSMSTime = new Time(Time.getCurrentTimezone());
+ mSMSTime.set(mSMSTime.toMillis(false) - 60000);
// Start timer to log data regularly..
@@ -318,6 +331,10 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("SdServer.onStartCommand() - dataLog timer already running???");
}
+ if (mLogDataRemote) {
+ startEventsTimer();
+ }
+
// Start the web server
mUtil.writeToSysLogFile("SdServer.onStartCommand() - starting web server");
@@ -333,6 +350,8 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("SdServer.onStartCommand() - mWakeLock is not null - this shouldn't happen???");
}
+ checkEvents();
+
return START_STICKY;
}
@@ -345,7 +364,7 @@ public class SdServer extends Service implements SdDataReceiver {
if (mWakeLock != null) {
try {
mWakeLock.release();
- Log.v(TAG, "Released Wake Lock to allow device to sleep.");
+ Log.d(TAG, "Released Wake Lock to allow device to sleep.");
} catch (Exception e) {
Log.e(TAG, "Error Releasing Wakelock - " + e.toString());
mUtil.writeToSysLogFile("SdServer.onDestroy() - Error releasing wakelock.");
@@ -357,7 +376,7 @@ public class SdServer extends Service implements SdDataReceiver {
}
if (mSdDataSource != null) {
- Log.i(TAG, "stopping mSdDataSource");
+ Log.d(TAG, "stopping mSdDataSource");
mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping mSdDataSource");
mSdDataSource.stop();
} else {
@@ -367,7 +386,7 @@ public class SdServer extends Service implements SdDataReceiver {
// Stop the Cancel Audible timer
if (mCancelAudibleTimer != null) {
- Log.v(TAG, "onDestroy(): cancelling Cancel_Audible timer");
+ Log.d(TAG, "onDestroy(): cancelling Cancel_Audible timer");
mCancelAudibleTimer.cancel();
//mCancelAudibleTimer.purge();
mCancelAudibleTimer = null;
@@ -376,17 +395,25 @@ public class SdServer extends Service implements SdDataReceiver {
// Stop the Fault timer
if (mFaultTimer != null) {
- Log.v(TAG, "onDestroy(): cancelling fault timer");
+ Log.d(TAG, "onDestroy(): cancelling fault timer");
mFaultTimer.cancel();
mFaultTimer = null;
}
+ // Stop the Event timer
+ if (mEventsTimer != null) {
+ Log.d(TAG, "onDestroy(): Cancelling events timer");
+ stopEventsTimer();
+ }
+
// Stop the Cancel Alarm Latch timer
+ Log.d(TAG, "onDestroy(): stopping alarm latch timer");
stopLatchTimer();
// Stop the location finder.
if (mLocationFinder != null) {
+ Log.d(TAG, "onDestroy(): stopping Location Finder");
mLocationFinder.destroy();
mLocationFinder = null;
}
@@ -394,7 +421,7 @@ public class SdServer extends Service implements SdDataReceiver {
try {
// Stop web server
- Log.v(TAG, "onDestroy(): stopping web server");
+ Log.d(TAG, "onDestroy(): stopping web server");
mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping Web Server");
stopWebServer();
@@ -404,20 +431,29 @@ public class SdServer extends Service implements SdDataReceiver {
this.stopForeground(true);
// Cancel the notification.
- Log.v(TAG, "onDestroy(): cancelling notification");
+ Log.d(TAG, "onDestroy(): cancelling notification");
mUtil.writeToSysLogFile("SdServer.onDestroy - cancelling notification");
mNM.cancel(NOTIFICATION_ID);
+ mNM.cancel(EVENT_NOTIFICATION_ID);
+ mNM.cancel(DATASHARE_NOTIFICATION_ID);
+
// stop this service.
- Log.v(TAG, "onDestroy(): calling stopSelf()");
+ Log.d(TAG, "onDestroy(): calling stopSelf()");
mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping self");
stopSelf();
} catch (Exception e) {
- Log.v(TAG, "Error in onDestroy() - " + e.toString());
+ Log.e(TAG, "Error in onDestroy() - " + e.toString());
mUtil.writeToSysLogFile("SdServer.onDestroy() -error " + e.toString());
}
+ if (mLm != null) {
+ Log.d(TAG, "Closing Down Log Manager");
+ mLm.stop();
+ mLm.close();
+ }
+
super.onDestroy();
}
@@ -427,7 +463,7 @@ public class SdServer extends Service implements SdDataReceiver {
* Show a notification while this service is running.
*/
private void showNotification(int alarmLevel) {
- Log.v(TAG, "showNotification() - alarmLevel="+alarmLevel);
+ Log.v(TAG, "showNotification() - alarmLevel=" + alarmLevel);
int iconId;
String titleStr;
Uri soundUri = null;
@@ -462,7 +498,7 @@ public class SdServer extends Service implements SdDataReceiver {
}
if (mCancelAudible) {
- Log.v(TAG,"ShowNotification - Not beeping because mCancelAudible set");
+ Log.v(TAG, "ShowNotification - Not beeping because mCancelAudible set");
soundUri = null;
}
@@ -491,7 +527,7 @@ public class SdServer extends Service implements SdDataReceiver {
.build();
if (mMp3Alarm) {
if (soundUri != null) {
- Log.v(TAG, "showNotification - setting Notification Sound to "+soundUri.toString());
+ Log.v(TAG, "showNotification - setting Notification Sound to " + soundUri.toString());
mNotificationBuilder.setSound(soundUri);
}
}
@@ -521,6 +557,13 @@ public class SdServer extends Service implements SdDataReceiver {
}
}
+ public void raiseManualAlarm() {
+ Log.d(TAG, "raiseManualAlarm()");
+ SdData sdData = mSdData;
+ sdData.alarmState = 5;
+ onSdDataReceived(sdData);
+ }
+
/**
* Process the data received from the SdData source. On exit, the mSdData structure is populated with
* the appropriate data.
@@ -529,6 +572,8 @@ public class SdServer extends Service implements SdDataReceiver {
*/
public void onSdDataReceived(SdData sdData) {
Log.v(TAG, "onSdDataReceived() - " + sdData.toString());
+ Log.v(TAG, "onSdDataReceived(), sdData.fallAlarmStanding=" + sdData.fallAlarmStanding);
+
if (sdData.alarmState == 0) {
if ((!mLatchAlarms) ||
(mLatchAlarms &&
@@ -557,7 +602,7 @@ public class SdServer extends Service implements SdDataReceiver {
}
if (mLogAlarms) {
Log.v(TAG, "WARNING - Logging to SD Card");
- writeAlarmToSD();
+ //writeAlarmToSD();
} else {
Log.v(TAG, "WARNING");
}
@@ -570,7 +615,7 @@ public class SdServer extends Service implements SdDataReceiver {
sdData.alarmStanding = true;
if (mLogAlarms) {
Log.v(TAG, "***ALARM*** - Logging to SD Card");
- writeAlarmToSD();
+ //writeAlarmToSD();
} else {
Log.v(TAG, "***ALARM***");
}
@@ -602,12 +647,13 @@ public class SdServer extends Service implements SdDataReceiver {
startLatchTimer();
}
// Handle fall alarm
+ Log.v(TAG, "sdData.fallAlarmStanding=" + sdData.fallAlarmStanding);
if ((sdData.alarmState == 3) || (sdData.fallAlarmStanding)) {
sdData.alarmPhrase = "FALL";
sdData.fallAlarmStanding = true;
if (mLogAlarms) {
Log.v(TAG, "***FALL*** - Logging to SD Card");
- writeAlarmToSD();
+ //writeAlarmToSD();
showNotification(2);
} else {
Log.v(TAG, "***FALL***");
@@ -641,7 +687,7 @@ public class SdServer extends Service implements SdDataReceiver {
sdData.alarmPhrase = "HR ABNORMAL";
if (mLogAlarms) {
Log.v(TAG, "***HEART RATE*** - Logging to SD Card");
- writeAlarmToSD();
+ //writeAlarmToSD();
} else {
Log.v(TAG, "***HEART RATE***");
}
@@ -668,19 +714,56 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.showToast(getString(R.string.SMSAlarmDisabledNotSendingMsg));
Log.v(TAG, "mSMSAlarm is false - not sending");
}
-
}
+ // Handle Oxygen Saturation alarm
+ if ((sdData.mO2SatAlarmActive) && (sdData.mO2SatAlarmStanding)) {
+ sdData.alarmPhrase = "Oxygen Saturation ABNORMAL";
+ if (mLogAlarms) {
+ Log.v(TAG, "***OXYGEN SATURATION*** - Logging to SD Card");
+ //writeAlarmToSD();
+ } else {
+ Log.v(TAG, "***OXYGEN SATURATION***");
+ }
+ // Make alarm beep tone
+ alarmBeep();
+ showNotification(2);
+ // Display MainActvity
+ showMainActivity();
+ // 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;
+ } else {
+ mUtil.showToast("SMS Alarm already sent - not re-sending");
+ Log.v(TAG, "SMS Alarm already sent - not re-sending");
+ }
+ } else {
+ mUtil.showToast(getString(R.string.SMSAlarmDisabledNotSendingMsg));
+ Log.v(TAG, "mSMSAlarm is false - not sending");
+ }
+ }
+
+
// Fault
if ((sdData.alarmState) == 4 || (sdData.alarmState == 7) || (sdData.mHRFaultStanding)) {
sdData.alarmPhrase = "FAULT";
- writeAlarmToSD();
+ //writeAlarmToSD();
faultWarningBeep();
showNotification(-1);
} else {
stopFaultTimer();
}
mSdData = sdData;
+ mSdData.dataSourceName = mSdDataSourceName;
+ mSdData.phoneAppVersion = mUtil.getAppVersionName();
+
if (webServer != null) webServer.setSdData(mSdData);
Log.v(TAG, "onSdDataReceived() - setting mSdData to " + mSdData.toString());
@@ -713,7 +796,7 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("onSdDataFault() - starting Fault Timer");
}
- showNotification(-1);
+ showNotification(-1);
}
/* from http://stackoverflow.com/questions/12154940/how-to-make-a-beep-in-android */
@@ -737,8 +820,8 @@ public class SdServer extends Service implements SdDataReceiver {
*/
public void faultWarningBeep() {
if (mCancelAudible) {
- Log.v(TAG, "faultWarningBeep() - CancelAudible Active - silent beep...");
- } else {
+ Log.v(TAG, "faultWarningBeep() - CancelAudible Active - silent beep...");
+ } else {
if (mAudibleFaultWarning) {
if (mMp3Alarm) {
Log.v(TAG, "Not making MP3 fault beep - handled by notification");
@@ -807,24 +890,14 @@ public class SdServer extends Service implements SdDataReceiver {
AlertDialog ad;
if (mSMSAlarm) {
if (!mCancelAudible) {
- if (!mUtil.areSMSPermissionsOK()) {
- mUtil.showToast(getString(R.string.SMSPermissionsDeniedMsg));
- Log.e(TAG, "ERROR - Permission for SMS or Location Denied - Not Sending SMS");
- } else {
- //mSMSAlertDialog = new AlertDialog.Builder(this);
- //mSMSAlertDialog.setMessage("SMS Will be Sent in 10 Seconds, unless you press the Cancel Button")
- // .setPositiveButton("Send", smsCancelClickListener)
- // .setNegativeButton("Cancel", smsCancelClickListener);
- //ad = mSMSAlertDialog.show();
- startSmsTimer();
- }
+ startSmsTimer();
} else {
Log.i(TAG, "sendSMSAlarm() - Cancel Audible Active - not sending SMS");
mUtil.showToast(getString(R.string.cancel_audible_not_sending_sms));
}
} else {
Log.i(TAG, "sendSMSAlarm() - SMS Alarms Disabled - not doing anything!");
- mUtil.showToast(getString(R.string.sms_alarm_disabled));
+ mUtil.showToast(getString(R.string.sms_alarms_disabled));
}
if (mPhoneAlarm) {
if (!mCancelAudible) {
@@ -848,14 +921,14 @@ public class SdServer extends Service implements SdDataReceiver {
DialogInterface.OnClickListener smsCancelClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- switch (which){
+ switch (which) {
case DialogInterface.BUTTON_POSITIVE:
- Log.v(TAG,"smsCancelClickListener - Positive button");
+ Log.v(TAG, "smsCancelClickListener - Positive button");
//Yes button clicked
break;
case DialogInterface.BUTTON_NEGATIVE:
- Log.v(TAG,"smsCancelClickListener - Negative button");
+ Log.v(TAG, "smsCancelClickListener - Negative button");
//No button clicked
break;
}
@@ -863,7 +936,6 @@ public class SdServer extends Service implements SdDataReceiver {
};
-
/*
* Start the timer that will send and SMS alert after a given period.
*/
@@ -896,10 +968,8 @@ public class SdServer extends Service implements SdDataReceiver {
}
-
-
/*
- * Start the timer that will automatically re-set a latched alarm after a given period.
+ * Start the timer that will automatically re-set a latched alarm after a given period.
*/
private void startLatchTimer() {
if (mLatchAlarms) {
@@ -1011,7 +1081,7 @@ public class SdServer extends Service implements SdDataReceiver {
} else {
if (webServer.isAlive()) {
Log.w(TAG, "stopWebServer() - server still alive???");
- mUtil.writeToSysLogFile( "stopWebServer() - server still alive???");
+ mUtil.writeToSysLogFile("stopWebServer() - server still alive???");
} else {
mUtil.writeToSysLogFile("stopWebServer() - server died ok");
Log.v(TAG, "stopWebServer() - server died ok");
@@ -1069,9 +1139,13 @@ public class SdServer extends Service implements SdDataReceiver {
*/
public void logData() {
if (mLogData) {
- Log.v(TAG, "logData() - writing data to SD Card");
- writeToSD();
- //mLm.writeToLocalDb(mSdData);
+ if (mLm != null) {
+ Log.v(TAG, "logData() - writing data to Database");
+ //writeToSD();
+ mLm.writeDatapointToLocalDb(mSdData);
+ } else {
+ Log.e(TAG, "logData() - mLm is null - this should not happen");
+ }
}
}
@@ -1101,7 +1175,7 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("updatePrefs() - mLatchAlarmTimerPeriod = " + mLatchAlarmPeriod);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem with LatchAlarmTimerPeriod preference!");
- mUtil.writeToSysLogFile( "updatePrefs() - Problem with LatchAlarmTimerPeriod preference!");
+ mUtil.writeToSysLogFile("updatePrefs() - Problem with LatchAlarmTimerPeriod preference!");
mUtil.showToast(getString(R.string.problem_parsing_preferences));
}
mAudibleFaultWarning = SP.getBoolean("AudibleFaultWarning", true);
@@ -1127,42 +1201,65 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("updatePrefs() - mAuidbleWarning = " + mAudibleWarning);
mMp3Alarm = SP.getBoolean("UseMp3Alarm", false);
Log.v(TAG, "updatePrefs() - mMp3Alarm = " + mMp3Alarm);
- mUtil.writeToSysLogFile( "updatePrefs() - mMp3Alarm = " + mMp3Alarm);
+ mUtil.writeToSysLogFile("updatePrefs() - mMp3Alarm = " + mMp3Alarm);
mSMSAlarm = SP.getBoolean("SMSAlarm", false);
Log.v(TAG, "updatePrefs() - mSMSAlarm = " + mSMSAlarm);
- mUtil.writeToSysLogFile( "updatePrefs() - mSMSAlarm = " + mSMSAlarm);
+ mUtil.writeToSysLogFile("updatePrefs() - mSMSAlarm = " + mSMSAlarm);
mPhoneAlarm = SP.getBoolean("PhoneCallAlarm", false);
Log.v(TAG, "updatePrefs() - mSMSAlarm = " + mSMSAlarm);
- mUtil.writeToSysLogFile( "updatePrefs() - mSMSAlarm = " + mSMSAlarm);
+ mUtil.writeToSysLogFile("updatePrefs() - mSMSAlarm = " + mSMSAlarm);
String SMSNumberStr = SP.getString("SMSNumbers", "");
mSMSNumbers = SMSNumberStr.split(",");
mSMSMsgStr = SP.getString("SMSMsg", "Seizure Detected!!!");
Log.v(TAG, "updatePrefs() - SMSNumberStr = " + SMSNumberStr);
- mUtil.writeToSysLogFile( "updatePrefs() - SMSNumberStr = " + SMSNumberStr);
+ mUtil.writeToSysLogFile("updatePrefs() - SMSNumberStr = " + SMSNumberStr);
Log.v(TAG, "updatePrefs() - mSMSNumbers = " + mSMSNumbers);
- mUtil.writeToSysLogFile( "updatePrefs() - mSMSNumbers = " + mSMSNumbers);
+ mUtil.writeToSysLogFile("updatePrefs() - mSMSNumbers = " + mSMSNumbers);
mLogAlarms = SP.getBoolean("LogAlarms", true);
Log.v(TAG, "updatePrefs() - mLogAlarms = " + mLogAlarms);
mUtil.writeToSysLogFile("updatePrefs() - mLogAlarms = " + mLogAlarms);
- mLogData = SP.getBoolean("LogData", true);
- Log.v(TAG, "updatePrefs() - mLogData = " + mLogData);
- mUtil.writeToSysLogFile( "updatePrefs() - mLogData = " + mLogData);
- mLogDataRemote = SP.getBoolean("LogDataRemote", false);
+ //mLogData = SP.getBoolean("LogData", true);
+ mLogData = true;
+ Log.v(TAG, "SdServer.updatePrefs() - mLogData = " + mLogData);
+ mUtil.writeToSysLogFile("updatePrefs() - mLogData = " + mLogData);
+ //mLogDataRemote = SP.getBoolean("LogDataRemote", false);
+ mLogDataRemote = true;
Log.v(TAG, "updatePrefs() - mLogDataRemote = " + mLogDataRemote);
- mUtil.writeToSysLogFile( "updatePrefs() - mLogDataRemote = " + mLogDataRemote);
+ mUtil.writeToSysLogFile("updatePrefs() - mLogDataRemote = " + mLogDataRemote);
mLogDataRemoteMobile = SP.getBoolean("LogDataRemoteMobile", false);
Log.v(TAG, "updatePrefs() - mLogDataRemoteMobile = " + mLogDataRemoteMobile);
mUtil.writeToSysLogFile("updatePrefs() - mLogDataRemoteMobile = " + mLogDataRemoteMobile);
- mOSDUname = SP.getString("OSDUname", "");
- Log.v(TAG, "updatePrefs() - mOSDUname = " + mOSDUname);
- mOSDPasswd = SP.getString("OSDPasswd", "");
- Log.v(TAG, "updatePrefs() - mOSDPasswd = " + mOSDPasswd);
- mOSDWearerId = Integer.parseInt(SP.getString("OSDWearerId", "0"));
- Log.v(TAG, "updatePrefs() - mOSDWearerId = " + mOSDWearerId);
+ mAuthToken = SP.getString("webApiAuthToken", null);
+ Log.v(TAG, "updatePrefs() - mAuthToken = " + mAuthToken);
+ mUtil.writeToSysLogFile("updatePrefs() - mAuthToken = " + mAuthToken);
+
+ String prefVal;
+ prefVal = SP.getString("EventDurationSec", "300");
+ mEventDuration = Integer.parseInt(prefVal);
+ Log.v(TAG, "mEventDuration=" + mEventDuration);
+
+ mAutoPruneDb = SP.getBoolean("AutoPruneDb", true);
+ Log.v(TAG, "mAutoPruneDb=" + mAutoPruneDb);
+
+ prefVal = SP.getString("DataRetentionPeriod", "28");
+ mDataRetentionPeriod = Integer.parseInt(prefVal);
+ Log.v(TAG, "mDataRetentionPeriod=" + mDataRetentionPeriod);
+
+ //prefVal = SP.getString("RemoteLogPeriod", "60");
+ //mRemoteLogPeriod = Integer.parseInt(prefVal);
+ //mRemoteLogPeriod = 60;
+ Log.v(TAG, "mRemoteLogPeriod=" + mRemoteLogPeriod);
+
+ //mOSDUname = SP.getString("OSDUname", "");
+ //Log.v(TAG, "updatePrefs() - mOSDUname = " + mOSDUname);
+ //mOSDPasswd = SP.getString("OSDPasswd", "");
+ //Log.v(TAG, "updatePrefs() - mOSDPasswd = " + mOSDPasswd);
+ //mOSDWearerId = Integer.parseInt(SP.getString("OSDWearerId", "0"));
+ //Log.v(TAG, "updatePrefs() - mOSDWearerId = " + mOSDWearerId);
mOSDUrl = SP.getString("OSDUrl", "http://openseizuredetector.org.uk/webApi");
Log.v(TAG, "updatePrefs() - mOSDUrl = " + mOSDUrl);
- mUtil.writeToSysLogFile( "updatePrefs() - mOSDUrl = " + mOSDUrl);
+ mUtil.writeToSysLogFile("updatePrefs() - mOSDUrl = " + mOSDUrl);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem parsing preferences!");
mUtil.writeToSysLogFile("SdServer.updatePrefs() - Error " + ex.toString());
@@ -1171,70 +1268,14 @@ public class SdServer extends Service implements SdDataReceiver {
}
- /**
- * 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 (mUtil.isExternalStorageWritable()) {
- try {
- FileWriter of = new FileWriter(getExternalFilesDir(null).toString()
- + "/" + fname, true);
- if (mSdData != null) {
- if (alarm) {
- //Log.v(TAG, "writeToSD() - logging mSdData.toString()");
- of.append(mSdData.toString() + "\n");
- } else {
- //Log.v(TAG, "writeToSD() - logging mSdData.toCSVString()");
- of.append(mSdData.toCSVString(true) + "\n");
- }
- }
- of.close();
- } catch (Exception ex) {
- Log.e(TAG, "writeAlarmToSD - error " + ex.toString());
- }
- } else {
- Log.e(TAG, "ERROR - Can not Write to External Folder");
- }
- }
-
-
public void sendPhoneAlarm() {
/**
* Use the separate OpenSeizureDetector Dialler app to generate a phone call alarm to the numbers selected for SMS Alarms.
*/
- Log.v(TAG,"sendPhoneAlarm() - sending broadcast intent");
+ Log.v(TAG, "sendPhoneAlarm() - sending broadcast intent");
Intent intent = new Intent();
intent.setAction("uk.org.openseizuredetector.dialler.ALARM");
- intent.putExtra("NUMBERS",mSMSNumbers);
+ intent.putExtra("NUMBERS", mSMSNumbers);
sendBroadcast(intent);
}
@@ -1245,6 +1286,7 @@ public class SdServer extends Service implements SdDataReceiver {
*/
public class SmsTimer extends CountDownTimer implements SdLocationReceiver {
public long mTimeLeft = -1;
+
public SmsTimer(long startTime, long interval) {
super(startTime, interval);
}
@@ -1254,21 +1296,26 @@ public class SdServer extends Service implements SdDataReceiver {
public void onFinish() {
Log.v(TAG, "SmsTimer.onFinish()");
mTimeLeft = 0;
- mLocationFinder.getLocation(this);
- Location loc = mLocationFinder.getLastLocation();
- if (loc != null) {
- mUtil.showToast(getString(R.string.send_sms_last_location)
- + loc.getLongitude() + ","
- + loc.getLatitude());
+ if (mLocationFinder != null) {
+ mLocationFinder.getLocation(this);
+ Location loc = mLocationFinder.getLastLocation();
+ if (loc != null) {
+ mUtil.showToast(getString(R.string.send_sms_last_location)
+ + loc.getLongitude() + ","
+ + loc.getLatitude());
+ } else {
+ Log.i(TAG, "SmsTimer.onFinish() - Last Location is Null so sending first SMS without location.");
+ }
} else {
- Log.i(TAG, "SmsTimer.onFinish() - Last Location is Null so sending first SMS without location.");
+ Log.e(TAG,"SmsTimer.onFinish - mLocationFinder is null - this should not happen!");
+ mUtil.showToast("SmsTimer.onFinish - mLocationFinder is null - this should not happen! - Please report this issue!");
}
Log.i(TAG, "SmsTimer.onFinish() - Sending to " + mSMSNumbers.length + " Numbers");
mUtil.writeToSysLogFile("SdServer.SmsTimer.onFinish()");
Time tnow = new Time(Time.getCurrentTimezone());
tnow.setToNow();
String dateStr = tnow.format("%H:%M:%S %d/%m/%Y");
- String shortUuidStr = mUuidStr.substring(mUuidStr.length()-6);
+ String shortUuidStr = mUuidStr.substring(mUuidStr.length() - 6);
// SmsManager sm = SmsManager.getDefault();
for (int i = 0; i < mSMSNumbers.length; i++) {
@@ -1289,7 +1336,6 @@ public class SdServer extends Service implements SdDataReceiver {
/**
* onSdLocationReceived - called with the best estimate location after mLocationReceiver times out.
*
- * @param ll - location (may be null if no location found)
*/
private void sendSMS(String phoneNo, String msgStr) {
Log.i(TAG, "sendSMS() - Sending to " + phoneNo);
@@ -1341,7 +1387,7 @@ public class SdServer extends Service implements SdDataReceiver {
+ ";u=" + df.format(ll.getAccuracy()) + "'>here";
String googleUrl = "https://www.google.com/maps/place?q="
+ ll.getLatitude() + "%2C" + ll.getLongitude();
- String shortUuidStr = mUuidStr.substring(mUuidStr.length()-6);
+ String shortUuidStr = mUuidStr.substring(mUuidStr.length() - 6);
String messageStr = mSMSMsgStr + " - " +
dateStr + " - " + googleUrl + " " + shortUuidStr;
@@ -1360,14 +1406,13 @@ public class SdServer extends Service implements SdDataReceiver {
}
-
}
/*
- * Latch alarm in alarm state for a given period (mLatchAlarmPeriod seconds) after the alarm is raised.
- * This is to ensure multiple Alarm annunciations even if only a single Alarm signal is received.
- */
+ * Latch alarm in alarm state for a given period (mLatchAlarmPeriod seconds) after the alarm is raised.
+ * This is to ensure multiple Alarm annunciations even if only a single Alarm signal is received.
+ */
private class LatchAlarmTimer extends CountDownTimer {
public LatchAlarmTimer(long startTime, long interval) {
super(startTime, interval);
@@ -1390,9 +1435,9 @@ public class SdServer extends Service implements SdDataReceiver {
}
/*
- * Temporary cancel audible alarms, for the period specified by the
- * CancelAudiblePeriod setting.
- */
+ * 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);
@@ -1438,13 +1483,13 @@ public class SdServer extends Service implements SdDataReceiver {
public void stopFaultTimer() {
if (mFaultTimer != null) {
- Log.v(TAG, "stopFaultTimer(): fault timer already running - cancelling it.");
+ //Log.v(TAG, "stopFaultTimer(): fault timer already running - cancelling it.");
mUtil.writeToSysLogFile("stopFaultTimer() - stopping fault timer");
mFaultTimer.cancel();
mFaultTimer = null;
mFaultTimerCompleted = false;
} else {
- Log.v(TAG, "stopFaultTimer(): fault timer not running - not doing anything.");
+ //Log.v(TAG, "stopFaultTimer(): fault timer not running - not doing anything.");
//mUtil.writeToSysLogFile("stopFaultTimer() - fault timer not running");
}
}
@@ -1476,4 +1521,211 @@ public class SdServer extends Service implements SdDataReceiver {
}
+
+ /**
+ * Start the events timer.
+ */
+ public void startEventsTimer() {
+ if (mEventsTimer != null) {
+ Log.v(TAG, "startEventsTimer(): timer already running - not doing anything.");
+ mUtil.writeToSysLogFile("startEventsTimer() - timer already running");
+ } else {
+ Log.v(TAG, "startEventsTimer(): starting timer.");
+ mUtil.writeToSysLogFile("startEventsTimer() - starting timer");
+ runOnUiThread(new Runnable() {
+ public void run() {
+ mEventsTimer =
+ // Run every 10 sec (convert to ms.)
+ new CheckEventsTimer(mEventsTimerPeriod * 1000, 1000);
+ mEventsTimer.mIsRunning = true;
+ mEventsTimer.start();
+ }
+ });
+ }
+ }
+
+ public void stopEventsTimer() {
+ if (mEventsTimer != null) {
+ Log.v(TAG, "stopEventsTimer(): timer already running - cancelling it.");
+ mUtil.writeToSysLogFile("stopEventsTimer() - stopping timer, setting mIsRunning to false");
+ mEventsTimer.mIsRunning = false;
+ mEventsTimer.cancel();
+ //mEventsTimer = null;
+ } else {
+ Log.v(TAG, "stopEventsTimer(): timer not running - not doing anything.");
+ }
+ }
+
+
+ private void checkEvents() {
+ // Retrieve events from remote database
+ if (mLm.mWac.getEvents((JSONObject remoteEventsObj) -> {
+ Log.v(TAG, "CheckEvents.getEvents.Callback()");
+ Boolean haveUnvalidatedEvent = false;
+ if (remoteEventsObj == null) {
+ Log.e(TAG, "CheckEvents.Callback: Error Retrieving events");
+ } else {
+ try {
+ JSONArray eventsArray = remoteEventsObj.getJSONArray("events");
+ // A bit of a hack to display in reverse chronological order
+ for (int i = eventsArray.length() - 1; i >= 0; i--) {
+ JSONObject eventObj = eventsArray.getJSONObject(i);
+ String typeStr = eventObj.getString("type");
+ //Log.v(TAG,"CheckEventsTimer: id="+id+", typeStr="+typeStr);
+ if (typeStr.equals("null") || typeStr.equals("")) {
+ haveUnvalidatedEvent = true;
+ //Log.v(TAG,"CheckEventsTimer:setting firstUnvalidatedEvent to "+firstUnvalidatedEvent);
+ }
+ }
+ Log.v(TAG, "CheckEventsTimer.onFinish.callback - haveUnvalidatedEvent = " +
+ haveUnvalidatedEvent);
+ if (haveUnvalidatedEvent) {
+ showEventNotification();
+ mNM.cancel(DATASHARE_NOTIFICATION_ID);
+ } else {
+ mNM.cancel(EVENT_NOTIFICATION_ID);
+ mNM.cancel(DATASHARE_NOTIFICATION_ID);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "CheckEventsTimer.onFinish(): Error Parsing remoteEventsObj: " + e.getMessage());
+ //mUtil.showToast("Error Parsing remoteEventsObj - this should not happen!!!");
+ }
+ }
+ })) {
+ Log.v(TAG, "CheckEventsTimer() - requested events");
+ } else {
+ Log.v(TAG, "CheckEventsTimer() - Not Logged In");
+ mNM.cancel(EVENT_NOTIFICATION_ID);
+ showDatashareNotification();
+ }
+
+ }
+
+ /**
+ * Periodically check if we have unvalidated events in the remote database.
+ * Show a notification if we do.
+ */
+ private class CheckEventsTimer extends CountDownTimer {
+ public boolean mIsRunning = true;
+
+ public CheckEventsTimer(long startTime, long interval) {
+ super(startTime, interval);
+ }
+
+ @Override
+ public void onFinish() {
+ Log.v(TAG, "CheckEventsTimer.onFinish()");
+ checkEvents();
+ if (mIsRunning) {
+ // Restart this timer.
+ Log.v(TAG, "CheckEventsTimer.onFinish() - mIsRunning is true, so re-starting timer");
+ start();
+ }
+ }
+
+ @Override
+ public void onTick(long msRemaining) {
+ }
+ }
+
+ /**
+ * Show a notification to tell the user that we have unvalidated events.
+ */
+ private void showEventNotification() {
+ Log.v(TAG, "showEventNotification()");
+ int iconId;
+ String titleStr;
+ Uri soundUri = null;
+
+ // Initialise Notification channel for API level 26 and over
+ // from https://stackoverflow.com/questions/44443690/notificationcompat-with-api-26
+ NotificationManager nM = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), mEventNotChId);
+ if (Build.VERSION.SDK_INT >= 26) {
+ NotificationChannel channel = new NotificationChannel(mEventNotChId,
+ mEventNotChName,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription(mEventNotChDesc);
+ nM.createNotificationChannel(channel);
+ }
+
+ iconId = R.drawable.datasharing_query_24x24;
+ titleStr = getString(R.string.unvalidatedEventsTitle);
+
+ Intent i = new Intent(getApplicationContext(), LogManagerControlActivity.class);
+ i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ i.setAction("None");
+ PendingIntent contentIntent =
+ PendingIntent.getActivity(getApplicationContext(),
+ 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ String contentStr = getString(R.string.please_confirm_seizure_events);
+
+ Notification notification = notificationBuilder.setContentIntent(contentIntent)
+ .setSmallIcon(iconId)
+ .setColor(0x00ffffff)
+ .setAutoCancel(false)
+ .setContentTitle(titleStr)
+ .setContentText(contentStr)
+ .setOnlyAlertOnce(true)
+ .build();
+ nM.notify(EVENT_NOTIFICATION_ID, notification);
+ }
+
+
+ /**
+ * Show a notification asking the user to set-up data sharing.
+ */
+ private void showDatashareNotification() {
+ Log.v(TAG, "showDatashareNotification()");
+ int iconId;
+ String titleStr;
+ Uri soundUri = null;
+
+ // Initialise Notification channel for API level 26 and over
+ // from https://stackoverflow.com/questions/44443690/notificationcompat-with-api-26
+ NotificationManager nM = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), mEventNotChId);
+ if (Build.VERSION.SDK_INT >= 26) {
+ NotificationChannel channel = new NotificationChannel(mEventNotChId,
+ mEventNotChName,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription(mEventNotChDesc);
+ nM.createNotificationChannel(channel);
+ }
+
+ iconId = R.drawable.datasharing_fault_24x24;
+ titleStr = getString(R.string.datasharing_notification_title);
+
+ Intent i = new Intent(getApplicationContext(), MainActivity.class);
+ i.putExtra("action", "showDataSharingDialog");
+ i.setAction("showDataSharingDialog");
+ i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ PendingIntent contentIntent =
+ PendingIntent.getActivity(getApplicationContext(),
+ 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ Intent loginIntent = new Intent(getApplicationContext(), AuthenticateActivity.class);
+ loginIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ PendingIntent loginPendingIntent =
+ PendingIntent.getActivity(getApplicationContext(),
+ 0, loginIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ String contentStr = getString(R.string.datasharing_notification_text);
+ Notification notification = notificationBuilder
+ .setContentIntent(contentIntent)
+ .setSmallIcon(iconId)
+ .setColor(0x00ffffff)
+ .setAutoCancel(false)
+ .setContentTitle(titleStr)
+ .setContentText(contentStr)
+ .setOnlyAlertOnce(true)
+ .addAction(R.drawable.common_google_signin_btn_icon_dark, getString(R.string.login), loginPendingIntent)
+ .setPriority(0)
+ .build();
+ nM.notify(DATASHARE_NOTIFICATION_ID, notification);
+ }
+
+
}
+
+
+
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java b/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java
index 2b0e182..eadebc1 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java
@@ -104,13 +104,18 @@ public class SdWebServer extends NanoHTTPD {
Log.v(TAG, " files=" + files.toString());
String postData = files.get("postData");
Log.v(TAG, " postData=" + postData);
- // Send the data to the SdDataSource so the app can pick it up.
- if (parameters.get("dataObj") != null) {
- Log.v(TAG,"passing parameters to data source");
- answer = mSdServer.mSdDataSource.updateFromJSON(parameters.get("dataObj").toString());
+ if (mSdServer.mSdDataSourceName.equals("Garmin")) {
+ // Send the data to the SdDataSource so the app can pick it up.
+ if (parameters.get("dataObj") != null) {
+ Log.v(TAG, "passing parameters to data source");
+ answer = mSdServer.mSdDataSource.updateFromJSON(parameters.get("dataObj").toString());
+ } else {
+ Log.v(TAG, "Passing postData to data source");
+ answer = mSdServer.mSdDataSource.updateFromJSON(files.get("postData"));
+ }
} else {
- Log.v(TAG,"Passing postData to data source");
- answer = mSdServer.mSdDataSource.updateFromJSON(files.get("postData"));
+ Log.i(TAG,"Web server received data, but datasource is not set to 'Garmin' - Ignoring");
+ mUtil.showToast("Web server received data, but datasource is not set to 'Garmin' - Ignoring");
}
break;
default:
@@ -202,7 +207,7 @@ public class SdWebServer extends NanoHTTPD {
} else {
Log.v(TAG, "WebServer.serve() - Unknown uri -" +
uri);
- answer = "{'msg' : 'Unknown URI: '}";
+ answer = "{'msg' : 'Unknown URI: "+uri+"'}";
}
}
res = new NanoHTTPD.Response(answer);
diff --git a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java
index 4c86409..78eba5b 100644
--- a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java
+++ b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java
@@ -24,20 +24,22 @@
*/
package uk.org.openseizuredetector;
-import android.app.Activity;
-import android.app.AlertDialog;
+import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.graphics.Color;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.PowerManager;
import android.preference.PreferenceManager;
+import android.text.Html;
import android.text.SpannableString;
-import android.text.SpannedString;
import android.text.util.Linkify;
import android.util.Log;
import android.view.View;
@@ -46,6 +48,12 @@ import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.text.HtmlCompat;
+
import com.rohitss.uceh.UCEHandler;
import java.util.Timer;
@@ -56,7 +64,7 @@ import java.util.TimerTask;
* for it to start and to receive data and settings from the seizure detector before exiting and
* starting the main activity.
*/
-public class StartupActivity extends Activity {
+public class StartupActivity extends AppCompatActivity {
private static String TAG = "StartupActivity";
private int okColour = Color.BLUE;
private int warnColour = Color.MAGENTA;
@@ -74,11 +82,42 @@ public class StartupActivity extends Activity {
private Handler mHandler = new Handler(); // used to update ui from mUiTimer
private boolean mUsingPebbleDataSource = true;
private String mPebbleAppPackageName = null;
+ private boolean mBatteryOptDialogDisplayed = false;
+ private AlertDialog mBatteryOptDialog;
+ private boolean mLocationPermissions1Requested;
+ private boolean mLocationPermissions2Requested;
+ private boolean mSmsPermissionsRequested;
+ private boolean mPermissionsRequested;
+
+ public final String[] REQUIRED_PERMISSIONS = {
+ //Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.WAKE_LOCK,
+ };
+
+ public final String[] SMS_PERMISSIONS_1 = {
+ Manifest.permission.SEND_SMS,
+ Manifest.permission.READ_PHONE_STATE,
+ };
+
+ public final String[] LOCATION_PERMISSIONS_1 = {
+ Manifest.permission.SEND_SMS,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ //Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ Manifest.permission.READ_PHONE_STATE,
+ };
+
+ public final String[] LOCATION_PERMISSIONS_2 = {
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ };
+
+
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Log.i(TAG,"onCreate()");
+ Log.i(TAG, "onCreate()");
+ setContentView(R.layout.startup_activity);
// Set our custom uncaught exception handler to report issues.
//Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(StartupActivity.this));
@@ -86,20 +125,6 @@ public class StartupActivity extends Activity {
.addCommaSeparatedEmailAddresses("crashreports@openseizuredetector.org.uk,")
.build();
-
- mHandler = new Handler();
- mUtil = new OsdUtil(this, mHandler);
- mUtil.writeToSysLogFile("");
- mUtil.writeToSysLogFile("*******************************");
- mUtil.writeToSysLogFile("* StartUpActivity Started *");
- mUtil.writeToSysLogFile("*******************************");
-
- // Force the screen to stay on when the app is running
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-
- setContentView(R.layout.startup_activity);
-
// Read the default settings from the xml preferences files, so we do
// not have to use the hard coded ones in the java files.
PreferenceManager.setDefaultValues(this, R.xml.alarm_prefs, true);
@@ -108,6 +133,17 @@ public class StartupActivity extends Activity {
PreferenceManager.setDefaultValues(this, R.xml.pebble_datasource_prefs, true);
PreferenceManager.setDefaultValues(this, R.xml.seizure_detector_prefs, true);
PreferenceManager.setDefaultValues(this, R.xml.network_passive_datasource_prefs, true);
+ PreferenceManager.setDefaultValues(this, R.xml.logging_prefs, true);
+
+ mHandler = new Handler();
+ mUtil = new OsdUtil(getApplicationContext(), mHandler);
+ mUtil.writeToSysLogFile("");
+ mUtil.writeToSysLogFile("*******************************");
+ mUtil.writeToSysLogFile("* StartUpActivity Started *");
+ mUtil.writeToSysLogFile("*******************************");
+
+ // Force the screen to stay on when the app is running
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Button b;
@@ -142,24 +178,16 @@ public class StartupActivity extends Activity {
}
});
- mConnection = new SdServiceConnection(this);
+ mConnection = new SdServiceConnection(getApplicationContext());
}
@Override
protected void onStart() {
super.onStart();
- Log.i(TAG,"onStart()");
+ Log.i(TAG, "onStart()");
mUtil.writeToSysLogFile("StartupActivity.onStart()");
TextView tv;
- if (mUtil.arePermissionsOK()) {
- Log.i(TAG,"onStart() - Permissions OK");
- } else {
- Log.i(TAG,"onStart() - Permissions Not OK - requesting them");
- mUtil.requestPermissions(this);
- }
-
-
String versionName = mUtil.getAppVersionName();
tv = (TextView) findViewById(R.id.appNameTv);
tv.setText("OpenSeizureDetector V" + versionName);
@@ -174,18 +202,36 @@ public class StartupActivity extends Activity {
if (mUtil.isServerRunning()) {
- Log.i(TAG, "onStart() - server running - stopping it");
+ Log.i(TAG, "onStart() - server running - stopping it - isServerRunning="+mUtil.isServerRunning());
mUtil.writeToSysLogFile("StartupActivity.onStart() - server already running - stopping it.");
mUtil.stopServer();
+ } else {
+ Log.i(TAG, "onStart() - server not running - isServerRunning="+mUtil.isServerRunning());
+ }
+ // Wait 0.1 second to give the server chance to shutdown in case we have just shut it down below, then start it
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ mUtil.writeToSysLogFile("StartupActivity.onStart() - starting server after delay - isServerRunning="+mUtil.isServerRunning());
+ Log.i(TAG, "onStart() - starting server after delay -isServerRunning="+mUtil.isServerRunning());
+ mUtil.startServer();
+ // Bind to the service.
+ Log.i(TAG, "onStart() - binding to server");
+ mUtil.writeToSysLogFile("StartupActivity.onStart() - binding to server");
+ mUtil.bindToServer(getApplicationContext(), mConnection);
+ }
+ }, 100);
+
+ // Check power management settings
+ PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isIgnoringBatteryOptimizations(getPackageName())) {
+ Log.i(TAG, "Power Management OK - we are ignoring Battery Optimizations");
+ mBatteryOptDialogDisplayed = false;
+ } else {
+ Log.e(TAG, "Power Management Problem - not ignoring Battery Optimisations");
+ //mUtil.showToast("WARNING - Phone is Optimising OpenSeizureDetector Battery Usage - this is likely to prevent it working correctly when running on battery!");
+ if (!mBatteryOptDialogDisplayed) showBatteryOptimisationWarningDialog();
}
- mUtil.writeToSysLogFile("StartupActivity.onStart() - starting server");
- Log.i(TAG,"onStart() - starting server");
- mUtil.startServer();
- // Bind to the service.
- Log.i(TAG,"onStart() - binding to server");
- mUtil.writeToSysLogFile("StartupActivity.onStart() - binding to server");
- mUtil.bindToServer(this, mConnection);
// Check to see if this is the first time the app has been run, and display welcome dialog if it is.
checkFirstRun();
@@ -198,7 +244,7 @@ public class StartupActivity extends Activity {
mHandler.post(serverStatusRunnable);
//updateServerStatus();
}
- }, 0, 1000);
+ }, 0, 2000);
}
@@ -208,17 +254,17 @@ public class StartupActivity extends Activity {
super.onStop();
Log.i(TAG, "onStop() - unbinding from server");
mUtil.writeToSysLogFile("StartupActivity.onStop() - unbinding from server");
- mUtil.unbindFromServer(this, mConnection);
+ mUtil.unbindFromServer(getApplicationContext(), mConnection);
mUiTimer.cancel();
}
/*
- * serverStatusRunnable - called by updateServerStatus - updates the
- * user interface to reflect the current status received from the server.
- * If everything is ok, we close this activity and open the main user interface
- * activity.
- */
+ * serverStatusRunnable - called by updateServerStatus - updates the
+ * user interface to reflect the current status received from the server.
+ * If everything is ok, we close this activity and open the main user interface
+ * activity.
+ */
final Runnable serverStatusRunnable = new Runnable() {
public void run() {
Boolean allOk = true;
@@ -227,23 +273,49 @@ public class StartupActivity extends Activity {
boolean smsAlarmsActive = true;
boolean phoneAlarmsActive = true;
- Log.v(TAG,"serverStatusRunnable()");
+ Log.v(TAG, "serverStatusRunnable()");
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
smsAlarmsActive = SP.getBoolean("SMSAlarm", false);
phoneAlarmsActive = SP.getBoolean("PhoneCallAlarm", false);
+ // Check power management settings
+ PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isIgnoringBatteryOptimizations(getPackageName())) {
+ Log.i(TAG, "Power Management OK - we are ignoring Battery Optimizations");
+ if (mBatteryOptDialogDisplayed) {
+ mBatteryOptDialog.cancel();
+ mBatteryOptDialogDisplayed = false;
+ }
+ }
+
// Settings ok
tv = (TextView) findViewById(R.id.textItem1);
pb = (ProgressBar) findViewById(R.id.progressBar1);
- if (mUtil.arePermissionsOK()) {
- if (smsAlarmsActive && !mUtil.areSMSPermissionsOK()) {
+ if (arePermissionsOK()) {
+ if (smsAlarmsActive && !areSMSPermissions1OK()) {
+ Log.i(TAG,"SMS permissions NOT OK");
tv.setText(getString(R.string.SmsPermissionWarning));
- tv.setBackgroundColor(okColour);
- tv.setTextColor(okTextColour);
- pb.setIndeterminateDrawable(getResources().getDrawable(R.drawable.start_server));
- pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server));
- mUtil.requestSMSPermissions(StartupActivity.this);
+ tv.setBackgroundColor(alarmColour);
+ tv.setTextColor(alarmTextColour);
+ //pb.setIndeterminateDrawable(getResources().getDrawable(R.drawable.start_server));
+ //pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server));
+ requestSMSPermissions();
+ allOk = false;
+ } else if (smsAlarmsActive && !areLocationPermissions1OK()) {
+ Log.i(TAG,"Location permissions NOT OK");
+ tv.setText(getString(R.string.SmsPermissionWarning));
+ tv.setBackgroundColor(alarmColour);
+ tv.setTextColor(alarmTextColour);
+ requestLocationPermissions1();
+ allOk = false;
+ } else if (smsAlarmsActive && !areLocationPermissions2OK()) {
+ Log.i(TAG,"SMS permissions2 NOT OK");
+ tv.setText(getString(R.string.SmsPermissionWarning));
+ tv.setBackgroundColor(alarmColour);
+ tv.setTextColor(alarmTextColour);
+ requestLocationPermissions2();
+ allOk = false;
} else {
tv.setText(getString(R.string.AppPermissionsOk));
tv.setBackgroundColor(okColour);
@@ -257,7 +329,7 @@ public class StartupActivity extends Activity {
tv.setTextColor(alarmTextColour);
pb.setIndeterminate(true);
allOk = false;
- mUtil.requestPermissions(StartupActivity.this);
+ requestPermissions(StartupActivity.this);
}
// If phone alarms are selected, we need to have the uk.org.openseizuredetector.dialler package installed to do the actual dialling.
@@ -305,7 +377,6 @@ public class StartupActivity extends Activity {
}
-
// Do we have seizure detector data?
tv = (TextView) findViewById(R.id.textItem5);
pb = (ProgressBar) findViewById(R.id.progressBar5);
@@ -342,11 +413,10 @@ public class StartupActivity extends Activity {
}
-
// If all the parameters are ok, close this activity and open the main
// user interface activity instead.
if (allOk) {
- if (!mDialogDisplayed) {
+ if (!mDialogDisplayed && !mBatteryOptDialogDisplayed) {
if (!mStartedMainActivity) {
Log.i(TAG, "serverStatusRunnable() - starting main activity...");
mUtil.writeToSysLogFile("StartupActivity.serverStatusRunnable - all checks ok - starting main activity.");
@@ -393,42 +463,65 @@ public class StartupActivity extends Activity {
}
}
- /**
- * checkFirstRun - checks to see if this is the first run of the app after installation or upgrade.
- * if it is, the relevant dialog message is displayed. If not, the routine just exists so start-up can continue.
- */
+ /**
+ * checkFirstRun - checks to see if this is the first run of the app after installation or upgrade.
+ * if it is, the relevant dialog message is displayed. If not, the routine just exists so start-up can continue.
+ */
public void checkFirstRun() {
String storedVersionName = "";
String versionName;
AlertDialog UpdateDialog;
AlertDialog FirstRunDialog;
SharedPreferences prefs;
- Log.i(TAG,"checkFirstRun()");
+ Log.i(TAG, "checkFirstRun()");
versionName = this.getVersionName(this, StartupActivity.class);
prefs = PreferenceManager.getDefaultSharedPreferences(this);
storedVersionName = (prefs.getString("AppVersionName", null));
- Log.v(TAG,"storedVersionName="+storedVersionName+", versionName="+versionName);
+ Log.v(TAG, "storedVersionName=" + storedVersionName + ", versionName=" + versionName);
// CHeck for new installation
+ //storedVersionName = null; // FIXME Force first run dialog for easier testing ****************************
if (storedVersionName == null || storedVersionName.length() == 0) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
this);
- final SpannableString s = new SpannableString(
- getString(R.string.FirstRunDlgMsg)+getString(R.string.changelog)
- );
- // This makes the links display as links, but they do not respond to clicks for some reason...
- Linkify.addLinks(s, Linkify.ALL);
+ final String s = new String(
+ getString(R.string.FirstRunDlgMsg));
alertDialogBuilder
.setTitle(getString(R.string.FirstRunDlgTitle))
- .setMessage(s)
+ .setMessage(Html.fromHtml(s))
.setCancelable(false)
- .setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
+ .setNeutralButton(getString(R.string.closeBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
mDialogDisplayed = false;
//MainActivity.this.finish();
}
- });
+ })
+ .setPositiveButton("Privacy Policy", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ mDialogDisplayed = false;
+ String url = OsdUtil.PRIVACY_POLICY_URL;
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ startActivity(i);
+ dialog.cancel();
+ mDialogDisplayed = false;
+ }
+ })
+ .setNegativeButton("Data Sharing", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ mDialogDisplayed = false;
+ String url = OsdUtil.DATA_SHARING_URL;
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ startActivity(i);
+ dialog.cancel();
+ mDialogDisplayed = false;
+ }
+ })
+ ;
FirstRunDialog = alertDialogBuilder.create();
Log.i(TAG, "Displaying First Run Dialog");
FirstRunDialog.show();
@@ -437,19 +530,43 @@ public class StartupActivity extends Activity {
// Check for update of installed application
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
this);
- final SpannableString s = new SpannableString(
- getString(R.string.UpgradeMsg)+getString(R.string.changelog)
+ final String s = new String(
+ getString(R.string.UpgradeMsg) + getString(R.string.changelog)
);
- // This makes the links display as links, but they do not respond to clicks for some reason...
- Linkify.addLinks(s, Linkify.ALL);
+
alertDialogBuilder
.setTitle(getString(R.string.UpdateDialogTitleTxt))
- .setMessage(s)
+ .setMessage(Html.fromHtml(s))
.setCancelable(false)
- .setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
+ .setNeutralButton(getString(R.string.closeBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
mDialogDisplayed = false;
+ //MainActivity.this.finish();
+ }
+ })
+ .setPositiveButton("Privacy Policy", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ mDialogDisplayed = false;
+ String url = OsdUtil.PRIVACY_POLICY_URL;
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ startActivity(i);
+ dialog.cancel();
+ mDialogDisplayed = false;
+ }
+ })
+ .setNegativeButton("Data Sharing", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ mDialogDisplayed = false;
+ String url = OsdUtil.DATA_SHARING_URL;
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ startActivity(i);
+ dialog.cancel();
+ mDialogDisplayed = false;
}
});
UpdateDialog = alertDialogBuilder.create();
@@ -463,4 +580,202 @@ public class StartupActivity extends Activity {
prefs.edit().putString("AppVersionName", versionName).commit();
}
+ private void showBatteryOptimisationWarningDialog() {
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
+ this);
+ final SpannableString s = new SpannableString(
+ getString(R.string.battery_usage_optimisation_dialog_text)
+ );
+ // This makes the links display as links, but they do not respond to clicks for some reason...
+ Linkify.addLinks(s, Linkify.ALL);
+ alertDialogBuilder
+ .setTitle(R.string.battery_usage_optimisation_dialog_title)
+ .setMessage(s)
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ mBatteryOptDialogDisplayed = false;
+ }
+ });
+ mBatteryOptDialog = alertDialogBuilder.create();
+ Log.i(TAG, "Displaying Update Dialog");
+ mBatteryOptDialog.show();
+ mBatteryOptDialogDisplayed = true;
+ }
+
+ /*****************************************************************************/
+ public boolean arePermissionsOK() {
+ boolean allOk = true;
+ Log.v(TAG, "arePermissionsOK");
+ for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) {
+ if (ContextCompat.checkSelfPermission(this, REQUIRED_PERMISSIONS[i])
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.i(TAG, REQUIRED_PERMISSIONS[i] + " Permission Not Granted");
+ allOk = false;
+ }
+ }
+ return allOk;
+ }
+
+ public boolean areSMSPermissions1OK() {
+ boolean allOk = true;
+ Log.v(TAG, "areSMSPermissions1 OK()");
+ for (int i = 0; i < SMS_PERMISSIONS_1.length; i++) {
+ if (ContextCompat.checkSelfPermission(this, SMS_PERMISSIONS_1[i])
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.i(TAG, "areSMSPermissions1OK: "+SMS_PERMISSIONS_1[i] + " Permission Not Granted");
+ allOk = false;
+ }
+ }
+ return allOk;
+ }
+
+
+ public boolean areLocationPermissions1OK() {
+ boolean allOk = true;
+ Log.v(TAG, "areLocationPermissions1 OK()");
+ for (int i = 0; i < LOCATION_PERMISSIONS_1.length; i++) {
+ if (ContextCompat.checkSelfPermission(this, LOCATION_PERMISSIONS_1[i])
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.i(TAG, LOCATION_PERMISSIONS_1[i] + " Permission Not Granted");
+ allOk = false;
+ }
+ }
+ return allOk;
+ }
+
+ public boolean areLocationPermissions2OK() {
+ boolean allOk = true;
+ Log.v(TAG, "areSMSPermissions2OK()");
+ for (int i = 0; i < LOCATION_PERMISSIONS_2.length; i++) {
+ if (ContextCompat.checkSelfPermission(this, LOCATION_PERMISSIONS_2[i])
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.i(TAG, LOCATION_PERMISSIONS_2[i] + " Permission Not Granted");
+ allOk = false;
+ }
+ }
+ return allOk;
+ }
+
+ public void requestPermissions(AppCompatActivity activity) {
+ if (mPermissionsRequested) {
+ Log.i(TAG, "requestPermissions() - request already sent - not doing anything");
+ } else {
+ Log.i(TAG, "requestPermissions() - requesting permissions");
+ for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
+ REQUIRED_PERMISSIONS[i])) {
+ Log.i(TAG, "shouldShowRationale for permission" + REQUIRED_PERMISSIONS[i]);
+ }
+ }
+ ActivityCompat.requestPermissions(activity,
+ REQUIRED_PERMISSIONS,
+ 42);
+ mPermissionsRequested = true;
+ }
+ }
+
+ public void requestSMSPermissions() {
+ if (mSmsPermissionsRequested) {
+ Log.i(TAG, "requestSMSPermissions() - request already sent - not doing anything");
+ } else {
+ Log.i(TAG, "requestSMSPermissions() - requesting permissions");
+ mSmsPermissionsRequested = true;
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
+ this);
+ alertDialogBuilder
+ .setTitle(R.string.permissions_required)
+ .setMessage(R.string.sms_permissions_rationale_1)
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ Log.i(TAG,"requestSMSPermissions(): Launching ActivityCompat.requestPermissions()");
+ ActivityCompat.requestPermissions(StartupActivity.this,
+ SMS_PERMISSIONS_1,
+ 45);
+ }
+ })
+ .setNegativeButton(getString(R.string.cancelBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ }).create().show();
+ }
+ }
+
+
+ public void requestLocationPermissions1() {
+ if (mLocationPermissions1Requested) {
+ Log.i(TAG, "requestLocationPermissions1() - request already sent - not doing anything");
+ } else {
+ Log.i(TAG, "requestLocationPermissions1() - requesting permissions");
+ mLocationPermissions1Requested = true;
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
+ this);
+ alertDialogBuilder
+ .setTitle(R.string.permissions_required)
+ .setMessage(R.string.location_permissions_rationale_1)
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ Log.i(TAG, "requestLocationPermissions1(): Launching ActivityCompat.requestPermissions()");
+ ActivityCompat.requestPermissions(StartupActivity.this,
+ LOCATION_PERMISSIONS_1,
+ 43);
+ }
+ })
+ .setNegativeButton(getString(R.string.cancelBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ })
+ .create().show();
+ }
+ }
+
+ public void requestLocationPermissions2() {
+ if (mLocationPermissions2Requested) {
+ Log.i(TAG, "requestSMSPermissions2() - request already sent - not doing anything");
+ } else {
+ Log.i(TAG, "requestSMSPermissions2() - requesting permissions");
+ mLocationPermissions2Requested = true;
+
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
+ this);
+ alertDialogBuilder
+ .setTitle(R.string.permissions_required)
+ .setMessage(R.string.location_permissions_2_rationale)
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ Log.i(TAG,"requestSMSPermissions(): Launching ActivityCompat.requestPermissions()");
+ ActivityCompat.requestPermissions(StartupActivity.this,
+ LOCATION_PERMISSIONS_2,
+ 44);
+ }
+ })
+ .setNegativeButton(getString(R.string.cancelBtnTxt), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ }).create().show();
+ }
+ }
+
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ String permissions[], int[] grantResults) {
+ Log.i(TAG, "onRequestPermissionsResult - Permission" + permissions + " = " + grantResults);
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ for (int i = 0; i < permissions.length; i++) {
+ Log.i(TAG, "Permission " + permissions[i] + " = " + grantResults[i]);
+ }
+ }
+
+
}
diff --git a/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java
new file mode 100644
index 0000000..b261bec
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java
@@ -0,0 +1,234 @@
+package uk.org.openseizuredetector;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.VolleyLog;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.toolbox.Volley;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import com.google.android.gms.tasks.Task;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.DocumentReference;
+import com.google.firebase.firestore.DocumentSnapshot;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.Query;
+import com.google.firebase.firestore.QueryDocumentSnapshot;
+import com.google.firebase.firestore.QuerySnapshot;
+import com.google.firebase.firestore.core.OrderBy;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+// This class is intended to handle all interactions with the OSD WebAPI
+public abstract class WebApiConnection {
+ protected Context mContext;
+ protected OsdUtil mUtil;
+ private String TAG = "WebApiConnection";
+ private String mAuthToken;
+
+
+ public interface JSONObjectCallback {
+ public void accept(JSONObject retValObj);
+ }
+
+ public interface StringCallback {
+ public void accept(String retValStr);
+ }
+
+ public interface LongCallback {
+ public void accept(Long retVal);
+ }
+
+ public WebApiConnection(Context context) {
+ mContext = context;
+ mUtil = new OsdUtil(mContext, new Handler());
+ }
+
+ public void close() {
+ Log.i(TAG, "stop()");
+ }
+
+ public abstract boolean isLoggedIn();
+
+
+ // Create a new event in the remote database, based on the provided parameters.
+ // passes the newly created documentId to function callback on successful completion, or null on error.
+ public abstract boolean createEvent(final int osdAlarmState, final Date eventDate, final String type, final String subType,
+ final String eventDesc, final String dataJSON, StringCallback callback);
+
+ // calls function callback with a JSONObject representation of the event with id 'eventId'
+ public abstract boolean getEvent(String eventId, JSONObjectCallback callback);
+
+
+ /**
+ * Retrieve all events accessible to the logged in user, and pass them to the callback function as a JSONObject
+ *
+ * @param callback
+ * @return true on success or false on failure to initiate the request.
+ */
+ public abstract boolean getEvents(JSONObjectCallback callback);
+
+ public abstract boolean updateEvent(final JSONObject eventObj, JSONObjectCallback callback);
+
+ public abstract boolean createDatapoint(JSONObject dataObj, String eventId, StringCallback callback);
+
+ /**
+ * Retrieve the file containing the standard event types from the server.
+ * Calls the specified callback function, passing a JSONObject as a parameter when the data has been received and parsed.
+ *
+ * @return true if request sent successfully or else false.
+ */
+ public abstract boolean getEventTypes(JSONObjectCallback callback);
+
+
+ /**
+ * Retrieve a trivial file from the server to check we have a good server connection.
+ * sets mServerConnectionOk.
+ *
+ * @return true if request sent successfully or else false.
+ */
+ public abstract boolean checkServerConnection();
+
+ public abstract boolean getUserProfile(JSONObjectCallback callback);
+
+
+ public boolean authenticate(final String uname, final String passwd, StringCallback callback) {
+ Log.e(TAG, "WebApiConnection.authenticate(username, password, callback) Not Implemented");
+ return false;
+ }
+
+ // Remove the stored token so future calls are not authenticated.
+ public void logout() {
+ Log.v(TAG, "logout()");
+ setStoredToken(null);
+ }
+
+ protected void setStoredToken(String authToken) {
+ mAuthToken = authToken;
+ }
+
+ protected String getStoredToken() {
+ return (mAuthToken);
+ }
+
+
+ /**
+ * Mark all of the events with IDs contained in eventList as unknown type.
+ * @param eventList list of String IDs of the events to mark as unknown.
+ * @return true if request sent successfully or false.
+ */
+ private boolean markEventsAsUnknown(ArrayListeventList) {
+ if (eventList.size()>0) {
+ Log.i(TAG,"markEventsAsUnknown - eventList.size()="+eventList.size());
+ Log.i(TAG,"markEventsAsUnknown - eventList(0) = "+eventList.get(0));
+ getEvent(eventList.get(0), new WebApiConnection.JSONObjectCallback() {
+ @Override
+ public void accept(JSONObject eventObj) {
+ Log.v(TAG, "markEventsAsUnknown.getEvent.callback: "+eventObj);
+ if (eventObj != null) {
+ Log.v(TAG, "markEventsAsUnknown.getEvent.callback: eventObj=" + eventObj.toString());
+ try {
+ eventObj.put("type", "Unknown");
+ String notesStr = eventObj.getString("desc");
+ if (notesStr == null) notesStr = new String("");
+ notesStr = notesStr + " Set to Unknown automatically by OSD Android App";
+ eventObj.put("desc", notesStr);
+ updateEvent(eventObj,new WebApiConnection.JSONObjectCallback() {
+ @Override
+ public void accept(JSONObject eventObj) {
+ if (eventObj != null) {
+ Log.i(TAG, "markEventsAsUnknown.updateEvent.callback" + eventObj.toString());
+ // Remove the first item from the list,then call this whole procedure again to modify the next one on the list.
+ eventList.remove(0);
+ markEventsAsUnknown(eventList);
+ } else {
+ Log.e(TAG, "markEventsAsUnknown.updateEvent.callback - eventObj is null");
+ mUtil.showToast("markEventsAsUnknown.updateEvent.callback - eventObj is null");
+ }
+ }
+ });
+ } catch (JSONException e) {
+ Log.e(TAG,"markEventsAsUnknown.getEvent.callback: Error editing eventObj");
+ mUtil.showToast("markEventsAsUnknown.getEvent.callback: Error editing eventObj");
+ }
+ } else {
+ mUtil.showToast("Failed to Retrieve Event from Remote Database");
+ return;
+ }
+ }
+ });
+ } else {
+ Log.i(TAG,"markEventsAsUnknown(): No more events to Modify");
+ mUtil.showToast("No more unvalidated events to modify.");
+
+ }
+ return(true);
+ }
+
+ /**
+ * Mark all unverified events in the remote database as unknown
+ *
+ * @return true if request is successful or false.
+ */
+ public boolean markUnverifiedEventsAsUnknown() {
+ if (getEvents((JSONObject remoteEventsObj) -> {
+ Log.v(TAG, "markUnverifiedEventsAsUnknown.getEvents.Callback()");
+ Boolean haveUnvalidatedEvent = false;
+ if (remoteEventsObj == null) {
+ Log.e(TAG, "markUnverifiedEventsAsUnknown.getEvents.Callback: Error Retrieving events");
+ } else {
+ try {
+ JSONArray eventsArray = remoteEventsObj.getJSONArray("events");
+ ArrayList unvalidatedEventsList = new ArrayList();
+ for (int i = eventsArray.length() - 1; i >= 0; i--) {
+ JSONObject eventObj = eventsArray.getJSONObject(i);
+ String typeStr = eventObj.getString("type");
+ if (typeStr.equals("null") || typeStr.equals("")) {
+ haveUnvalidatedEvent = true;
+ unvalidatedEventsList.add(eventObj.getString("id"));
+ }
+ }
+ Log.v(TAG, "markUnverifiedEventsAsUnknown.getEvents.onFinish.callback - haveUnvalidatedEvent = " +
+ haveUnvalidatedEvent);
+ markEventsAsUnknown(unvalidatedEventsList);
+
+ } catch (JSONException e) {
+ Log.e(TAG, "markUnverifiedEventsAsUnknown.getEvents.onFinish(): Error Parsing remoteEventsObj: " + e.getMessage());
+ //mUtil.showToast("Error Parsing remoteEventsObj - this should not happen!!!");
+ }
+ }
+ })) {
+ Log.v(TAG, "markUnverifiedEventsAsUnknown.getEvents - requested events");
+ } else {
+ Log.v(TAG, "markUnverifiedEventsAsUnknown.getEvents - Not Logged In");
+
+ }
+
+
+ return (true);
+ }
+
+}
diff --git a/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_firebase.java b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_firebase.java
new file mode 100644
index 0000000..426f2ea
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_firebase.java
@@ -0,0 +1,426 @@
+package uk.org.openseizuredetector;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.Volley;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import com.google.android.gms.tasks.Task;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.DocumentReference;
+import com.google.firebase.firestore.DocumentSnapshot;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.Query;
+import com.google.firebase.firestore.QueryDocumentSnapshot;
+import com.google.firebase.firestore.QuerySnapshot;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+// This class is intended to handle all interactions with the OSD WebAPI
+public class WebApiConnection_firebase extends WebApiConnection {
+ public String retVal;
+ public int retCode;
+ public boolean mServerConnectionOk = false;
+ private String TAG = "WebApiConnection_firebase";
+ private String mAuthToken;
+ private Context mContext;
+ private OsdUtil mUtil;
+ FirebaseFirestore mDb;
+
+ RequestQueue mQueue;
+
+
+ public WebApiConnection_firebase(Context context) {
+ super(context);
+ loginToFirebase();
+ }
+
+ public void loginToFirebase() {
+ // Check if we are already logged in
+ FirebaseAuth auth = FirebaseAuth.getInstance();
+ mDb = FirebaseFirestore.getInstance();
+ if (auth != null) {
+ if (auth.getCurrentUser() != null) {
+ Log.i(TAG, "Firebase Logged in OK -" + auth.getCurrentUser().getDisplayName());
+ } else {
+ Log.e(TAG, "Firebase not logged in - no current user");
+ }
+ } else {
+ Log.e(TAG, "Firebase not logged in");
+ }
+ }
+
+ public void close() {
+ Log.i(TAG, "stop()");
+ mQueue.stop();
+ }
+
+ public boolean isLoggedIn() {
+ FirebaseAuth auth = FirebaseAuth.getInstance();
+ if (auth != null) {
+ if (auth.getCurrentUser() != null) {
+ //Log.v(TAG, "isLoggedIn(): Firebase Logged in OK");
+ return (true);
+ } else {
+ //Log.v(TAG, "isLoggedIn(): Current user is null - Firebase not logged in");
+ return (false);
+ }
+ } else {
+ //Log.v(TAG, "isLoggedIn(): Firebase not logged in");
+ return (false);
+ }
+ }
+
+ public boolean getUserProfile(JSONObjectCallback callback) {
+ Log.v(TAG, "getUserProfile()");
+ FirebaseAuth auth = FirebaseAuth.getInstance();
+ if (!isLoggedIn()) {
+ Log.v(TAG, "not logged in - doing nothing");
+ return (false);
+ } else {
+ try {
+ JSONObject retObj = new JSONObject();
+ retObj.put("id",auth.getCurrentUser().getUid());
+ retObj.put("username", auth.getCurrentUser().getDisplayName());
+ retObj.put("email", auth.getCurrentUser().getEmail());
+ callback.accept(retObj);
+ } catch (JSONException e) {
+ Log.e(TAG, "Error Creating retObjObj: " + e.getMessage());
+ mUtil.showToast("Error Creating retObj - this should not happen!!!");
+ return (false);
+ }
+ }
+ return (true);
+ }
+
+ public String getStoredToken() {
+ return null;
+ }
+
+ public void setStoredToken(String s) {
+ return;
+ }
+
+
+ // Create a new event in the remote database, based on the provided parameters.
+ // passes the newly created documentId to function callback on successful completion, or null on error.
+ public boolean createEvent(final int osdAlarmState, final Date eventDate, final String type, final String subType,
+ final String eventDesc, final String dataJSON, StringCallback callback) {
+ // FIXME - save type, subtype, eventDesc and dataJSON
+ Log.v(TAG, "createEvent()");
+ String userId = null;
+
+ if (mDb == null) {
+ Log.w(TAG, "createEvent() - mDb is null - not doing anything");
+ return false;
+ }
+
+ if (FirebaseAuth.getInstance().getCurrentUser() == null) {
+ Log.e(TAG, "ERROR: createEvent() - not logged in");
+ return false;
+ } else {
+ userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
+ }
+ Map event = new HashMap<>();
+ event.put("dataTime", eventDate.getTime());
+ event.put("osdAlarmState", osdAlarmState);
+ event.put("desc", eventDesc);
+ event.put("type", type);
+ event.put("subType", subType);
+ event.put("dataJSON", dataJSON);
+ event.put("userId", userId);
+
+ mDb.collection("Events")
+ .add(event)
+ .addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(DocumentReference documentReference) {
+ Log.d(TAG, "createEvent.onSuccess() - DocumentSnapshot added with ID: " + documentReference.getId());
+ mServerConnectionOk = true;
+ callback.accept(documentReference.getId());
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ Log.w(TAG, "createEvent.onFailure() - Error adding document", e);
+ callback.accept(null);
+ }
+ });
+ return (true);
+ }
+
+ // calls function callback with a JSONObject representation of the event with id 'eventId'
+ public boolean getEvent(String eventId, JSONObjectCallback callback) {
+ Log.v(TAG, "getEvent()");
+ if (mDb == null) {
+ Log.w(TAG, "getEvent() - mDb is null - not doing anything");
+ return false;
+ }
+
+ DocumentReference docRef = mDb
+ .collection("Events").document(eventId);
+ docRef.get().addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ DocumentSnapshot document = task.getResult();
+ if (document.exists()) {
+ Log.d(TAG, "getEvent.onComplete(): DocumentSnapshot data: " + document.getData());
+ if (document.getData() == null) {
+ callback.accept(null);
+ } else
+ callback.accept(new JSONObject(document.getData()));
+ } else {
+ Log.d(TAG, "No such document");
+ callback.accept(null);
+ }
+ } else {
+ Log.d(TAG, "get failed with ", task.getException());
+ callback.accept(null);
+ }
+ }
+ });
+
+ return true;
+
+ }
+
+ /**
+ * Retrieve all events accessible to the logged in user, and pass them to the callback function as a JSONObject
+ *
+ * @param callback
+ * @return true on success or false on failure to initiate the request.
+ */
+ public boolean getEvents(JSONObjectCallback callback) {
+ //Long eventId=Long.valueOf(285);
+ Log.v(TAG, "getEvents()");
+ if (mDb == null) {
+ Log.w(TAG, "getEvents() - mDb is null - not doing anything");
+ return false;
+ }
+
+ if (!isLoggedIn()) {
+ Log.w(TAG, "getEvents() - not logged in - not doing anything");
+ return false;
+ }
+ String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
+ mDb.collection("Events") //.where("userId", "==", userId)
+ .whereEqualTo("userId", userId)
+ .orderBy("dataTime", Query.Direction.ASCENDING)
+ .get()
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ try {
+ JSONObject retObj = new JSONObject();
+ JSONArray eventArray = new JSONArray();
+ Log.d(TAG, "getEvents() - returned " + task.getResult().size());
+ for (QueryDocumentSnapshot document : task.getResult()) {
+ Log.d(TAG, "getEvents() - " + document.getId() + " => " + document.getData());
+ JSONObject eventObj = new JSONObject(document.getData());
+ // Add the event id into the event data because firebase does not include it as part of the document.
+ eventObj.put("id", document.getId());
+ eventArray.put(eventObj);
+ }
+ retObj.put("events", eventArray);
+ callback.accept(retObj);
+ } catch (JSONException e) {
+ Log.e(TAG, "getEvents.onResponse(): Error: " + e.getMessage() + "," + e.toString());
+ callback.accept(null);
+ }
+
+ } else {
+ Log.d(TAG, "Error getting documents: ", task.getException());
+ callback.accept(null);
+ }
+ }
+ });
+ return (true);
+ }
+
+ public boolean updateEvent(final JSONObject eventObj, JSONObjectCallback callback) {
+ String eventId;
+ Log.v(TAG, "updateEvent()");
+ if (mDb == null) {
+ Log.w(TAG, "updateEvent() - mDb is null - not doing anything");
+ return false;
+ }
+
+ try {
+ eventId = eventObj.getString("id");
+ } catch (JSONException e) {
+ Log.e(TAG, "updateEvent(): Error reading id from eventObj");
+ eventId = null;
+ return false;
+ }
+ final String dataStr = eventObj.toString();
+ Log.v(TAG, "updateEvent - data=" + dataStr);
+ Map eventMap = new HashMap<>();
+ try {
+ eventMap.put("dataTime", eventObj.getLong("dataTime"));
+ eventMap.put("osdAlarmState", eventObj.getInt("osdAlarmState"));
+ eventMap.put("desc", eventObj.getString("desc"));
+ eventMap.put("type", eventObj.getString("type"));
+ eventMap.put("subType", eventObj.getString("subType"));
+ eventMap.put("userId", eventObj.getString("userId"));
+ } catch (JSONException e) {
+ Log.e(TAG, "updateEvent(): Error data from eventObj." + e.toString());
+ e.printStackTrace();
+ return false;
+ }
+ Log.v(TAG, "updateEvent - map=" + eventMap.toString());
+
+ try {
+ DocumentReference docRef = mDb.collection("Events").document(eventId);
+ docRef.set(eventMap)
+ .addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(Void aVoid) {
+ JSONObject retObj;
+ try {
+ retObj = new JSONObject("{\"status\":\"OK\"}");
+ } catch (Exception e) {
+ retObj = null;
+ }
+ callback.accept(retObj);
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ Log.w(TAG, "Error updating document", e);
+ callback.accept(null);
+ }
+ });
+ return (true);
+ } catch (Exception e) {
+ Log.e(TAG, "updateEvent() - ERROR: " + e.toString());
+ e.printStackTrace();
+ }
+ return (false);
+ }
+
+ public boolean createDatapoint(JSONObject dataObj, String eventId, StringCallback callback) {
+ Log.v(TAG, "createDatapoint()");
+ // Create a new event in the remote database, based on the provided parameters.
+ String userId = null;
+ if (FirebaseAuth.getInstance().getCurrentUser() == null) {
+ Log.e(TAG, "ERROR: createDatapoint() - not logged in");
+ return false;
+ } else {
+ userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
+ }
+ String dataTime;
+ try {
+ dataTime = dataObj.getString("dataTime");
+ } catch (JSONException e) {
+ dataTime = "";
+ }
+ Map datapoint = new HashMap<>();
+ datapoint.put("dataTime", dataTime);
+ datapoint.put("dataJSON", dataObj.toString());
+ datapoint.put("userId", userId);
+ datapoint.put("eventId", userId);
+
+ mDb.collection("Datapoints")
+ .add(datapoint)
+ .addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(DocumentReference documentReference) {
+ Log.d(TAG, "createDatapoint.onSuccess() - DocumentSnapshot added with ID: " + documentReference.getId());
+ mServerConnectionOk = true;
+ callback.accept(documentReference.getId());
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ Log.w(TAG, "createDatapoint.onFailure() - Error adding document", e);
+ callback.accept(null);
+ }
+ });
+ return (true);
+ }
+
+
+ /**
+ * Retrieve the file containing the standard event types from the server.
+ * Calls the specified callback function, passing a JSONObject as a parameter when the data has been received and parsed.
+ *
+ * @return true if request sent successfully or else false.
+ */
+ public boolean getEventTypes(JSONObjectCallback callback) {
+ Log.v(TAG, "getEventTypes()");
+ if (mDb == null) {
+ Log.w(TAG, "getEventTypes() - mDb is null - not doing anything");
+ return false;
+ }
+
+ mDb.collection("EventTypes")
+ .get()
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ try {
+ JSONObject retObj = new JSONObject();
+ for (QueryDocumentSnapshot document : task.getResult()) {
+ Log.d(TAG, "getEventTypes.onComplete(): " + document.getId() + " => " + document.getData());
+ Log.v(TAG, "getEventTypes.onComplete() - subtypes=" + document.getData().get("subTypes"));
+ JSONArray subTypesArray = listToJSONArray((List) document.getData().get("subTypes"));
+ retObj.put(document.getData().get("type").toString(), subTypesArray);
+ }
+ Log.d(TAG, "getEventTypes.onComplete() - retObj=" + retObj.toString());
+ callback.accept(retObj);
+ } catch (JSONException e) {
+ Log.e(TAG, "getEventTypes.onResponse(): Error: " + e.getMessage() + "," + e.toString());
+ callback.accept(null);
+ }
+ } else {
+ Log.d(TAG, "Error getting documents: ", task.getException());
+ callback.accept(null);
+ }
+ }
+ });
+ return (true);
+
+ }
+
+ private JSONArray listToJSONArray(List