/** * ORcycle, Copyright 2014, 2015, PSU Transportation, Technology, and People Lab. * * ORcycle 2.2.0 has introduced new app features: safety focus with new buttons * to report safety issues and crashes (new questionnaires), expanded trip * questionnaire (adding questions besides trip purpose), app utilization * reminders, app tutorial, and updated font and color schemes. * * @author Bryan.Blanc <bryanpblanc@gmail.com> (code) * @author Miguel Figliozzi <figliozzi@pdx.edu> and ORcycle team (general app * design and features, report questionnaires and new ORcycle features) * * For more information on the project, go to * http://www.pdx.edu/transportation-lab/orcycle and http://www.pdx.edu/transportation-lab/app-development * * Updated/modified for Oregon pilot study and app deployment. * * ORcycle is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or any later version. * ORcycle is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with * ORcycle. If not, see <http://www.gnu.org/licenses/>. * ************************************************************************************* * * Cycle Altanta, Copyright 2012 Georgia Institute of Technology * Atlanta, GA. USA * * @author Christopher Le Dantec <ledantec@gatech.edu> * @author Anhong Guo <guoanhong15@gmail.com> * * Updated/Modified for Atlanta's app deployment. Based on the * CycleTracks codebase for SFCTA. * * CycleTracks, Copyright 2009,2010 San Francisco County Transportation Authority * San Francisco, CA, USA * * @author Billy Charlton <billy.charlton@sfcta.org> * * This file is part of CycleTracks. * * CycleTracks is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CycleTracks is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CycleTracks. If not, see <http://www.gnu.org/licenses/>. */ package edu.pdx.cecs.orcycle; import android.app.Service; import android.content.Context; import android.content.Intent; import android.hardware.Sensor; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.util.Log; public class RecordingService extends Service implements IRecordService, LocationListener { public final static String MODULE_TAG = "RecordingService"; IRecordServiceListener recordServiceListener; LocationManager lm = null; // Aspects of the currently recording trip Location lastLocation; float distanceMeters; // The distance travelled in meters private TripData trip = null; private final static long MIN_TIME_BETWEEN_READINGS_MILLISECONDS = 1000; private final static float MIN_DISTANCE_BETWEEN_READINGS_METERS = 0.0f; private final static int MIN_DESIRED_ACCURACY = 19; private final static float MAX_BELIEVABLE_BIKE_SPEED_MPS = 26.8224f; // In meters per second = 60 Miles per hour public final static int STATE_IDLE = 0; public final static int STATE_RECORDING = 1; public final static int STATE_PAUSED = 2; public final static int STATE_FULL = 3; int state = STATE_IDLE; private SpeedMonitor speedMonitor; private int pauseId = -1; private SensorRecorder accelerationSensorRecorder; private final MyServiceBinder myServiceBinder = new MyServiceBinder(); // ********************************************************************************* // * Service Implementation // ********************************************************************************* @Override public IBinder onBind(Intent arg0) { return myServiceBinder; } @Override public void onCreate() { super.onCreate(); try { accelerationSensorRecorder = SensorRecorder.create(this, Sensor.TYPE_ACCELEROMETER); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } @Override public void onDestroy() { super.onDestroy(); } public class MyServiceBinder extends Binder { /** * This function returns a reference to the bound service * * @return Reference to the bound service */ public RecordingService getService() { return RecordingService.this; } public int getState() { return RecordingService.this.getState(); } public void startRecording(TripData trip) { RecordingService.this.startRecording(trip); } public void cancelRecording() { RecordingService.this.cancelRecording(); } public long finishRecording() { return RecordingService.this.finishRecording(); } public long getCurrentTripID() { return RecordingService.this.getCurrentTripID(); } public void pauseRecording(int pauseId) { RecordingService.this.pauseRecording(pauseId); } public void resumeRecording() { RecordingService.this.resumeRecording(); } public void reset() { RecordingService.this.reset(); } public int pauseId() { return this.pauseId(); } public void setListener(IRecordServiceListener mia) { RecordingService.this.setListener(mia); } } // ********************************************************************************* // * RecordingService Implementation // ********************************************************************************* public int getState() { return state; } public long getCurrentTripID() { if (RecordingService.this.trip != null) { return RecordingService.this.trip.tripid; } return -1; } public TripData getCurrentTripData() { return trip; } public int pauseId() { return pauseId; } public void setListener(IRecordServiceListener listener) { RecordingService.this.recordServiceListener = listener; //notifyListeners(); } public void reset() { RecordingService.this.state = STATE_IDLE; if (null != accelerationSensorRecorder) { accelerationSensorRecorder.reset(); } } /** * Start the recording process: * - reset trip variables * - enable location manager updates * - enable bike bell timer */ public void startRecording(TripData trip) { this.state = STATE_RECORDING; this.trip = trip; this.pauseId = -1; this.distanceMeters = 0.0f; this.lastLocation = null; // Start listening for GPS updates! registerLocationUpdates(); // Start listening for sensor updates! startSensorRecorders(); if (null == speedMonitor) { speedMonitor = new SpeedMonitor(this); } speedMonitor.start(); } /** * Pause the recording process: * - disable location manager updates * - start recording paused time */ public void pauseRecording(int pauseId) { this.pauseId = pauseId; this.state = STATE_PAUSED; trip.startPause(); pauseSensorRecorders(); if (null != speedMonitor) speedMonitor.cancel(); } /** * Resume recording process: * - enable location manager updates * - calculate time paused and save in trip data */ public void resumeRecording() { this.state = STATE_RECORDING; this.pauseId = -1; trip.finishPause(); resumeSensorRecorders(); if (null != speedMonitor) speedMonitor.start(); } /** * End the recording process: * - disable location manager updates * - clear notifications * - if trip has any points, finalize data collection and push to * database, otherwise cancel trip and don't save any data */ public long finishRecording() { this.state = STATE_FULL; if (null != speedMonitor) speedMonitor.cancel(); // Disable location manager updates unregisterLocationUpdates(); unregisterSensorRecorders(); // if (trip.getNumPoints() > 0) { trip.finish(); // makes some final calculations and pushed trip to the database } else { cancelRecording(); // TODO: isn't the tripid invalid at this point? Verify. } return trip.tripid; } public void cancelRecording() { if (null != speedMonitor) speedMonitor.cancel(); if (trip != null) { trip.dropTrip(); } // Disable location manager updates unregisterLocationUpdates(); unregisterSensorRecorders(); this.state = STATE_IDLE; } // ********************************************************************************* // * LocationListener Implementation // ********************************************************************************* private void registerLocationUpdates() { LocationManager lm; if (null != (lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE))) { lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_BETWEEN_READINGS_MILLISECONDS, MIN_DISTANCE_BETWEEN_READINGS_METERS, this); } } private void unregisterLocationUpdates() { LocationManager lm; if (null != (lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE))) { lm.removeUpdates(this); } } @Override public void onLocationChanged(Location location) { try { if (location != null) { long currentTimeMillis = System.currentTimeMillis(); double timeSinceStart = currentTimeMillis - trip.getStartTime(); float accuracy = location.getAccuracy(); if ((null != speedMonitor) && (location.hasSpeed())) speedMonitor.recordSpeed(currentTimeMillis, location.getSpeed()); // The first 2 points are recorded regardless of accuracy, and // we ignore accuracy for the first minute. After those 2 // conditions, we only add points if the accuracy is decent if ((accuracy <= MIN_DESIRED_ACCURACY) || (trip.getNumPoints() < 2) || (timeSinceStart < 60000 /* milliseconds*/ )) { if (lastLocation != null) { distanceMeters += lastLocation.distanceTo(location); } trip.addPointNow(location, System.currentTimeMillis(), distanceMeters); if (null != accelerationSensorRecorder) { accelerationSensorRecorder.writeResult(trip, currentTimeMillis, location); } // Update the status page every time, if we can. // notifyListeners(); lastLocation = location; } } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } @Override public void onProviderDisabled(String arg0) { } @Override public void onProviderEnabled(String arg0) { } @Override public void onStatusChanged(String arg0, int arg1, Bundle arg2) { } // ********************************************************************************* // * SensorEventListener Implementation // ********************************************************************************* private void startSensorRecorders() { if (null != accelerationSensorRecorder) { accelerationSensorRecorder.reset(); accelerationSensorRecorder.start(this); } } private void pauseSensorRecorders() { if (null != accelerationSensorRecorder) { accelerationSensorRecorder.pause(); } } private void resumeSensorRecorders() { if (null != accelerationSensorRecorder) { accelerationSensorRecorder.resume(); } } private void unregisterSensorRecorders() { if (null != accelerationSensorRecorder) { accelerationSensorRecorder.unregister(this); } } }