package me.guillaumin.android.osmtracker.service.gps;
import me.guillaumin.android.osmtracker.OSMTracker;
import me.guillaumin.android.osmtracker.R;
import me.guillaumin.android.osmtracker.activity.TrackLogger;
import me.guillaumin.android.osmtracker.db.DataHelper;
import me.guillaumin.android.osmtracker.db.TrackContentProvider;
import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema;
import me.guillaumin.android.osmtracker.listener.SensorListener;
import android.app.Notification;
import android.app.NotificationManager;
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.location.GpsSatellite;
import android.location.GpsStatus;
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.preference.PreferenceManager;
import android.util.Log;
/**
* GPS logging service.
*
* @author Nicolas Guillaumin
*
*/
public class GPSLogger extends Service implements LocationListener {
private static final String TAG = GPSLogger.class.getSimpleName();
/**
* Data helper.
*/
private DataHelper dataHelper;
/**
* Are we currently tracking ?
*/
private boolean isTracking = false;
/**
* Is GPS enabled ?
*/
private boolean isGpsEnabled = false;
/**
* System notification id.
*/
private static final int NOTIFICATION_ID = 1;
/**
* Last known location
*/
private Location lastLocation;
/**
* Last number of satellites used in fix.
*/
private int lastNbSatellites;
/**
* LocationManager
*/
private LocationManager lmgr;
/**
* Current Track ID
*/
private long currentTrackId = -1;
/**
* the timestamp of the last GPS fix we used
*/
private long lastGPSTimestamp = 0;
/**
* the interval (in ms) to log GPS fixes defined in the preferences
*/
private long gpsLoggingInterval;
/**
* sensors for magnetic orientation
*/
private SensorListener sensorListener = new SensorListener();
/**
* Receives Intent for way point tracking, and stop/start logging.
*/
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.v(TAG, "Received intent " + intent.getAction());
if (OSMTracker.INTENT_TRACK_WP.equals(intent.getAction())) {
// Track a way point
Bundle extras = intent.getExtras();
if (extras != null) {
// because of the gps logging interval our last fix could be very old
// so we'll request the last known location from the gps provider
lastLocation = lmgr.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if(lastLocation != null){
Long trackId = extras.getLong(Schema.COL_TRACK_ID);
String uuid = extras.getString(OSMTracker.INTENT_KEY_UUID);
String name = extras.getString(OSMTracker.INTENT_KEY_NAME);
String link = extras.getString(OSMTracker.INTENT_KEY_LINK);
dataHelper.wayPoint(trackId, lastLocation, lastNbSatellites, name, link, uuid, sensorListener.getAzimuth(), sensorListener.getAccuracy());
}
}
} else if (OSMTracker.INTENT_UPDATE_WP.equals(intent.getAction())) {
// Update an existing waypoint
Bundle extras = intent.getExtras();
if (extras != null) {
Long trackId = extras.getLong(Schema.COL_TRACK_ID);
String uuid = extras.getString(OSMTracker.INTENT_KEY_UUID);
String name = extras.getString(OSMTracker.INTENT_KEY_NAME);
String link = extras.getString(OSMTracker.INTENT_KEY_LINK);
dataHelper.updateWayPoint(trackId, uuid, name, link);
}
} else if (OSMTracker.INTENT_DELETE_WP.equals(intent.getAction())) {
// Delete an existing waypoint
Bundle extras = intent.getExtras();
if (extras != null) {
String uuid = extras.getString(OSMTracker.INTENT_KEY_UUID);
dataHelper.deleteWayPoint(uuid);
}
} else if (OSMTracker.INTENT_START_TRACKING.equals(intent.getAction()) ) {
Bundle extras = intent.getExtras();
if (extras != null) {
Long trackId = extras.getLong(Schema.COL_TRACK_ID);
startTracking(trackId);
}
} else if (OSMTracker.INTENT_STOP_TRACKING.equals(intent.getAction()) ) {
stopTrackingAndSave();
}
}
};
/**
* Binder for service interaction
*/
private final IBinder binder = new GPSLoggerBinder();
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG, "Service onBind()");
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.v(TAG, "Service onUnbind()");
// If we aren't currently tracking we can
// stop ourselves
if (! isTracking ) {
Log.v(TAG, "Service self-stopping");
stopSelf();
}
// We don't want onRebind() to be called, so return false.
return false;
}
/**
* Bind interface for service interaction
*/
public class GPSLoggerBinder extends Binder {
/**
* Called by the activity when binding.
* Returns itself.
* @return the GPS Logger service
*/
public GPSLogger getService() {
return GPSLogger.this;
}
}
@Override
public void onCreate() {
Log.v(TAG, "Service onCreate()");
dataHelper = new DataHelper(this);
//read the logging interval from preferences
gpsLoggingInterval = Long.parseLong(PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString(
OSMTracker.Preferences.KEY_GPS_LOGGING_INTERVAL, OSMTracker.Preferences.VAL_GPS_LOGGING_INTERVAL)) * 1000;
// Register our broadcast receiver
IntentFilter filter = new IntentFilter();
filter.addAction(OSMTracker.INTENT_TRACK_WP);
filter.addAction(OSMTracker.INTENT_UPDATE_WP);
filter.addAction(OSMTracker.INTENT_DELETE_WP);
filter.addAction(OSMTracker.INTENT_START_TRACKING);
filter.addAction(OSMTracker.INTENT_STOP_TRACKING);
registerReceiver(receiver, filter);
// Register ourselves for location updates
lmgr = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
lmgr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
//register for Orientation updates
sensorListener.register(this);
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "Service onStartCommand(-,"+flags+","+startId+")");
startForeground(NOTIFICATION_ID, getNotification());
return Service.START_STICKY;
}
@Override
public void onDestroy() {
Log.v(TAG, "Service onDestroy()");
if (isTracking) {
// If we're currently tracking, save user data.
stopTrackingAndSave();
}
// Unregister listener
lmgr.removeUpdates(this);
// Unregister broadcast receiver
unregisterReceiver(receiver);
// Cancel any existing notification
stopNotifyBackgroundService();
// stop sensors
sensorListener.unregister();
super.onDestroy();
}
/**
* Start GPS tracking.
*/
private void startTracking(long trackId) {
currentTrackId = trackId;
Log.v(TAG, "Starting track logging for track #" + trackId);
// Refresh notification with correct Track ID
NotificationManager nmgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nmgr.notify(NOTIFICATION_ID, getNotification());
isTracking = true;
}
/**
* Stops GPS Logging
*/
private void stopTrackingAndSave() {
isTracking = false;
dataHelper.stopTracking(currentTrackId);
currentTrackId = -1;
this.stopSelf();
}
@Override
public void onLocationChanged(Location location) {
// We're receiving location, so GPS is enabled
isGpsEnabled = true;
// first of all we check if the time from the last used fix to the current fix is greater than the logging interval
if((lastGPSTimestamp + gpsLoggingInterval) < System.currentTimeMillis()){
lastGPSTimestamp = System.currentTimeMillis(); // save the time of this fix
lastLocation = location;
lastNbSatellites = countSatellites();
if (isTracking) {
dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy());
}
}
}
/**
* Counts number of satellites used in last fix.
* @return The number of satellites
*/
private int countSatellites() {
int count = 0;
GpsStatus status = lmgr.getGpsStatus(null);
for(GpsSatellite sat:status.getSatellites()) {
if (sat.usedInFix()) {
count++;
}
}
return count;
}
/**
* Builds the notification to display when tracking in background.
*/
private Notification getNotification() {
Notification n = new Notification(R.drawable.ic_stat_track, getResources().getString(R.string.notification_ticker_text), System.currentTimeMillis());
Intent startTrackLogger = new Intent(this, TrackLogger.class);
startTrackLogger.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, currentTrackId);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, startTrackLogger, PendingIntent.FLAG_UPDATE_CURRENT);
n.flags = Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
n.setLatestEventInfo(
getApplicationContext(),
getResources().getString(R.string.notification_title).replace("{0}", (currentTrackId > -1) ? Long.toString(currentTrackId) : "?"),
getResources().getString(R.string.notification_text),
contentIntent);
return n;
}
/**
* Stops notifying the user that we're tracking in the background
*/
private void stopNotifyBackgroundService() {
NotificationManager nmgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nmgr.cancel(NOTIFICATION_ID);
}
@Override
public void onProviderDisabled(String provider) {
isGpsEnabled = false;
}
@Override
public void onProviderEnabled(String provider) {
isGpsEnabled = true;
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Not interested in provider status
}
/**
* Getter for gpsEnabled
* @return true if GPS is enabled, otherwise false.
*/
public boolean isGpsEnabled() {
return isGpsEnabled;
}
/**
* Setter for isTracking
* @return true if we're currently tracking, otherwise false.
*/
public boolean isTracking() {
return isTracking;
}
}