Something went wrong on our end
-
Peter Joseph De Jonckheere CESM2014 authoredPeter Joseph De Jonckheere CESM2014 authored
Accelerometer.java 10.47 KiB
package com.notificationFramework.stimulusStrategy;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.notificationFramework.sedentary.frontEnd.R;
import com.notificationFramework.sedentary.frontEnd.SaveFile;
import java.util.Calendar;
/**
* Created by Peter De Jonckheere on 09/01/2018.
* <p>
* The strategy which detects sedentary behaviour using the Accelerometer sensor provided by the
* Android hardware. It uses the previous readings to determine if movement has occurred. Upon
* determining a significant enough movement which lasts for a period of 15 seconds, an alarm which
* is set for one notification period from the current time is cancelled. The Accelerometer sensor
* reports in a streaming fashion and so uses significant power on the device and the listener for
* the sensor is constantly registered.
* </p>
*/
public class Accelerometer extends Service implements StimulusStrategy, SensorEventListener {
/**
* The alarm manager instance which is used to manage alarms via the OS
*/
private AlarmManager am;
/**
* An instance of Shared Preferences used throughout the class to obtain and save settings
*/
private SharedPreferences preferences;
/**
* The previous 3 readings of the accelerometer
*/
private float[] history = new float[3];
/**
* The previous timestamp of the accelerometer reading
*/
private long historyTime;
/**
* The last recorded number of minutes moved
*/
private int prevMinutes = 0;
/**
* The current number of minutes moved
*/
private int minutes;
/**
* The current number of seconds moved up to 60
*/
private double seconds = 0;
/**
* The arbitrary default value of sensitivity
*/
private double sensitivity = 0.8;
/**
* An enum which defines values which can be used to alter the sensitivity from within the
* application
*/
private enum sensitivityLevel {
ZERO, VERY_HIGH, HIGH, NORMAL, LOW, VERY_LOW;
}
/**
* The method which is called when the Accelerometer service is started. Sets up a number of
* fields then delegates for the daily progress and clock to be set up.
*
* @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
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
historyTime = SystemClock.elapsedRealtimeNanos();
preferences = getSharedPreferences(getString(R.string.preference_file_key),
Context.MODE_PRIVATE);
sensitivity = sensitivity * sensitivityLevel.valueOf(
preferences.getString(getString(R.string.accel_sensitivity), "NORMAL")).ordinal();
SensorManager mSensorManager =
(SensorManager) this.getSystemService(Context.SENSOR_SERVICE);
//1 second used as the sampling period
try {
mSensorManager.registerListener(this,
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
600000000);
} catch (NullPointerException e) {
Log.e("SENSOR", "FAILED TO FIND ACCELEROMTER");
}
am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
setUpDailyProgress();
setUpClock();
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 setUpDailyProgress() {
int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_YEAR);
if (currentDay == preferences.getInt(getString(R.string.progress_day), 0)) {
minutes = preferences.getInt(getString(R.string.daily_progress), 0);
if (minutes >= preferences.getInt(getString(R.string.daily_goal_set), getResources().getInteger(R.integer.daily_goal_minutes))
&& !(preferences.getBoolean((getString(R.string.goal_met)), false))) {
goalNotify();
preferences.edit().putBoolean(getString(R.string.goal_met), true).commit();
}
} else {
minutes = 0;
SharedPreferences.Editor editor = preferences.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();
}
prevMinutes = minutes;
}
/**
* Sets up the alarm which will trigger the broadcast to the stimulus class, hence triggering a
* notification. The true implementation uses the notification period stored in Shared
* Preferences and a test implementation is also present which uses an arbitrary short time.
*/
private void setUpClock() {
Intent i = new Intent(getBaseContext(),
com.notificationFramework.stimulus.SedentaryStimulus.class);
PendingIntent pi = PendingIntent.getBroadcast(getBaseContext(),
R.integer.alarm_rc, i, PendingIntent.FLAG_UPDATE_CURRENT);
int interval = preferences.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);
}
/**
* The monitor method is called when a movement is identified and uses identical intents and
* pending intents to cancel the alarm set previously. This is also recorded as a movement in
* the log and a new alarm is set.
*/
public void monitor() {
Intent i = new Intent(getBaseContext(),
com.notificationFramework.stimulus.SedentaryStimulus.class);
PendingIntent pi = PendingIntent.getBroadcast(getBaseContext(),
R.integer.alarm_rc, i, PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi);
SaveFile.recordNotification(0, 0, 1, this);
setUpClock();
}
/**
* 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(getBaseContext(),
com.notificationFramework.stimulus.GoalStimulus.class);
SaveFile.recordNotification(1, 1, 0, this);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
lbm.sendBroadcast(i);
}
/**
* Required to be implemented by implementing the SensorEventListener interface. Carries out the
* main work of the class in determining if a movement has taken place based on the values
* passed in in the event param and the previous values stored. This is done by determining if
* multiple values have changed either positively or negatively by the sensitivity level. In
* this case the timestamp is then used to determine the length of time for which the movement
* lasts and this is recorded as the minute value in Shared Preferences.
*
* @param event the sensor event which has occurred (here an Accelerometer event)
* @see android.hardware.SensorEventListener
*/
@Override
public void onSensorChanged(SensorEvent event) {
int moved = 0;
//For each x,y and z plane determine if significant movement has occurred.
for (int i = 0; i < history.length; i++) {
if ((-1 * sensitivity) > (history[i] - event.values[i])
|| (history[i] - event.values[i]) > sensitivity) {
if (!((history[i] - event.values[i]) > 10
&& !(-10 > (history[i]) - event.values[i]))) {
moved++;
}
}
}
//Update the previous values
for (int i = 0; i < history.length; i++) {
history[i] = event.values[i];
}
//If all 3 planes had movement
if (moved >= 3) {
//Update seconds and cancel the alarm if the time is greater than the last seconds
//value
if (((event.timestamp - historyTime) / 1000000000) > (seconds + 15)) {
seconds = seconds + 15;
monitor();
}
//Update minutes if the time is greater than a minute or seconds has reached 60
if ((((event.timestamp - historyTime) / 1000000000) > 60) || (seconds > 60)) {
historyTime = event.timestamp;
seconds = 0;
minutes++;
}
}
//If movement has not occurred update minutes
else if (minutes > prevMinutes) {
SharedPreferences.Editor editor = preferences.edit();
editor.putInt(getString(R.string.daily_progress), minutes);
editor.commit();
prevMinutes = minutes;
}
}
/**
* Unused as accuracy is not a major factor when using previous values. Required by
* SensorEventListener
*
* @param sensor as in SensorEventListener
* @param i as in SensorEventListener
* @see android.hardware.SensorEventListener
*/
@Override
public void onAccuracyChanged(Sensor sensor, int 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;
}
}