package edu.fsu.cs.contextprovider.sensor; import java.util.Date; import java.util.List; import java.util.Stack; import edu.fsu.cs.contextprovider.ContextExpandableListActivity; import edu.fsu.cs.contextprovider.PrefsActivity; import edu.fsu.cs.contextprovider.data.ContextConstants; 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.SharedPreferences.OnSharedPreferenceChangeListener; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.IBinder; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; public class AccelerometerService extends Service implements SensorEventListener, OnSharedPreferenceChangeListener { private static final String TAG = "AccelerometerService"; private static final boolean DEBUG = false; private static boolean serviceEnabled = false; private SensorManager sm; private Sensor accelerometerSensor; public final static int READINGS_REMEMBER_MAX = 100; SharedPreferences prefs; private boolean shakeEnabled; private String accelPoll; // eg SensorManager.SENSOR_DELAY_UI private int accelPeriod; private String ignoreThreshold; private int ignoreRange; private int ignoreCounter = 0; /* * TODO: Make this a circular buffer or a double ended queue It seems that * deque (double ended queue) is only in the newest (3.0) android sdk */ public static Stack<Float> xHistory = new Stack<Float>(); public static Stack<Float> yHistory = new Stack<Float>(); public static Stack<Float> zHistory = new Stack<Float>(); public static float x = 0, y = 0, z = 0; public static float lastX = 0, lastY = 0, lastZ = 0; private static long step_count = 0; private static long step_timestamp = 0; private static int shakeCount = 0; // private Date lastStep = new Date(); /* Accelerometer -> walking calculation variables */ private static float mLimit = 10; private static float mLastValues[] = new float[3 * 2]; private static float mScale[] = new float[2]; private static float mYOffset; private static float mLastDirections[] = new float[3 * 2]; private static float mLastExtremes[][] = { new float[3 * 2], new float[3 * 2] }; private static float mLastDiff[] = new float[3 * 2]; private static int mLastMatch = -1; public AccelerometerService() { xHistory.ensureCapacity(READINGS_REMEMBER_MAX); yHistory.ensureCapacity(READINGS_REMEMBER_MAX); zHistory.ensureCapacity(READINGS_REMEMBER_MAX); int h = 480; // TODO: remove this constant mYOffset = h * 0.5f; mScale[0] = -(h * 0.5f * (1.0f / (SensorManager.STANDARD_GRAVITY * 2))); mScale[1] = -(h * 0.5f * (1.0f / (SensorManager.MAGNETIC_FIELD_EARTH_MAX))); } static public long getStepCount() { return step_count; } static public long getStepTimestamp() { return step_timestamp; } static public Date getLastStepTimestamp() { return new Date(step_timestamp); } @Override public void onCreate() { if (!serviceEnabled) { startService(); } } private void startService() { getPrefs(); // make sure not to call it twice sm = (SensorManager) getSystemService(SENSOR_SERVICE); List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ACCELEROMETER); if (sensors != null && sensors.size() > 0) { accelerometerSensor = sensors.get(0); switch (accelPeriod) { case 4: sm.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_FASTEST); break; case 3: sm.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME); break; case 2: sm.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL); break; case 1: default: sm.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_UI); break; } serviceEnabled = true; } else { Toast.makeText(this, "Accelerometer sensor is not available on this device!", Toast.LENGTH_SHORT).show(); } IntentFilter restartFilter = new IntentFilter(); restartFilter.addAction(ContextConstants.CONTEXT_RESTART_INTENT); registerReceiver(restartIntentReceiver, restartFilter); } private void getPrefs() { prefs = getSharedPreferences(ContextConstants.CONTEXT_PREFS, MODE_WORLD_READABLE); accelPoll = prefs.getString(ContextConstants.PREFS_ACCEL_POLL_FREQ, "1"); accelPeriod = Integer.parseInt(accelPoll); ignoreThreshold = prefs.getString(ContextConstants.PREFS_ACCEL_IGNORE_THRESHOLD, "0"); ignoreRange = Integer.parseInt(ignoreThreshold); prefs.registerOnSharedPreferenceChangeListener(this); } private void stopService() { sm.unregisterListener(this); PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this); unregisterReceiver(restartIntentReceiver); } BroadcastReceiver restartIntentReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (DEBUG) { Log.d(TAG, TAG + "Restart Intent: " + intent.getAction()); } if (serviceEnabled) { stopService(); } startService(); } }; @Override public void onDestroy() { if (serviceEnabled) { stopService(); } super.onDestroy(); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // we don't need this } @Override public void onSensorChanged(SensorEvent event) { Sensor sensor = event.sensor; if (sensor.getType() != Sensor.TYPE_ACCELEROMETER) { return; } if (ignoreCounter >= ignoreRange) { ignoreCounter = 0; } else { ignoreCounter++; return; } /* * TODO: Replace synchronized(): Object level lock, make granularity * finer with an explicitly lock */ synchronized (this) { // if (xHistory.size() == READINGS_REMEMBER_MAX) { // xHistory.remove(xHistory.size()-1); // yHistory.remove(yHistory.size()-1); // zHistory.remove(zHistory.size()-1); // } // // xHistory.push(event.values[0]); // yHistory.push(event.values[1]); // zHistory.push(event.values[2]); lastX = x; lastY = y; lastZ = z; x = event.values[0]; y = event.values[0]; z = event.values[0]; // // if (DEBUG) { // Log.i(TAG, "New: [" + event.values[0] + "," + event.values[1] + // "," + event.values[2] + "]"); // String history = new String(); // for (int i=0; i < xHistory.size(); ++i) { // Float a = xHistory.get(i); // Float b = yHistory.get(i); // Float c = zHistory.get(i); // // history += "[" + a + "," + b + "," + c + "]" + ", "; // } // // Log.i(TAG, "Old: " + history); // } } /* Check if a step was taken */ boolean stepTaken = isStepTaken(); if (stepTaken == true) { step_count++; step_timestamp = System.currentTimeMillis(); if (DEBUG) { Log.i(TAG, "Step Taken: [" + step_count + "] | At: [" + step_timestamp + "]"); } } if (shakeEnabled) { if (ContextExpandableListActivity.running == false) { boolean isShaken = isShakeEnough(x, y, z); if (isShaken == true) { if (DEBUG) { Log.i(TAG, "Shake detected, going to start activity"); } Intent intent = new Intent(this, edu.fsu.cs.contextprovider.ContextExpandableListActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } } } } private boolean isStepTaken() { boolean stepTaken = false; float vSum = 0; float v; int k = 0; vSum += mYOffset + x * mScale[1]; vSum += mYOffset + y * mScale[1]; vSum += mYOffset + z * mScale[1]; v = vSum / 3; float direction = (v > mLastValues[k] ? 1 : (v < mLastValues[k] ? -1 : 0)); if (direction == -mLastDirections[k]) { // Direction changed int extType = (direction > 0 ? 0 : 1); // minumum or maximum? mLastExtremes[extType][k] = mLastValues[k]; float diff = Math.abs(mLastExtremes[extType][k] - mLastExtremes[1 - extType][k]); if (diff > mLimit) { boolean isAlmostAsLargeAsPrevious = diff > (mLastDiff[k] * 2 / 3); boolean isPreviousLargeEnough = mLastDiff[k] > (diff / 3); boolean isNotContra = (mLastMatch != 1 - extType); if (isAlmostAsLargeAsPrevious && isPreviousLargeEnough && isNotContra) { if (DEBUG) { Log.i(TAG, "step"); } stepTaken = true; mLastMatch = extType; } else { mLastMatch = -1; } } mLastDiff[k] = diff; } mLastDirections[k] = direction; mLastValues[k] = v; return stepTaken; } private boolean isShakeEnough(float x, float y, float z) { double force = 0.0d; force += Math.pow((x - lastX) / SensorManager.GRAVITY_EARTH, 2.0); force += Math.pow((y - lastY) / SensorManager.GRAVITY_EARTH, 2.0); force += Math.pow((z - lastZ) / SensorManager.GRAVITY_EARTH, 2.0); force = Math.sqrt(force); lastX = x; lastY = y; lastZ = z; if (DEBUG) { Log.i(TAG, "Force detected [" + force + "]"); } if (force > Defaults.THRESHOLD) { if (DEBUG) { Log.i(TAG, "Shake detected but we haven't reached our limit yet"); } shakeCount++; if (shakeCount > Defaults.SHAKE_COUNT) { shakeCount = 0; lastX = 0; lastY = 0; lastZ = 0; return true; } } return false; } private static class Defaults { public static final float THRESHOLD = (float) 0.275; public static final int SHAKE_COUNT = 4; } public static void setSensitivity(float sensitivity) { mLimit = sensitivity; // 1.97 2.96 4.44 6.66 10.00 15.00 22.50 33.75 // 50.62 } @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (key.equals(ContextConstants.PREFS_ACCEL_POLL_FREQ)) { Toast.makeText(this, "AccelService: PREFS_ACCEL_POLL_FREQ", Toast.LENGTH_SHORT).show(); getPrefs(); stopService(); startService(); } else if (key.equals(ContextConstants.PREFS_ACCEL_IGNORE_THRESHOLD)) { Toast.makeText(this, "AccelService: PREFS_ACCEL_IGNORE_THRESHOLD", Toast.LENGTH_SHORT).show(); getPrefs(); } } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }