package de.fuberlin.osm2.trackrecorder;
import java.util.LinkedList;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import de.fuberlin.osm2.R;
import de.fuberlin.osm2.data.DataBaseConnector;
import de.fuberlin.osm2.data.DataBaseConnectorImpl;
import de.fuberlin.osm2.gui.activity.ViewActivity;
import de.fuberlin.osm2.gui.view.ServiceLocationListener;
import de.fuberlin.osm2.log.Constants;
import de.fuberlin.osm2.settings.Settings;
/**
* TrackRecorderService is a component between GUI and database. It implements
* LocationListener to obtain new GPS fixes.
*
* @author micha & simon
*/
public class TrackRecorderService extends Service implements LocationListener {
/**
* Bundle Wrapper stores pointId of a point and its attributes in a bundle.
*
* @author micha & simon
*
*/
private static class BundleWrapper {
protected long pointId;
protected Bundle bundle;
/**
*
* @param pId pointId of the point
* @param bndl attributes of the point
*/
protected BundleWrapper(long pId, Bundle bndl) {
pointId = pId;
bundle = bndl;
}
}
// static variables
private static boolean serviceIsRunning;
// reference to the singleton TrackRecorderService
private static TrackRecorderService service;
private static Settings settings;
private static SharedPreferences sharedPreferences;
private static final int NOTIFICATION_ID = 1;
private static final int MILLIS_TO_SEC = 1000;
/**
* Starts TrackRecorderService if necessary and returns the reference to the
* singleton. NOTE: reference is null, if TrackRecorderService is not yet
* running.
*
* @param context parent context
* @return reference to the singleton
*/
public static TrackRecorderService getService(Context context) {
if (!serviceIsRunning) {
Log.d(Constants.TAG, "TrackRecorderService getService: service is null");
startService(context);
}
return service;
}
/**
* Starts TrackRecorderService if necessary.
*
* @param context parent context
*/
public static void startService(Context context) {
if (settings == null) {
settings = new Settings(context);
}
if (sharedPreferences == null) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
}
if (!serviceIsRunning) {
context.startService(new Intent(context, TrackRecorderService.class));
}
}
/**
* Stops TrackRecorderService.
*
* @param context context of caller
*/
public static void stopService(Context context) {
if (service != null) {
Log.d(Constants.TAG, "stopService");
context.stopService(new Intent(context, TrackRecorderService.class));
service = null;
}
}
private OnSharedPreferenceChangeListener settingsListener;
private String ns = Context.NOTIFICATION_SERVICE;
private NotificationManager notificationManager;
private ServiceLocationListener locationListener;
private boolean notifyListener;
private LocationManager locationManager;
/** for database access. */
DataBaseConnector db;
private float minDistance = 0.5F; // in meters
private boolean gpsEnabled;
private boolean isTracking;
private boolean isPaused;
private Location currentLocation;
private long currentLocationId;
private long trackId;
private boolean trackingWay;
private long wayId;
private boolean trackingArea;
private long areaId;
// it shall be possible to add POIs right at the beginning,
// when no location is available yet.
private String currentTrackName;
private LinkedList<Bundle> poiQueue = new LinkedList<Bundle>();
/**
* Stops the current tracked area.
*
* @param tags identifies a area, if we allow to track more then one area at
* one time (not yet used)
*
*/
public void closeArea(Bundle tags) {
if (trackingArea) {
Log.d(Constants.TAG, "TRC: closeArea");
if (notifyListener)
locationListener.onStopArea();
trackingArea = false;
}
}
/**
* Gives the name of the current track.
*
* @return name of the current track
*/
public String getCurrentTrackName() {
return currentTrackName;
}
/**
* returns the id of the current track.
*
* @return trackId
*/
public long getCurrentTrackId() {
return trackId;
}
/**
* returns the id of the current point.
*
* @return pointId
*/
public long getCurrentPointId() {
return currentLocationId;
}
/**
* checks whether the gps provider is enabled.
*
* @return true if gps provider is enabled
*/
public boolean isGpsEnabled() {
gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
return gpsEnabled;
}
/**
* We don't use the Android binder mechanism, so return null.
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
private static void setService(TrackRecorderService service) {
TrackRecorderService.service = service;
}
private static void setServiceAsRunning() {
TrackRecorderService.serviceIsRunning = true;
}
private static void setServiceAsStopped() {
TrackRecorderService.serviceIsRunning = false;
}
/**
* Registers SettingsListener and LocationUpdates.
*/
@Override
public void onCreate() {
super.onCreate();
setService(this);
setServiceAsRunning();
// get gps LocationManager
locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
isGpsEnabled();
db = new DataBaseConnectorImpl(this);
registerSettingsListener();
}
/**
* Unregisters SettingsListener and LocationUpdates.
*/
@Override
public void onDestroy() {
setServiceAsStopped();
setService(null);
unregisterSettingsListener();
db.close();
// if (serviceDemand) {
// Service is crashed
// }
Log.d(Constants.TAG, "Service: onDestroyed");
if (!isPaused) {
locationManager.removeUpdates(this);
notificationManager.cancelAll();
}
}
/**
* Receives a new gps-fix and delegates it to the db-wrapper. Adds the
* gps-fix to ways or areas when they are tracked actually.
*/
@Override
public void onLocationChanged(Location location) {
Log.d(Constants.TAG,
"new gps fix lat->" + location.getLatitude() + " lng->"
+ location.getLongitude());
isTracking = true;
if (!isPaused) {
currentLocation = location;
currentLocationId = db.addPoint(location.getLongitude(), location.getLatitude(),
System.currentTimeMillis(), trackId);
if (notifyListener) {
locationListener.onLocationUpdate(currentLocation.getLongitude(),
currentLocation.getLatitude());
}
if (trackingWay) {
final AsyncTask<Long, Integer, Long> asyncTask = new AsyncTask<Long, Integer, Long>() {
@Override
protected Long doInBackground(Long... ids) {
db.addPointToWay(ids[0].longValue(), ids[1].longValue());
Log.d(Constants.TAG, "TRC: added point to way");
return null;
}
};
asyncTask.execute(Long.valueOf(currentLocationId), Long.valueOf(wayId));
}
if (trackingArea) {
final AsyncTask<Long, Integer, Long> asyncTask = new AsyncTask<Long, Integer, Long>() {
@Override
protected Long doInBackground(final Long... ids) {
db.addPointToWay(ids[0].longValue(), ids[1].longValue());
Log.d(Constants.TAG, "TRC: added point to area");
return null;
}
};
asyncTask.execute(Long.valueOf(currentLocationId), Long.valueOf(areaId));
}
if (!poiQueue.isEmpty()) {
Log.d(Constants.TAG, "TRC: Added Poi from Queue");
for (Bundle b : poiQueue) {
onPOI(b);
}
poiQueue.clear();
}
}
}
/**
* Adds a new POI with the given attributes.
*
* @param tags contains the attributes of the POI
*
*/
public void onPOI(Bundle tags) {
if (null != currentLocation) {
final AsyncTask<BundleWrapper, Integer, Long> asyncTask = new AsyncTask<BundleWrapper, Integer, Long>() {
@Override
protected Long doInBackground(BundleWrapper... bundleWrp) {
Log.d(Constants.TAG, "TRC: onPoi currentLocationId->"
+ bundleWrp[0].pointId + " tags ->" + bundleWrp[0].bundle);
db.addPoi(bundleWrp[0].pointId, bundleWrp[0].bundle);
return null;
}
};
asyncTask.execute(new BundleWrapper(currentLocationId, tags));
// TODO: tag name
String tag = tags.containsKey("KEY") ? tags.getString("KEY") : "";
if (notifyListener) {
locationListener.onPOI(currentLocation.getLongitude(),
currentLocation.getLatitude(), tag);
}
}
else {
Log.d(Constants.TAG, "TRC: Adding Poi to Queue");
poiQueue.add(tags);
}
}
/**
* Is called when the user disables the gps provider.
*/
@Override
public void onProviderDisabled(String provider) {
gpsEnabled = false;
Log.d(Constants.TAG, "gps provider disabled");
}
/**
* Is called when the user enables the gps provider.
*/
@Override
public void onProviderEnabled(String provider) {
gpsEnabled = true;
Log.d(Constants.TAG, "gps provider enabled");
}
/**
* Starts the service sticky, so it is running until it is explicitly
* stopped.
*/
// @Override
// public int onStartCommand(Intent intent, int flags, int startId) {
// Log.d(Constants.TAG, "Received start id " + startId + ": " + intent);
// // We want this service to continue running until it is explicitly
// // stopped, so return sticky.
// return START_STICKY;
// }
/**
* Start the service. Called by the system.
*/
@Override
public void onStart(Intent intent, int startId) {
Log.d(Constants.TAG, "Received start id " + startId + ": " + intent);
}
/**
* Pauses the current track. The LocationManager is still running to avoid a
* gps-fix delay.
*/
public void pauseTrack() {
if (!isPaused) {
isTracking = false;
unregisterLocationListener();
showNotificationPaused();
isPaused = true;
}
}
/**
* Registers gps LocationLister to obtain gps-fixes.
*/
private void registerLocationListener() {
// DEBUG
// locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
// settings.gpsInterval() * MILLIS_TO_SEC, minDistance, this);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
settings.gpsInterval() * MILLIS_TO_SEC, minDistance, this);
}
/**
* Enables notifications for the locationListener. The locationListener will
* be informed if the location changes or new points are added.
*
* @param locListener which wants to be informed about location changes
*/
public void registerLocationUpdates(ServiceLocationListener locListener) {
Log.d(Constants.TAG, "TRS: registerLocationUpdates ");
this.locationListener = locListener;
notifyListener = true;
}
/**
* Registers SettingsListener to obtain setting changes i.e gps interval.
*/
private void registerSettingsListener() {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
settingsListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences pref, String arg) {
Log.d(Constants.TAG, "setting changed");
if ((TrackRecorderService.this).getResources()
.getString(R.string.settings_key_gps_interval).equals(arg)) {
Log.d(Constants.TAG, "gps interval changed");
// pause and resume Track to change gps interval of the
// location locationListener
TrackRecorderService.this.restartTracking();
} else if ((TrackRecorderService.this).getResources()
.getString(R.string.settings_key_osm_layer).equals(arg)) {
Log.d(Constants.TAG, "showOSMlayer changed");
}
}
};
sharedPreferences.registerOnSharedPreferenceChangeListener(settingsListener);
}
/**
* Pauses and resumes tracking to update preferences of the LocationListener
* i.e. gps interval
*/
void restartTracking() {
if (!isPaused) {
pauseTrack();
resumeTrack();
}
}
/**
* Resumes the paused track.
*/
public void resumeTrack() {
if (isPaused) {
registerLocationListener();
showNotificationRunning();
isPaused = false;
}
}
/**
* Shows pause notification icon in statusbar.
*/
private void showNotificationPaused() {
// TODO: modify notification paused
// Instantiate the Notification:
int icon = R.drawable.ic_stat_notify_paused;
CharSequence tickerText = this.getString(R.string.service_notification_paused);
long when = System.currentTimeMillis();
Notification notification = new Notification(icon, tickerText, when);
// Define the Notification's expanded message and Intent:
Context context = getApplicationContext();
CharSequence contentTitle = this.getString(R.string.app_name);
CharSequence contentText = this.getString(R.string.service_notification_paused);
Intent notificationIntent = new Intent(this, ViewActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
// Pass the Notification to the NotificationManager:
notificationManager.notify(NOTIFICATION_ID, notification);
}
/**
* Shows running-notification icon in statusbar.
*/
private void showNotificationRunning() {
// Instantiate the Notification:
int icon = R.drawable.ic_stat_notify_running;
CharSequence tickerText = this.getString(R.string.service_notification_running);
long when = System.currentTimeMillis();
Notification notification = new Notification(icon, tickerText, when);
// Define the Notification's expanded message and Intent:
Context context = getApplicationContext();
CharSequence contentTitle = this.getString(R.string.app_name);
CharSequence contentText = this.getString(R.string.service_notification_running);
Intent notificationIntent = new Intent(this, ViewActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
// Pass the Notification to the NotificationManager:
notificationManager.notify(NOTIFICATION_ID, notification);
}
/**
* Starts a new area with the given attributes.
*
* @param tags contains the attributes of the area
*
*/
public void startArea(Bundle tags) {
if (!trackingArea) {
Log.d(Constants.TAG, "TRC: startArea");
areaId = db.startWay(tags, true);
if (notifyListener) {
locationListener.onStartArea();
}
trackingArea = true;
}
}
/**
* Starts a new track. The LocationManger is started immediately to obtain
* gps fixes.
*
* @param bundle contains the name of the new track ( key:"TRACK_NAME")
*/
public void startTrack(Bundle bundle) {
Log.d(Constants.TAG, "TRC: startTrack -> " + bundle);
registerLocationListener();
Log.d(Constants.TAG, "TRS: gpsEnabled->" + gpsEnabled);
currentTrackName = bundle.getString("TRACK_NAME");
trackId = db.addTrack(currentTrackName, System.currentTimeMillis());
// Get a reference to the NotificationManager:
notificationManager = (NotificationManager) getSystemService(ns);
showNotificationRunning();
}
/**
* Starts a new way with the given attributes.
*
* @param tags contains the attributes of the way
*
*/
public void startWay(Bundle tags) {
if (!trackingWay) {
Log.d(Constants.TAG, "TRC: startWay");
wayId = db.startWay(tags, false);
if (notifyListener) {
locationListener.onStartWay();
}
trackingWay = true;
}
}
/**
* Stops current track and the LocationManager.
*/
public void stopTrack() {
unregisterLocationListener();
isTracking = false;
notificationManager.cancelAll();
}
/**
* Stops the current tracked way.
*
* @param tags identifies a way, if we allow to track more then one way at
* one time (not yet used)
*
*/
public void stopWay(Bundle tags) {
if (trackingWay) {
Log.d(Constants.TAG, "TRC: stopWay");
if (notifyListener) {
locationListener.onStopWay();
}
trackingWay = false;
}
}
/**
* Unregisters gps LocationListener, stops obtaining gps-fixes.
*/
private void unregisterLocationListener() {
locationManager.removeUpdates(this);
}
/**
* Disables ServiceLocationListener's notifications.
*/
public void unregisterLocationUpdates() {
notifyListener = false;
this.locationListener = null;
}
/**
* Unregisters SettingsListener, stops obtaining setting changes.
*/
private void unregisterSettingsListener() {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(settingsListener);
}
/**
* informs about the the state of tracking.
*
* @return true if the service has a actual gps fix
*/
public boolean isTracking() {
return isTracking;
}
@Override
public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
// TODO Auto-generated method stub
}
}
|