V4.1.11 - Fixed crash when exporting larger amounts of data and added simple fidget detector to detect if watch is not being worn.

This commit is contained in:
Graham Jones
2023-08-21 20:53:48 +01:00
parent 70f7a46764
commit a4b6a43008
7 changed files with 274 additions and 132 deletions

View File

@@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:versionCode="125" android:versionCode="125"
android:versionName="4.1.11a"> android:versionName="4.1.11">
<!-- android:allowBackup="false" --> <!-- android:allowBackup="false" -->
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

View File

@@ -209,17 +209,34 @@ public class ExportDataActivity extends AppCompatActivity
// mDateTxt.getText().toString(), mTimeTxt.getText().toString(), mDuration)); // mDateTxt.getText().toString(), mTimeTxt.getText().toString(), mDuration));
Log.d(TAG, String.format("EndDate=%s %s, Duration=%3.1f hrs", Log.d(TAG, String.format("EndDate=%s %s, Duration=%3.1f hrs",
mDateTxt.getText().toString(), mTimeTxt.getText().toString(), mDuration)); mDateTxt.getText().toString(), mTimeTxt.getText().toString(), mDuration));
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
pb.setIndeterminate(true);
pb.setVisibility(View.VISIBLE);
mExportBtn.setEnabled(false); showProgressBar();
mExportBtn.setVisibility(View.INVISIBLE);
this.openFile(); this.openFile();
} }
} }
public void showProgressBar() {
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
pb.setIndeterminate(true);
pb.setVisibility(View.VISIBLE);
mExportBtn.setEnabled(false);
mExportBtn.setVisibility(View.INVISIBLE);
}
public void hideProgressBar() {
runOnUiThread(new Runnable() {
public void run() {
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
pb.setIndeterminate(true);
pb.setVisibility(View.INVISIBLE);
mExportBtn.setEnabled(true);
mExportBtn.setVisibility(View.VISIBLE);
}
});
}
private void openFile() { private void openFile() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
@@ -248,96 +265,14 @@ public class ExportDataActivity extends AppCompatActivity
// Perform operations on the document using its URI. // Perform operations on the document using its URI.
//mUtil.showToast("URI="+uri.toString()); //mUtil.showToast("URI="+uri.toString());
Log.v(TAG, "onActivityResult() - exporting to file " + uri.toString()); Log.v(TAG, "onActivityResult() - exporting to file " + uri.toString());
exportToFile(uri); mLm.exportToCsvFile(mEndDate, mDuration,uri, (boolean b)-> {
Log.v(TAG,"onActivityResult callback");
hideProgressBar();
});
} }
} }
super.onActivityResult(requestCode, resultCode, resultData); super.onActivityResult(requestCode, resultCode, resultData);
} }
private void exportToFile(Uri uri) {
Log.v(TAG, "exportToFile(): uri=" + uri.toString());
long endDateMillis = mEndDate.getTime();
long durationMillis = (long) (mDuration * 3600. * 1000);
long startDateMillis = endDateMillis - durationMillis;
Log.v(TAG, "exportToFile() - endDateMillis=" + endDateMillis + ", startDateMillis=" + startDateMillis + ", durationMillis=" + durationMillis);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String sDateStr = dateFormat.format(new Date(startDateMillis));
String eDateStr = dateFormat.format(new Date(endDateMillis));
Log.v(TAG, "exportToFile() - sDateStr=" + sDateStr + " eDateStr=" + eDateStr);
mLm.getDatapointsByDate(
sDateStr, eDateStr, (String datapointsJsonStr) -> {
Log.v(TAG, "exportToFile() - datapoints=" + datapointsJsonStr);
// Open file for writing
try {
ParcelFileDescriptor pfd = this.getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(("# dataTime, alarmState, hr, o2sat, accel*125\n").getBytes());
JSONArray dataObj;
try {
dataObj = new JSONArray(datapointsJsonStr);
Log.v(TAG, "exportToFile() - dataObj length=" + dataObj.length());
for (int i = 0; i < dataObj.length(); i++) {
JSONObject datapointJsonObj = dataObj.getJSONObject(i);
String dataJsonStr = datapointJsonObj.getString("dataJSON");
Log.v(TAG, "exportToFile() - i=" + i + "dataJsonStr=" + dataJsonStr);
JSONObject dataJsonObj = new JSONObject(dataJsonStr);
JSONArray rawDataArr = dataJsonObj.getJSONArray("rawData");
try {
fileOutputStream.write(dataJsonObj.getString("dataTime").getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(dataJsonObj.getString("alarmState").getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(dataJsonObj.getString("hr").getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(dataJsonObj.getString("o2Sat").getBytes(StandardCharsets.UTF_8));
for (int j = 0; j < rawDataArr.length(); j++) {
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(rawDataArr.getString(j).getBytes(StandardCharsets.UTF_8));
}
fileOutputStream.write("\n".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
Log.e(TAG, "exportToFile() - ERROR Writing File: " + e.toString());
//mUtil.showToast("ERROR WRITING FILE");
}
}
} catch (JSONException | NullPointerException e) {
Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string " + datapointsJsonStr);
dataObj = null;
mUtil.showToast(getString(R.string.error_exporting_data));
Log.e(TAG, "exportToFile() - JSONException: " + e.toString());
}
// Let the document provider know you're done by closing the stream.
fileOutputStream.close();
pfd.close();
mUtil.showToast(getString(R.string.data_exported_ok));
} catch (FileNotFoundException e) {
e.printStackTrace();
mUtil.showToast(getString(R.string.error_exporting_data));
Log.e(TAG, "exportToFile() - FileNotFoundException: " + e.toString());
} catch (IOException e) {
e.printStackTrace();
mUtil.showToast(getString(R.string.error_exporting_data));
Log.e(TAG, "exportToFile() - IOException: " + e.toString());
}
runOnUiThread(new Runnable() {
public void run() {
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
pb.setIndeterminate(true);
pb.setVisibility(View.INVISIBLE);
mExportBtn.setEnabled(true);
mExportBtn.setVisibility(View.VISIBLE);
}
});
});
}
} }

View File

@@ -30,17 +30,25 @@ import android.database.DatabaseUtils;
import android.database.SQLException; import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.CountDownTimer; import android.os.CountDownTimer;
import android.os.Handler; import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.format.Time; import android.text.format.Time;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -66,7 +74,7 @@ import java.util.HashMap;
* - Query the local database to return all datapoints within +/- EventDuration/2 minutes of the event. * - Query the local database to return all datapoints within +/- EventDuration/2 minutes of the event.
* - Upload the datapoints, linking them to the new eventID. * - Upload the datapoints, linking them to the new eventID.
* - Mark all the uploaded datapoints as uploaded. * - Mark all the uploaded datapoints as uploaded.
* * <p>
* Event statuses: * Event statuses:
* 0 - OK * 0 - OK
* 1 - WARNING * 1 - WARNING
@@ -75,7 +83,7 @@ import java.util.HashMap;
* 4 - FAULT * 4 - FAULT
* 5 - Manual Alarm * 5 - Manual Alarm
* 6 - NDA (Normal Daily Activities) * 6 - NDA (Normal Daily Activities)
* * <p>
* NDA Timer creates an event periodically to record Normal Daily Activities (NDA), * NDA Timer creates an event periodically to record Normal Daily Activities (NDA),
* irrespective of the alarm state. This will upload a lot of data, so it will only run * irrespective of the alarm state. This will upload a lot of data, so it will only run
* for 24 hours after being activated before shutting down requring the user to re-select * for 24 hours after being activated before shutting down requring the user to re-select
@@ -122,6 +130,11 @@ public class LogManager {
void accept(ArrayList<HashMap<String, String>> retVal); void accept(ArrayList<HashMap<String, String>> retVal);
} }
public interface BooleanCallback {
void accept(boolean retVal);
}
public LogManager(Context context, public LogManager(Context context,
boolean logRemote, boolean logRemoteMobile, String authToken, boolean logRemote, boolean logRemoteMobile, String authToken,
long eventDuration, long remoteLogPeriod, long eventDuration, long remoteLogPeriod,
@@ -479,6 +492,111 @@ public class LogManager {
return (true); return (true);
} }
/**
* exportToFile - export datapoints data to a csv file on the android device.
*
* @param endDate end date of period to export (Date type)
* @param duration duration in hours of period to export (double)
* @param uri uri of file to save.
*/
public void exportToCsvFile(Date endDate, double duration, Uri uri, BooleanCallback callback) {
Log.v(TAG, "exportToCsvFile(): uri=" + uri.toString());
long endDateMillis = endDate.getTime();
long durationMillis = (long) (duration * 3600. * 1000);
long startDateMillis = endDateMillis - durationMillis;
Log.v(TAG, "exportToCsvFile() - endDateMillis=" + endDateMillis + ", startDateMillis=" + startDateMillis + ", durationMillis=" + durationMillis);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String sDateStr = dateFormat.format(new Date(startDateMillis));
String eDateStr = dateFormat.format(new Date(endDateMillis));
Log.v(TAG, "exportToFile() - sDateStr=" + sDateStr + " eDateStr=" + eDateStr);
String[] columns = {"*"};
String whereClause = "DataTime>? AND DataTime<?";
String[] whereArgs = {sDateStr, eDateStr};
new SelectQueryTask(mDpTableName, columns, whereClause, whereArgs,
null, null, "dataTime DESC", (Cursor cursor) -> {
Log.v(TAG, "exportToCsvFile - returned " + cursor);
if (cursor != null) {
Log.d(TAG, "we got a cursor!");
try {
ParcelFileDescriptor pfd = mContext.getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(("# dataTime, alarmState, hr, o2sat, accel*125\n").getBytes());
writeDatapointsToFile(cursor, fileOutputStream);
// Let the document provider know you're done by closing the stream.
fileOutputStream.close();
pfd.close();
callback.accept(true);
} catch (FileNotFoundException e) {
e.printStackTrace();
mUtil.showToast(mContext.getString(R.string.error_exporting_data));
Log.e(TAG, "exportToFile() - FileNotFoundException: " + e.toString());
callback.accept(false);
} catch (IOException e) {
e.printStackTrace();
mUtil.showToast(mContext.getString(R.string.error_exporting_data));
Log.e(TAG, "exportToFile() - IOException: " + e.toString());
callback.accept(false);
}
} else {
Log.w(TAG, "exportToCsvFile() - returned null result");
callback.accept(false);
}
}).execute();
return;
}
private void writeDatapointsToFile(Cursor c, FileOutputStream fileOutputStream) {
Log.v(TAG, "writeDatapointsToFile()");
JSONArray dataObj;
String dataJsonStr;
JSONObject dataJsonObj;
JSONArray rawDataArr;
Log.d(TAG,"writeDatapointsToFile()" + c.getColumnNames());
//for (int i=0;i<c.getColumnCount();i++) {
// Log.d(TAG," Column"+i+" = "+c.getColumnName(i));
//}
try {
Log.d(TAG,"writeDatapointsToFile() - writing query result to csv file....");
while (c.moveToNext()) {
//Log.d(TAG,"writeDatapointsToFile - row="+c.getString(0)+", "+c.getString(1));
dataJsonStr = c.getString(3); // dataJSON is index 3
//Log.v(TAG, "exportToFile() - i=" + i + "dataJsonStr=" + dataJsonStr);
dataJsonObj = new JSONObject(dataJsonStr);
rawDataArr = dataJsonObj.getJSONArray("rawData");
try {
//fileOutputStream.write(dataJsonObj.getString("dataTime").getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(c.getString(1).getBytes(StandardCharsets.UTF_8)); // We use the database record date rather than datajson date because it is formatted yyyy-mm-dd
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(dataJsonObj.getString("alarmState").getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(dataJsonObj.getString("hr").getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(dataJsonObj.getString("o2Sat").getBytes(StandardCharsets.UTF_8));
for (int j = 0; j < 125; j++) { // FIXME Hard Coded array length, but rawDataArr.length() is 125*3 so we don't want to use that.
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
fileOutputStream.write(rawDataArr.getString(j).getBytes(StandardCharsets.UTF_8));
}
fileOutputStream.write("\n".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
Log.e(TAG, "exportToFile() - ERROR Writing File: " + e.toString());
mUtil.showToast("ERROR WRITING FILE");
}
}
Log.d(TAG,"writeDatapointsToFile() - data written to file ok");
mUtil.showToast(mContext.getString(R.string.data_exported_ok));
} catch (JSONException | NullPointerException e) {
Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string ");
dataObj = null;
mUtil.showToast(mContext.getString(R.string.error_exporting_data));
Log.e(TAG, "exportToFile() - JSONException: " + e.toString());
}
}
/** /**
* Return an array list of objects representing the events in the database by calling the specified callback function. * Return an array list of objects representing the events in the database by calling the specified callback function.
@@ -486,7 +604,8 @@ public class LogManager {
* @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions. * @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. * @return True on successful start or false if call fails.
*/ */
public boolean getEventsList(boolean includeWarnings, ArrayListCallback callback) { public boolean getEventsList(boolean includeWarnings, ArrayListCallback
callback) {
Log.v(TAG, "getEventsList - includeWarnings=" + includeWarnings); Log.v(TAG, "getEventsList - includeWarnings=" + includeWarnings);
ArrayList<HashMap<String, String>> eventsList = new ArrayList<>(); ArrayList<HashMap<String, String>> eventsList = new ArrayList<>();
@@ -572,7 +691,8 @@ public class LogManager {
* @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions. * @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. * @return True on successful start or false if call fails.
*/ */
public boolean getNextEventToUpload(boolean includeWarnings, WebApiConnection.LongCallback callback) { public boolean getNextEventToUpload(boolean includeWarnings, WebApiConnection.
LongCallback callback) {
Log.v(TAG, "getNextEventToUpload - includeWarnings=" + includeWarnings); Log.v(TAG, "getNextEventToUpload - includeWarnings=" + includeWarnings);
String[] whereArgsStatus = getEventWhereArgs(includeWarnings); String[] whereArgsStatus = getEventWhereArgs(includeWarnings);
@@ -619,7 +739,8 @@ public class LogManager {
* *
* @return True on successful start or false if call fails. * @return True on successful start or false if call fails.
*/ */
public boolean getNearestDatapointToDate(String dateStr, WebApiConnection.LongCallback callback) { public boolean getNearestDatapointToDate(String
dateStr, WebApiConnection.LongCallback callback) {
Log.v(TAG, "getNextEventToDate - dateStr=" + dateStr); Log.v(TAG, "getNextEventToDate - dateStr=" + dateStr);
String[] columns = {"*", "(julianday(dataTime)-julianday(datetime('" + dateStr + "'))) as ddiff"}; 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;"; //SQLStr = "SELECT *, (julianday(dataTime)-julianday(datetime('" + dateStr + "'))) as ddiff from " + mDbTableName + " order by ABS(ddiff) asc;";
@@ -652,7 +773,8 @@ public class LogManager {
* @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions. * @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. * @return True on successful start or false if call fails.
*/ */
public boolean getLocalEventsCount(boolean includeWarnings, WebApiConnection.LongCallback callback) { public boolean getLocalEventsCount(boolean includeWarnings, WebApiConnection.
LongCallback callback) {
//Log.v(TAG, "getLocalEventsCount- includeWarnings=" + includeWarnings); //Log.v(TAG, "getLocalEventsCount- includeWarnings=" + includeWarnings);
String[] whereArgs = getEventWhereArgs(includeWarnings); String[] whereArgs = getEventWhereArgs(includeWarnings);
String whereClause = getEventWhereClause(includeWarnings); String whereClause = getEventWhereClause(includeWarnings);
@@ -967,7 +1089,8 @@ public class LogManager {
for (int i = 0; i < dataObj.length(); i++) { for (int i = 0; i < dataObj.length(); i++) {
mDatapointsToUploadList.add(dataObj.getJSONObject(i)); mDatapointsToUploadList.add(dataObj.getJSONObject(i));
} }
} catch (JSONException | NullPointerException e) { } catch (JSONException |
NullPointerException e) {
Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string " + datapointsJsonStr); Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string " + datapointsJsonStr);
dataObj = null; dataObj = null;
finishUpload(); finishUpload();
@@ -1196,7 +1319,6 @@ public class LogManager {
} }
public static class OsdDbHelper extends SQLiteOpenHelper { public static class OsdDbHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version. // If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1; public static final int DATABASE_VERSION = 1;
@@ -1275,6 +1397,7 @@ public class LogManager {
*/ */
private class NDATimer extends CountDownTimer { private class NDATimer extends CountDownTimer {
double mNDALogPeriodHours = 0; double mNDALogPeriodHours = 0;
public NDATimer(long startTime, long interval, double logPeriod) { public NDATimer(long startTime, long interval, double logPeriod) {
super(startTime, interval); super(startTime, interval);
mNDALogPeriodHours = logPeriod; mNDALogPeriodHours = logPeriod;

View File

@@ -108,6 +108,10 @@ public abstract class SdDataSource {
private Time mHrStatusTime; private Time mHrStatusTime;
private double mHrFrozenPeriod = 60; // seconds private double mHrFrozenPeriod = 60; // seconds
private boolean mHrFrozenAlarm; private boolean mHrFrozenAlarm;
private boolean mFidgetDetectorEnabled;
private double mFidgetPeriod;
private double mFidgetThreshold;
private Time mLastFidget = null;
public SdDataSource(Context context, Handler handler, SdDataReceiver sdDataReceiver) { public SdDataSource(Context context, Handler handler, SdDataReceiver sdDataReceiver) {
@@ -723,6 +727,31 @@ public abstract class SdDataSource {
} }
private double calcRawDataStd(SdData sdData) {
/**
* Calculate the standard deviation in % of the rawData array in the SdData instance provided.
* It assumes that rawdata will contain 125 samples.
* Returns the standard deviation in %.
*/
// FIXME - assumes length of rawdata array is 125 data points
int j;
double sum = 0.0;
for (j = 0; j < 125; j++) { // FIXME - assumed length!
sum += sdData.rawData[j];
}
double mean = sum / 125;
double standardDeviation = 0.0;
for (j = 0; j < 125; j++) { // FIXME - assumed length!
standardDeviation += Math.pow(sdData.rawData[j] - mean, 2);
}
standardDeviation = Math.sqrt(standardDeviation / 125); // FIXME - assumed length!
// Convert standard deviation from milli-g to %
standardDeviation = 100. * standardDeviation / mean;
return (standardDeviation);
}
/** /**
* Checks the status of the connection to the watch, * Checks the status of the connection to the watch,
* and sets class variables for use by other functions. * and sets class variables for use by other functions.
@@ -758,6 +787,23 @@ public abstract class SdDataSource {
} }
} else { } else {
mSdData.watchAppRunning = true; mSdData.watchAppRunning = true;
// Check we have seen a fidget within the required period, or else assume a fault because watch is not being worn
if (mFidgetDetectorEnabled) {
if (mLastFidget == null) mLastFidget = tnow; // Initialise last fidget time on startup.
double accStd = calcRawDataStd(mSdData);
if (accStd > mFidgetThreshold) {
mLastFidget = tnow;
} else {
Log.d(TAG,"onStatus() - Fidget Detector - low movement - is watch being worn?");
tdiff = (tnow.toMillis(false) - mLastFidget.toMillis(false));
if (tdiff > (mFidgetPeriod) * 60 * 1000) {
Log.e(TAG, "onStatus() - Fidget Not Detected - is watch being worn?");
mSdDataReceiver.onSdDataFault(mSdData);
}
}
}
} }
// if we have confirmation that the app is running, reset the // if we have confirmation that the app is running, reset the
@@ -877,6 +923,20 @@ public abstract class SdDataSource {
toast.show(); toast.show();
} }
// Parse the Fidget Detector settings.
try {
mFidgetDetectorEnabled = SP.getBoolean("FidgetDetectorEnabled", false);
mFidgetPeriod = readDoublePref(SP, "FidgetDetectorPeriod", "20"); // minutes
Log.v(TAG, "updatePrefs() - mFidgetPeriod = " + mFidgetPeriod);
mFidgetThreshold = readDoublePref(SP, "FidgetDetectorThreshold", "5");
Log.d(TAG,"updatePrefs(): mFidgetThreshold="+mFidgetThreshold);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem with FidgetDetector preferences!");
Toast toast = Toast.makeText(mContext, "Problem Parsing FidgetPeriod Preference", Toast.LENGTH_SHORT);
toast.show();
}
// Watch Settings // Watch Settings
String prefStr; String prefStr;

View File

@@ -3,7 +3,7 @@
<string name="app_name">OpenSeizureDetector</string> <string name="app_name">OpenSeizureDetector</string>
<string name="changelog"> <string name="changelog">
"\n "\n
\mV4.1.11 - Changed target Android Version to 13 (SDK33) (Play Store policy). \mV4.1.11 - Fixed issue with data export crashing when lots of data requested. Added simple 'Fidget Detector' to detect if watch has fallen off the wrist. Changed target Android Version to 13 (SDK33) (Play Store policy).
\nV4.1.10 - Added warning if heart rate readings freeze and do not change for more than 1 minute. \nV4.1.10 - Added warning if heart rate readings freeze and do not change for more than 1 minute.
\nV4.1.9 - Fixed problem with average heart rate alarm \nV4.1.9 - Fixed problem with average heart rate alarm
Fixed issue with phone data source generating continuous alarms for Heart Rate or O2Sat Fixed issue with phone data source generating continuous alarms for Heart Rate or O2Sat
@@ -503,4 +503,11 @@
<string name="error_exporting_data">*** ERROR Exporting Data ***</string> <string name="error_exporting_data">*** ERROR Exporting Data ***</string>
<string name="HrFrozenTitle">Heart Rate measurement Frozen Warning</string> <string name="HrFrozenTitle">Heart Rate measurement Frozen Warning</string>
<string name="HrFrozenSummary">Produce a fault warning if the heart rate measurement freezes and does not change for more than 1 minute.</string> <string name="HrFrozenSummary">Produce a fault warning if the heart rate measurement freezes and does not change for more than 1 minute.</string>
<string name="FidgetSettingsTitle">Fidget Detector Settings</string>
<string name="FidgetDetectorEnabledTitle">Enable Fidget Detector</string>
<string name="FidgetDetectorEnabledSummary">Generates a fault if no movement has been detected for a specified period (signifying the watch has been removed)</string>
<string name="FidgetDetectorThresholdSummary">The threshold (as % standard deviation) applied to each set of accelerometer data to determine if a \'Fidget\' has occurred.</string>
<string name="FidgetDetectorThresholdTitle">Fidget Detector Threshold (%)</string>
<string name="FidgetDetectorPeriodTitle">Fidget Detector Period (minutes)</string>
<string name="FidgetDetectorPeriodSummary">A fault is generated if no movement (fidgets) are detected for more than the Fidget Detector Period.</string>
</resources> </resources>

View File

@@ -186,5 +186,22 @@
android:title="@string/fall_window_title" /> android:title="@string/fall_window_title" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/FidgetSettingsTitle">
<CheckBoxPreference
android:defaultValue="false"
android:key="FidgetDetectorEnabled"
android:summary="@string/FidgetDetectorEnabledSummary"
android:title="@string/FidgetDetectorEnabledTitle" />
<EditTextPreference
android:defaultValue="5"
android:key="FidgetDetectorThreshold"
android:summary="@string/FidgetDetectorThresholdSummary"
android:title="@string/FidgetDetectorThresholdTitle" />
<EditTextPreference
android:defaultValue="20"
android:key="FidgetDetectorPeriod"
android:summary="@string/FidgetDetectorPeriodSummary"
android:title="@string/FidgetDetectorPeriodTitle" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>