diff --git a/CHANGELOG.md b/CHANGELOG.md index 931b2d4..3550291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ OpenSeizureDetector Android App - Change Log ============================================ - V4.2.11 - Added setting to change the delay before SMS alert is sent (Issue #202) + V4.2.11 - Updated permissions handling to support Android 14 (needed to publish on Play Store) + - added a crude 'flap' detector into OSD Algorithm + - Added setting to change the delay before SMS alert is sent (Issue #202) + - Added 'Send False Alarm notification Menu Option (Issue #206)' + - Reduced the frequency of checking if we have unvalided events on the data sharing server to reduce data usage (Issue #201). + - Improvements to Data Sharing Screen (Issue #199) V4.2.10 - fixed (infrequent) crash when opening data sharing page (#195), and crash if log manager fails to start (#196) V4.2.9 - fixed crash when using Polish translation. V4.2.8 - diff --git a/app/build.gradle b/app/build.gradle index 7b98881..1b6f2a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { defaultConfig { applicationId "uk.org.openseizuredetector" minSdkVersion 23 // Android 6 - targetSdkVersion 33 // Android 13 = 33 + targetSdkVersion 34 // Android 14 = 34 multiDexEnabled true } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 92c72e8..d050036 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ @@ -12,6 +12,8 @@ android:maxSdkVersion="30" /> + + @@ -80,6 +82,7 @@ diff --git a/app/src/main/java/uk/org/openseizuredetector/FragmentCommon.java b/app/src/main/java/uk/org/openseizuredetector/FragmentCommon.java index 5635622..f1cf113 100644 --- a/app/src/main/java/uk/org/openseizuredetector/FragmentCommon.java +++ b/app/src/main/java/uk/org/openseizuredetector/FragmentCommon.java @@ -127,7 +127,7 @@ public class FragmentCommon extends FragmentOsdBaseClass { tv.setTextColor(warnTextColour); } if (mConnection.mSdServer.mSdData.alarmStanding) { - tv.setText(R.string.Alarm); + tv.setText(getString(R.string.Alarm) + "\n" + mConnection.mSdServer.mSdData.alarmCause); tv.setBackgroundColor(alarmColour); tv.setTextColor(alarmTextColour); } diff --git a/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java b/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java index d17f572..e0e4518 100644 --- a/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java @@ -54,6 +54,7 @@ public class LogManagerControlActivity extends AppCompatActivity { private UiTimer mUiTimer; private ArrayList> mEventsList; private ArrayList> mRemoteEventsList; + private ArrayList>> mGroupedRemoteEventsList; // Each item is a list of event objects, similar to mRemoteEventsList private ArrayList> mSysLogList; private SdServiceConnection mConnection; private OsdUtil mUtil; @@ -281,6 +282,7 @@ public class LogManagerControlActivity extends AppCompatActivity { Log.v(TAG, "getRemoteEvents - skipping warning or NDA record"); } } + createGroupedEventsList(); Log.v(TAG, "getRemoteEvents() - set mRemoteEventsList(). Updating UI"); updateUi(); } catch (JSONException e) { @@ -293,6 +295,15 @@ public class LogManagerControlActivity extends AppCompatActivity { }); } + private void createGroupedEventsList() { + /** + * Reads the complete list of remote events mRemoteEventsList and creates a new list mGroupedRemoteEventsList + * where each item is a list of events that comprise a group based on time (all events within a 3 minute period are grouped together). + */ + Log.i(TAG, "createGroupedEventsList()"); + mGroupedRemoteEventsList = new ArrayList>>(); + // FIXME - Make this do something! + } private void updateUi() { Log.i(TAG, "updateUi()"); @@ -305,10 +316,10 @@ public class LogManagerControlActivity extends AppCompatActivity { 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)); - }); + //mLm.getLocalDatapointsCount((Long datapointsCount) -> { + // TextView tv2 = (TextView) findViewById(R.id.num_local_datapoints_tv); + // tv2.setText(String.format("%d", datapointsCount)); + //}); TextView tv3 = (TextView) findViewById(R.id.nda_time_remaining_tv); tv3.setText(String.format("%.1f hrs", mLm.mNDATimeRemaining)); Log.d(TAG, "mNDATimeRemaining = " + String.format("%.1f hrs", mLm.mNDATimeRemaining)); diff --git a/app/src/main/java/uk/org/openseizuredetector/MainActivity2.java b/app/src/main/java/uk/org/openseizuredetector/MainActivity2.java index a423152..66228d4 100644 --- a/app/src/main/java/uk/org/openseizuredetector/MainActivity2.java +++ b/app/src/main/java/uk/org/openseizuredetector/MainActivity2.java @@ -237,6 +237,12 @@ public class MainActivity2 extends AppCompatActivity { mConnection.mSdServer.sendSMSAlarm(); } return true; + case R.id.action_send_false_alarm_sms: + Log.i(TAG, "action_send_false_alarm_sms"); + if (mConnection.mBound) { + mConnection.mSdServer.sendFalseAlarmSMS(); + } + return true; case R.id.action_authenticate_api: Log.i(TAG, "action_autheticate_api"); diff --git a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java index 4b86b0b..b44ca30 100644 --- a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java +++ b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java @@ -116,6 +116,15 @@ public class OsdUtil { }; public String[] BT_PERMISSIONS; + public final String[] ACTIVITY_PERMISSIONS_API34 = { + Manifest.permission.FOREGROUND_SERVICE_HEALTH, + Manifest.permission.ACTIVITY_RECOGNITION + }; + + public final String[] ACTIVITY_PERMISSIONS_OLD = {}; + public String[] ACTIVITY_PERMISSIONS; + + public OsdUtil(Context context, Handler handler) { mContext = context; mHandler = handler; @@ -780,6 +789,33 @@ public class OsdUtil { return allOk; } + public String[] getRequiredActivityPermissions() { + // API 34 is Android 14 - see https://developer.android.com/develop/connectivity/bluetooth/bt-permissions + if (Build.VERSION.SDK_INT >= 34) { + Log.d(TAG, "getRequiredActivityPermissions() - using new Activity Permissions"); + ACTIVITY_PERMISSIONS = ACTIVITY_PERMISSIONS_API34; + } else { + Log.d(TAG, "getRequiredActivityPermissions() - using old Activity Permissions"); + ACTIVITY_PERMISSIONS = ACTIVITY_PERMISSIONS_OLD; + } + return (ACTIVITY_PERMISSIONS); + } + public boolean areActivityPermissionsOk() { + String[] activityPermissions = getRequiredActivityPermissions(); + boolean allOk = true; + Log.d(TAG, "areActivityPermissions OK()"); + for (int i = 0; i < activityPermissions.length; i++) { + if (ContextCompat.checkSelfPermission(mContext, activityPermissions[i]) + != PackageManager.PERMISSION_GRANTED) { + Log.i(TAG, activityPermissions[i] + " Permission Not Granted"); + allOk = false; + } + } + return allOk; + } + + + public double parseToDouble(String userInput) { /** * Parse a string to a double value, taking localisation into account. diff --git a/app/src/main/java/uk/org/openseizuredetector/SdData.java b/app/src/main/java/uk/org/openseizuredetector/SdData.java index 79f04a0..9bbcd4e 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdData.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdData.java @@ -40,6 +40,7 @@ public class SdData implements Parcelable { // Seizure Detection Algorithm Selection public boolean mOsdAlarmActive; + public boolean mFlapAlarmActive; public boolean mCnnAlarmActive; /* Analysis settings */ diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java index 91c91a9..b32f1bd 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java @@ -89,6 +89,12 @@ public abstract class SdDataSource { private short mAlarmTime; private short mAlarmThresh; private short mAlarmRatioThresh; + + private short mFlapThresh; + private short mFlapRatioThresh; + private double mFlapFreqMin; + private double mFlapFreqMax; + private boolean mFallActive; private short mFallThreshMin; private short mFallThreshMax; @@ -462,6 +468,22 @@ public abstract class SdDataSource { return mag; } + + /** + * getBinNo - returns the FFT bin number corresponding to a frequency freqHz in Hz when the sample frequency is + * sampleFreq Hz and the number of samples collected is nSamp. + * @param freqHz : The frequency (in Hz) for which the bin number is required. + * @param sampleFreq: Sample Frequency (Hz) + * @param nSamp: number of samples + * @return FFT bin Number + */ + int freq2FftBin(double freqHz, double sampleFreq, int nSamp) { + int binNo = (int)(nSamp * freqHz / sampleFreq); + return binNo; + } + + + /** * doAnalysis() - analyse the data if the accelerometer data array mAccData * and populate the output data structure mSdData @@ -482,12 +504,12 @@ public abstract class SdDataSource { Log.v(TAG, "doAnalysis(): mSampleFreq=" + mSampleFreq + " mNSamp=" + mSdData.mNsamp + ": freqRes=" + freqRes); Log.v(TAG, "doAnalysis(): rawData=" + Arrays.toString(mSdData.rawData)); // Set the frequency bounds for the analysis in fft output bin numbers. - nMin = (int) (mAlarmFreqMin / freqRes); - nMax = (int) (mAlarmFreqMax / freqRes); + nMin = freq2FftBin(mAlarmFreqMin, mSampleFreq, mSdData.mNsamp); + nMax = freq2FftBin(mAlarmFreqMax, mSampleFreq, mSdData.mNsamp); + // Calculate the bin number of the cutoff frequency + nFreqCutoff = freq2FftBin(mFreqCutoff, mSampleFreq, mSdData.mNsamp); Log.v(TAG, "doAnalysis(): mAlarmFreqMin=" + mAlarmFreqMin + ", nMin=" + nMin + ", mAlarmFreqMax=" + mAlarmFreqMax + ", nMax=" + nMax); - // Calculate the bin number of the cutoff frequency - nFreqCutoff = (int) (mFreqCutoff / freqRes); Log.v(TAG, "mFreqCutoff = " + mFreqCutoff + ", nFreqCutoff=" + nFreqCutoff); DoubleFFT_1D fftDo = new DoubleFFT_1D(mSdData.mNsamp); @@ -583,7 +605,9 @@ public abstract class SdDataSource { // Check this data to see if it represents an alarm state. mSdData.alarmCause = ""; - alarmCheck(); + + boolean flapDetected = flapCheck(); + alarmCheck(flapDetected); hrCheck(); o2SatCheck(); fallCheck(); @@ -593,6 +617,84 @@ public abstract class SdDataSource { mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data. } + /** + * flapCheck() - Performs the same analysis as the main OSD algorithm, but over a narrow + * frequency band to detect a flapping arm movement. + * returns True if in an alarm state, or false if ok. + * FIXME - we should generalise the OSD algorithm to allow several ROIs and thresholds to be + * specified, rather than doing this separately like this. + */ + protected boolean flapCheck() { + boolean retVal; + int nMin = 0; + int nMax = 0; + int nFreqCutoff = 0; + double[] fft = null; + double roiRatio; + double roiPower; + try { + // FIXME - Use specified sampleFreq, not this hard coded one + mSampleFreq = 25; + double freqRes = 1.0 * mSampleFreq / mSdData.mNsamp; + Log.v(TAG, "flapCheck(): mSampleFreq=" + mSampleFreq + " mNSamp=" + mSdData.mNsamp + ": freqRes=" + freqRes); + Log.v(TAG, "flapCheck(): rawData=" + Arrays.toString(mSdData.rawData)); + // Set the frequency bounds for the analysis in fft output bin numbers. + nMin = freq2FftBin(mFlapFreqMin, mSampleFreq, mSdData.mNsamp); + nMax = freq2FftBin(mFlapFreqMax, mSampleFreq, mSdData.mNsamp); + // Calculate the bin number of the cutoff frequency + nFreqCutoff = freq2FftBin(mFreqCutoff, mSampleFreq, mSdData.mNsamp); + Log.v(TAG, "flapCheck(): flapFreqMin=" + mFlapFreqMin + ", nMin=" + nMin + + ", flapFreqMax=" + mFlapFreqMax + ", nMax=" + nMax); + Log.v(TAG, "mFreqCutoff = " + mFreqCutoff + ", nFreqCutoff=" + nFreqCutoff); + + DoubleFFT_1D fftDo = new DoubleFFT_1D(mSdData.mNsamp); + fft = new double[mSdData.mNsamp * 2]; + System.arraycopy(mSdData.rawData, 0, fft, 0, mSdData.mNsamp); + fftDo.realForward(fft); + + // Calculate the whole spectrum power (well a value equivalent to it that avoids square root calculations + // and zero any readings that are above the frequency cutoff. + double specPower = 0; + for (int i = 1; i < mSdData.mNsamp / 2; i++) { + if (i <= nFreqCutoff) { + specPower = specPower + getMagnitude(fft, i); + } else { + fft[2 * i] = 0.; + fft[2 * i + 1] = 0.; + } + } + specPower = specPower / mSdData.mNsamp / 2; + specPower = specPower / ACCEL_SCALE_FACTOR; + + // Calculate the Region of Interest power and power ratio. + roiPower = 0; + for (int i = nMin; i < nMax; i++) { + roiPower = roiPower + getMagnitude(fft, i); + } + roiPower = roiPower / (nMax - nMin); + roiPower = roiPower / ACCEL_SCALE_FACTOR; + + roiRatio = 10 * roiPower / specPower; + + Log.d(TAG, "flapCheck() - roiPower="+roiPower+", roiRatio="+roiRatio); + + } catch (Exception e) { + Log.e(TAG, "flapCheck - Exception during Analysis"+e.toString()); + roiRatio = 0; + roiPower = 0; + } + + retVal = false; + if (roiPower > mFlapThresh) { + if (roiRatio > mFlapRatioThresh) { + Log.i(TAG,"flapCheck() - *** flap detected ***"); + retVal = true; + } + } + return retVal; + } + + /**************************************************************** * checkAlarm() - checks the current accelerometer data and uses @@ -600,7 +702,7 @@ public abstract class SdDataSource { * state. * Sets mSdData.alarmState and mSdData.hrAlarmStanding */ - private void alarmCheck() { + private void alarmCheck(boolean flapDetected) { boolean inAlarm = false; // Avoid potential divide by zero issue if (mSdData.specPower == 0) @@ -615,6 +717,13 @@ public abstract class SdDataSource { } } + if (mSdData.mFlapAlarmActive) { + if (flapDetected) { + inAlarm = true; + mSdData.alarmCause = mSdData.alarmCause + "Flap "; + } + } + if (mSdData.mCnnAlarmActive) { if (mSdData.mPseizure > 0.5) { inAlarm = true; @@ -1144,6 +1253,30 @@ public abstract class SdDataSource { Log.v(TAG, "updatePrefs() OsdAlarmActive = " + mSdData.mOsdAlarmActive); mUtil.writeToSysLogFile("updatePrefs() OsdAlarmActive = " + mSdData.mOsdAlarmActive); + mSdData.mFlapAlarmActive = SP.getBoolean("FlapAlarmActive", true); + Log.v(TAG, "updatePrefs() FlapAlarmActive = " + mSdData.mFlapAlarmActive); + mUtil.writeToSysLogFile("updatePrefs() FlaplarmActive = " + mSdData.mFlapAlarmActive); + + prefStr = SP.getString("FlapAlarmThresh", "SET_FROM_XML"); + mFlapThresh = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() FlapAlarmThresh = " + mFlapThresh); + mUtil.writeToSysLogFile("updatePrefs() FlapThresh = " + mFlapThresh); + + prefStr = SP.getString("FlapAlarmRatioThresh", "SET_FROM_XML"); + mFlapRatioThresh = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() FlapAlarmRatioThresh = " + mFlapRatioThresh); + mUtil.writeToSysLogFile("updatePrefs() FlapAlarmRatioThresh = " + mFlapRatioThresh); + + prefStr = SP.getString("FlapAlarmFreqMin", "SET_FROM_XML"); + mFlapFreqMin = (double) Double.parseDouble(prefStr); + Log.v(TAG, "updatePrefs() FlapAlarmFreqMin = " + mFlapFreqMin); + mUtil.writeToSysLogFile("updatePrefs() FlapAlarmFreqMin = " + mFlapFreqMin); + + prefStr = SP.getString("FlapAlarmFreqMax", "SET_FROM_XML"); + mFlapFreqMax = (double) Double.parseDouble(prefStr); + Log.v(TAG, "updatePrefs() FlapAlarmFreqMax = " + mFlapFreqMax); + mUtil.writeToSysLogFile("updatePrefs() FlapAlarmFreqMax = " + mFlapFreqMax); + mSdData.mCnnAlarmActive = SP.getBoolean("CnnAlarmActive", false); Log.v(TAG, "updatePrefs() CnnAlarmActive = " + mSdData.mCnnAlarmActive); mUtil.writeToSysLogFile("updatePrefs() CnnAlarmActive = " + mSdData.mCnnAlarmActive); diff --git a/app/src/main/java/uk/org/openseizuredetector/SdServer.java b/app/src/main/java/uk/org/openseizuredetector/SdServer.java index d86a58e..4ad088f 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdServer.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdServer.java @@ -100,7 +100,7 @@ public class SdServer extends Service implements SdDataReceiver { private int mCurrentNotificationAlarmLevel = -999; private SdWebServer webServer = null; private final static String TAG = "SdServer"; - private Timer dataLogTimer = null; + //private Timer dataLogTimer = null; private CancelAudibleTimer mCancelAudibleTimer = null; private int mCancelAudiblePeriod = 10; // Cancel Audible Period in minutes private long mCancelAudibleTimeRemaining = 0; @@ -127,6 +127,7 @@ public class SdServer extends Service implements SdDataReceiver { private boolean mSMSAlarm = false; private String[] mSMSNumbers; private String mSMSMsgStr = "default SMS Message"; + private String mSMSFalseAlarmMsgStr = "default SMS False Alarm Message"; public Time mSMSTime = null; // last time we sent an SMS Alarm (limited to one per minute) public SmsTimer mSmsTimer = null; // Timer to wait for specified time before sending an alert to give the user chance to cancel it. public int mSmsTimerSecs = 10; // Time delay in seconds before sending SMS alert. @@ -140,7 +141,7 @@ public class SdServer extends Service implements SdDataReceiver { public boolean mLogNDA = false; private String mAuthToken = null; - private long mEventsTimerPeriod = 60; // Number of seconds between checks to see if there are unvalidated remote events. + private long mEventsTimerPeriod = 600; // Number of seconds between checks to see if there are unvalidated remote events. 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 = 6; // Period in seconds between uploads to the remote server. @@ -329,10 +330,11 @@ public class SdServer extends Service implements SdDataReceiver { // Start timer to log data regularly.. + /* if (dataLogTimer == null) { Log.v(TAG, "onStartCommand(): starting dataLog timer"); mUtil.writeToSysLogFile("SdServer.onStartCommand() - starting dataLog timer"); - /*dataLogTimer = new Timer(); + dataLogTimer = new Timer(); dataLogTimer.schedule(new TimerTask() { @Override public void run() { @@ -340,11 +342,13 @@ public class SdServer extends Service implements SdDataReceiver { logData(); } }, 0, 1000 * 60); - */ + } else { Log.v(TAG, "onStartCommand(): dataLog timer already running."); mUtil.writeToSysLogFile("SdServer.onStartCommand() - dataLog timer already running???"); } + */ + if (mLogDataRemote) { startEventsTimer(); @@ -958,6 +962,48 @@ public class SdServer extends Service implements SdDataReceiver { } } + /** + * Sends SMS Alarms to the telephone numbers specified in mSMSNumbers[] + * Attempts to find a better location, and sends a second SMS after location search + * complete (onLocationReceived()). + */ + public void sendFalseAlarmSMS() { + AlertDialog ad; + if (mSMSAlarm) { + if (!mCancelAudible) { + Log.i(TAG, "sendFalseAlarmsSMS() - Sending to " + mSMSNumbers.length + " Numbers"); + mUtil.writeToSysLogFile("SdServer.sendFalseAlarmsSMS()"); + 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); + + // SmsManager sm = SmsManager.getDefault(); + for (int i = 0; i < mSMSNumbers.length; i++) { + Log.i(TAG, "sendFalseAlarmsSMS() - Sending to " + mSMSNumbers[i]); + //sendSMS(new String(mSMSNumbers[i]), mSMSFalseAlarmMsgStr + " - " + dateStr + " " + shortUuidStr); + try { + SmsManager sm = SmsManager.getDefault(); + sm.sendTextMessage(mSMSNumbers[i], null, mSMSFalseAlarmMsgStr + " - " + dateStr + " " + shortUuidStr, + null, null); + } catch (Exception e) { + Log.e(TAG, "sendFalseAlarmsSMS - Failed to send SMS Message"); + mUtil.showToast(getString(R.string.failed_to_send_sms)); + } + + } + + } else { + Log.i(TAG, "sendFalseAlarmSMS() - Cancel Audible Active - not sending SMS"); + mUtil.showToast(getString(R.string.cancel_audible_not_sending_sms)); + } + } else { + Log.i(TAG, "sendFalseAlarmSMS() - SMS Alarms Disabled - not doing anything!"); + mUtil.showToast(getString(R.string.sms_alarms_disabled)); + } + } + + /** * smsCanelClickListener - onClickListener for the SMS cancel dialog box. If the @@ -1261,11 +1307,14 @@ public class SdServer extends Service implements SdDataReceiver { 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); Log.v(TAG, "updatePrefs() - mSMSNumbers = " + mSMSNumbers); mUtil.writeToSysLogFile("updatePrefs() - mSMSNumbers = " + mSMSNumbers); + mSMSMsgStr = SP.getString("SMSMsg", "Seizure Detected!!!"); + Log.v(TAG, "updatePrefs() - SMSMsgStr = " + mSMSMsgStr); + mSMSFalseAlarmMsgStr = SP.getString("SMSFalseAlarmMsg", "False Alarm, Sorry!"); + Log.v(TAG, "updatePrefs() - SMSFalseAlarmMsgStr = " + mSMSFalseAlarmMsgStr); String smsDelayPeriodStr = SP.getString("SMSDelayPeriod","10"); mSmsTimerSecs = Integer.parseInt(smsDelayPeriodStr); diff --git a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java index dde3e9e..4ee024e 100644 --- a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java @@ -88,8 +88,10 @@ public class StartupActivity extends AppCompatActivity { private boolean mLocationPermissions2Requested; private boolean mSmsPermissionsRequested; private boolean mPermissionsRequested; + private boolean mActivityPermissionsRequested = false; private boolean mBindInProgress = false; + public final String[] REQUIRED_PERMISSIONS = { //Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.WAKE_LOCK, @@ -328,14 +330,22 @@ public class StartupActivity extends AppCompatActivity { //pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server)); requestBTPermissions(); allOk = false; - } else if (mBleDeviceAddr.equals("")) { - Log.i(TAG,"BLE data source selected, but no device address specified - starting BLEScanActivity"); + } else if (mBleDeviceAddr.equals("")) { + Log.i(TAG, "BLE data source selected, but no device address specified - starting BLEScanActivity"); Intent i; i = new Intent(getApplicationContext(), BLEScanActivity.class); startActivity(i); finish(); return; } + } else if (!mUtil.areActivityPermissionsOk()) { + Log.i(TAG, "Activity permissions NOT OK"); + tv.setText(getString(R.string.ActivityPermissionWarning)); + tv.setBackgroundColor(alarmColour); + tv.setTextColor(alarmTextColour); + requestActivityPermissions(); + allOk = false; + } else if (smsAlarmsActive && !areSMSPermissions1OK()) { Log.i(TAG, "SMS permissions NOT OK"); tv.setText(getString(R.string.SmsPermissionWarning)); @@ -699,6 +709,7 @@ public class StartupActivity extends AppCompatActivity { return allOk; } + public boolean areSMSPermissions1OK() { boolean allOk = true; Log.v(TAG, "areSMSPermissions1 OK()"); @@ -879,6 +890,33 @@ public class StartupActivity extends AppCompatActivity { } } + public void requestActivityPermissions() { + if (mActivityPermissionsRequested) { + Log.i(TAG, "requestActivityPermissions() - request already sent - not doing anything"); + } else { + Log.i(TAG, "requestActivityPermissions() - requesting permissions"); + mActivityPermissionsRequested = true; + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder( + this); + alertDialogBuilder + .setTitle(R.string.activity_permissions_required) + .setMessage(R.string.activity_permissions_rationale) + .setCancelable(false) + .setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + Log.i(TAG, "requestActivityPermissions(): Launching ActivityCompat.requestPermissions()"); + ActivityCompat.requestPermissions(StartupActivity.this, + mUtil.getRequiredActivityPermissions(), + 49); + } + }) + .create().show(); + } + } + + + @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { diff --git a/app/src/main/res/layout/activity_log_manager_control.xml b/app/src/main/res/layout/activity_log_manager_control.xml index 66d11b1..c5dafde 100644 --- a/app/src/main/res/layout/activity_log_manager_control.xml +++ b/app/src/main/res/layout/activity_log_manager_control.xml @@ -33,6 +33,7 @@ android:text="000" /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_activity_actions.xml b/app/src/main/res/menu/main_activity_actions.xml index 8d443a7..a5a2faa 100644 --- a/app/src/main/res/menu/main_activity_actions.xml +++ b/app/src/main/res/menu/main_activity_actions.xml @@ -73,6 +73,11 @@ android:icon="@drawable/stop_server" app:showAsAction="never|withText" android:title="@string/test_sms_alarm_notification" /> +