package com.notificationFramework.stimulusStrategy; import android.Manifest; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.TriggerEvent; import android.hardware.TriggerEventListener; import android.os.IBinder; import android.os.SystemClock; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.google.android.gms.awareness.Awareness; import com.google.android.gms.awareness.state.Weather; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.places.PlaceLikelihood; import com.notificationFramework.sedentary.frontEnd.MainActivity; import com.notificationFramework.sedentary.frontEnd.R; import com.notificationFramework.sedentary.frontEnd.RequestPermission; import com.notificationFramework.sedentary.frontEnd.SaveFile; import java.util.ArrayList; import java.util.Calendar; import java.util.List; /** * Created by Peter De Jonckheere on 17/01/2018. * <p> * The strategy which uses the significant motion sensor provided by Android in conjunction * with the step counter sensor to determine movement and sedentary behaviour. This method is * as accurate as the accelerometer method but requires less power as the reporting mode of * both sensors used in this method is not as persistent as streaming. Uses the step counter in * addition to this class as a time period for which the movement lasted was required to be * determined. * </p> */ public class SigMotionDetect extends Service implements StimulusStrategy { /** * The instance of the SensorManager used to handle the interfacing between the application * and sensor hardware */ private SensorManager mSensorManager; /** * An instance of the sensor whihc will be used in this class */ private Sensor md; /** * The trigger event listener attached to the sensor */ private TriggerEventListener tel; /** * The instance of the Android OS service to handle alarms */ private AlarmManager am; /** * The previous step count as reported by the step counter sensor */ private float prevStepCount; /** * The previous timestamp of the previous report from the step counter sensor */ private long prevTimeStamp; /** * The daily record of minutes moved */ private int minutes; /** * The number of seconds moved */ private double seconds = 0; /** * The previous value of the seconds field */ private double prevSeconds = 0; /** * The previous value of the minutes field */ private int prevMinutes; /** * The method which is called when the SigMotionDetect service is started. Sets up a number of * fields then delegates for the daily progress and clock to be set up. Also sets up a trigger * event listener for the significant motion detection sensor and calls the method to set up the * step counter service. * * @param intent the intent used to start this service * @param flags additional information about this service * @param startId the unique identifier for this service * @return the conditions under which the OS should treat this service * @see android.app.Service * @see android.hardware.TriggerEventListener */ @Override public int onStartCommand(Intent intent, int flags, int startId) { am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); setUpClock(); setUpDailyProgress(); prevMinutes = minutes; mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE); try { md = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION); } catch (NullPointerException e) { Log.e("SENSORERROR", "COULD NOT FIND SENSOR"); } tel = new TriggerEventListener() { @Override public void onTrigger(TriggerEvent triggerEvent) { monitor(); } }; stepCounter(); mSensorManager.requestTriggerSensor(tel, md); return START_STICKY; } /** * Uses the Shared Preferences to set up the daily progress and determine if the daily progress * should be reset for a new day. This is done by storing the current day in the Shared * Preferences and only changing this when the current calendar does not match this day. */ private void setUpClock() { Intent i = new Intent(this, com.notificationFramework.stimulus.SedentaryStimulus.class); PendingIntent pi = PendingIntent.getBroadcast(this, R.integer.alarm_rc, i, 0); SharedPreferences shared = this.getSharedPreferences(getString( R.string.preference_file_key), Context.MODE_PRIVATE); int interval = shared.getInt(getString(R.string.daily_goal), getResources().getInteger(R.integer.notify_period_minutes)); am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + (1000 * 60 * interval), pi); //test //am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 3000, pi); } /** * This method is called when an event is triggered by the trigger event listener for the * significant motion sensor. A step calculation is first performed then based on the results of * this, the alarm is cancelled and values updated as appropriate. A new request for a trigger * sensor is also made. */ public void monitor() { stepCalc(); //If a small movement has occurred cancel the alarm, record a movement and set up a new //alarm if (seconds > prevSeconds) { Intent i = new Intent(this, com.notificationFramework.stimulus.SedentaryStimulus.class); PendingIntent pi = PendingIntent.getBroadcast(this, R.integer.alarm_rc, i, 0); am.cancel(pi); SaveFile.recordNotification(0, 0, 1, 0, this); setUpClock(); prevSeconds = seconds; } //If a longer movement has occurred add a minute to the daily progress and sets new history //values if (minutes > prevMinutes) { storeData(); prevStepCount = StepCounter.stepCount; prevTimeStamp = StepCounter.timestamp; prevMinutes = minutes; } mSensorManager.requestTriggerSensor(tel, md); } /** * Carries out the calculation to determine how long a movement has lasted for. This is done * using a combination of the number of steps taken since the previous trigger and the time * since the previous trigger. This is based on an average of 60 steps per minute for the time * calculation. The average time of these two metrics is used to determine the length of * movement time. */ private void stepCalc() { float stepDifference = 0; long timeDifference = 0; if (prevStepCount != 0) { stepDifference = StepCounter.stepCount - prevStepCount; } else { prevStepCount = StepCounter.stepCount; } if (prevTimeStamp != 0) { timeDifference = StepCounter.timestamp - prevTimeStamp; } else { prevTimeStamp = StepCounter.timestamp; } float averageTime = ((timeDifference / 3600) + (stepDifference / 60)) / 2; if (averageTime > ((seconds / 60) + 0.25)) { seconds = seconds + 15; } if ((averageTime > 1) || (seconds >= 60)) { minutes = minutes + Math.round(averageTime); seconds = 0; prevSeconds = 0; } } /** * Starts the step counter service to monitor steps using the step counter sensor. */ private void stepCounter() { Intent intent = new Intent(this, StepCounter.class); startService(intent); } /** * Uses the Shared Preferences to set up the daily progress and determine if the daily progress * should be reset for a new day. This is done by storing the current day in the Shared * Preferences and only changing this when the current calendar does not match this day. */ private void setUpDailyProgress() { SharedPreferences sharedPref = this.getSharedPreferences(getString( R.string.preference_file_key), Context.MODE_PRIVATE); int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_YEAR); if (currentDay == sharedPref.getInt(getString(R.string.progress_day), 0)) { minutes = sharedPref.getInt(getString(R.string.daily_progress), 0); if (minutes >= sharedPref.getInt(getString(R.string.daily_goal_set), getResources().getInteger(R.integer.daily_goal_minutes)) && !(sharedPref.getBoolean((getString(R.string.goal_met)), false))) { goalNotify(); sharedPref.edit().putBoolean(getString(R.string.goal_met), true).commit(); } } else { minutes = 0; SharedPreferences.Editor editor = sharedPref.edit(); editor.putInt(getString(R.string.progress_day), currentDay); editor.putInt(getString(R.string.daily_progress), minutes); editor.putBoolean(getString(R.string.goal_met), false); editor.commit(); } } /** * Stores the recorded daily progress in the Shared Preferences. */ private void storeData() { SharedPreferences sharedPref = this.getSharedPreferences(getString( R.string.preference_file_key), Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPref.edit(); editor.putInt(getString(R.string.daily_progress), minutes); editor.commit(); } /** * A method added after the user trial to trigger once daily if a goal has been reached. A local * broadcast is then sent to the relevant stimulus class and on to the relevant notification * class. */ public void goalNotify() { Intent i = new Intent("FEEDBACKFILTER"); final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this.getApplicationContext()); BroadcastReceiver goal = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { SharedPreferences shared = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); if ((checkTime(shared, context)) && (shared.getBoolean(context.getString(R.string.notf_switch), true))) { Intent i = new Intent(context.getApplicationContext(), com.notificationFramework.notification.GoalNotification.class); context.getApplicationContext().startService(i); } lbm.unregisterReceiver(this); } /** * Checks the current time against the do not disturb time by first checking if the time spans 2 * days, then checking between the first time and midnight, then midnight and the second time, * followed finally by the simple check between the two times if the time does not span 2 days. * It is done in this way to avoid compications with date changes as each time is obtained using * the current calendar as a template so if done correctly dates should not need to be changed. * * @param shared the SharedPreferences instance set up in the onReceive() method * @param context the application context which this method has been called from * @return false if the time is within the do not disturb period and so a notification should * not be sent, true if a notification should be sent */ private boolean checkTime(SharedPreferences shared, Context context) { Calendar start = Calendar.getInstance(); start.set(Calendar.HOUR_OF_DAY, shared.getInt(context.getString(R.string.dnd_shour), 23)); start.set(Calendar.MINUTE, shared.getInt(context.getString(R.string.dnd_smin), 0)); Calendar midnightCal = Calendar.getInstance(); midnightCal.set(Calendar.HOUR_OF_DAY, 23); midnightCal.set(Calendar.MINUTE, 59); midnightCal.set(Calendar.SECOND, 59); Calendar end = Calendar.getInstance(); end.set(Calendar.HOUR_OF_DAY, shared.getInt(context.getString(R.string.dnd_ehour), 9)); end.set(Calendar.MINUTE, shared.getInt(context.getString(R.string.dnd_emin), 0)); Calendar curCal = Calendar.getInstance(); if (shared.getInt(context.getString(R.string.dnd_shour), 23) > shared.getInt(context.getString(R.string.dnd_ehour), 9)) { if (curCal.getTime().after(start.getTime()) && curCal.getTime().before(midnightCal.getTime())) { return false; } else { midnightCal.set(Calendar.HOUR_OF_DAY, 0); midnightCal.set(Calendar.MINUTE, 0); midnightCal.set(Calendar.SECOND, 1); if (curCal.getTime().after(midnightCal.getTime()) && curCal.getTime().before(end.getTime())) { return false; } else { return true; } } } else if (curCal.getTime().after(start.getTime()) && curCal.getTime().before(end.getTime())) { return false; } else { return true; } } }; lbm.registerReceiver(goal, new IntentFilter("GOALFILTER")); lbm.sendBroadcast(i); } /** * The method which reponds to a bind request for this service. * * @param intent the intent used to rqeuest binding of this service * @return null as no bind requests are required. * @see android.app.Service **/ @Nullable @Override public IBinder onBind(Intent intent) { return null; } }