/*
* This file is part of GPSLogger for Android.
*
* GPSLogger for Android 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 2 of the License, or
* (at your option) any later version.
*
* GPSLogger for Android 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 GPSLogger for Android. If not, see <http://www.gnu.org/licenses/>.
*/
//TODO: Simplify email logic (too many methods)
//TODO: Allow messages in IActionListener callback methods
//TODO: Handle case where a fix is not found and GPS gives up - restart alarm somehow?
package com.mendhak.gpslogger;
import android.app.*;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import arida.ufc.br.moapgpstracker.R;
import com.mendhak.gpslogger.common.AppSettings;
import com.mendhak.gpslogger.common.IActionListener;
import com.mendhak.gpslogger.common.Session;
import com.mendhak.gpslogger.common.Utilities;
import com.mendhak.gpslogger.loggers.FileLoggerFactory;
import com.mendhak.gpslogger.loggers.ILogger;
import com.mendhak.gpslogger.senders.AlarmReceiver;
import com.mendhak.gpslogger.senders.FileSenderFactory;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class GpsLoggingService extends Service implements IActionListener
{
private static NotificationManager gpsNotifyManager;
private static int NOTIFICATION_ID = 8675309;
private final IBinder mBinder = new GpsLoggingBinder();
private static IGpsLoggerServiceClient mainServiceClient;
// ---------------------------------------------------
// Helpers and managers
// ---------------------------------------------------
private GeneralLocationListener gpsLocationListener;
private GeneralLocationListener towerLocationListener;
LocationManager gpsLocationManager;
private LocationManager towerLocationManager;
private Intent alarmIntent;
AlarmManager nextPointAlarmManager;
// ---------------------------------------------------
@Override
public IBinder onBind(Intent arg0)
{
Utilities.LogDebug("GpsLoggingService.onBind");
return mBinder;
}
@Override
public void onCreate()
{
Utilities.LogDebug("GpsLoggingService.onCreate");
nextPointAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Utilities.LogInfo("GPSLoggerService created");
}
@Override
public void onStart(Intent intent, int startId)
{
Utilities.LogDebug("GpsLoggingService.onStart");
HandleIntent(intent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Utilities.LogDebug("GpsLoggingService.onStartCommand");
HandleIntent(intent);
return START_REDELIVER_INTENT;
}
@Override
public void onDestroy()
{
Utilities.LogWarning("GpsLoggingService is being destroyed by Android OS.");
mainServiceClient = null;
super.onDestroy();
}
@Override
public void onLowMemory()
{
Utilities.LogWarning("Android is low on memory.");
super.onLowMemory();
}
private void HandleIntent(Intent intent)
{
Utilities.LogDebug("GpsLoggingService.handleIntent");
GetPreferences();
Utilities.LogDebug("Null intent? " + String.valueOf(intent == null));
if (intent != null)
{
Bundle bundle = intent.getExtras();
if (bundle != null)
{
boolean stopRightNow = bundle.getBoolean("immediatestop");
boolean startRightNow = bundle.getBoolean("immediate");
boolean sendEmailNow = bundle.getBoolean("emailAlarm");
boolean getNextPoint = bundle.getBoolean("getnextpoint");
Utilities.LogDebug("startRightNow - " + String.valueOf(startRightNow));
Utilities.LogDebug("emailAlarm - " + String.valueOf(sendEmailNow));
if (startRightNow)
{
Utilities.LogInfo("Auto starting logging");
StartLogging();
}
if (stopRightNow)
{
Utilities.LogInfo("Auto stop logging");
StopLogging();
}
if (sendEmailNow)
{
Utilities.LogDebug("setReadyToBeAutoSent = true");
Session.setReadyToBeAutoSent(true);
AutoSendLogFile();
}
if (getNextPoint && Session.isStarted())
{
Utilities.LogDebug("HandleIntent - getNextPoint");
StartGpsManager();
}
}
}
else
{
// A null intent is passed in if the service has been killed and
// restarted.
Utilities.LogDebug("Service restarted with null intent. Start logging.");
StartLogging();
}
}
// @Override
public void OnComplete()
{
Utilities.HideProgress();
}
// @Override
public void OnFailure()
{
Utilities.HideProgress();
}
/**
* Can be used from calling classes as the go-between for methods and
* properties.
*/
public class GpsLoggingBinder extends Binder
{
public GpsLoggingService getService()
{
Utilities.LogDebug("GpsLoggingBinder.getService");
return GpsLoggingService.this;
}
}
/**
* Sets up the auto email timers based on user preferences.
*/
public void SetupAutoSendTimers()
{
Utilities.LogDebug("GpsLoggingService.SetupAutoSendTimers");
Utilities.LogDebug("isAutoSendEnabled - " + String.valueOf(AppSettings.isAutoSendEnabled()));
Utilities.LogDebug("Session.getAutoSendDelay - " + String.valueOf(Session.getAutoSendDelay()));
if (AppSettings.isAutoSendEnabled() && Session.getAutoSendDelay() > 0)
{
Utilities.LogDebug("Setting up autosend alarm");
long triggerTime = System.currentTimeMillis()
+ (long) (Session.getAutoSendDelay() * 60 * 60 * 1000);
alarmIntent = new Intent(getApplicationContext(), AlarmReceiver.class);
CancelAlarm();
Utilities.LogDebug("New alarm intent");
PendingIntent sender = PendingIntent.getBroadcast(this, 0, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, triggerTime, sender);
Utilities.LogDebug("Alarm has been set");
}
else
{
Utilities.LogDebug("Checking if alarmIntent is null");
if (alarmIntent != null)
{
Utilities.LogDebug("alarmIntent was null, canceling alarm");
CancelAlarm();
}
}
}
private void CancelAlarm()
{
Utilities.LogDebug("GpsLoggingService.CancelAlarm");
if (alarmIntent != null)
{
Utilities.LogDebug("GpsLoggingService.CancelAlarm");
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Utilities.LogDebug("Pending alarm intent was null? " + String.valueOf(sender == null));
am.cancel(sender);
}
}
/**
* Method to be called if user has chosen to auto email log files when he
* stops logging
*/
private void AutoSendLogFileOnStop()
{
Utilities.LogDebug("GpsLoggingService.AutoSendLogFileOnStop");
Utilities.LogVerbose("isAutoSendEnabled - " + AppSettings.isAutoSendEnabled());
// autoSendDelay 0 means send it when you stop logging.
if (AppSettings.isAutoSendEnabled() && Session.getAutoSendDelay() == 0)
{
Session.setReadyToBeAutoSent(true);
AutoSendLogFile();
}
}
/**
* Calls the Auto Email Helper which processes the file and sends it.
*/
private void AutoSendLogFile()
{
Utilities.LogDebug("GpsLoggingService.AutoSendLogFile");
Utilities.LogVerbose("isReadyToBeAutoSent - " + Session.isReadyToBeAutoSent());
// Check that auto emailing is enabled, there's a valid location and
// file name.
if (Session.getCurrentFileName() != null && Session.getCurrentFileName().length() > 0
&& Session.isReadyToBeAutoSent() && Session.hasValidLocation())
{
//Don't show a progress bar when auto-emailing
Utilities.LogInfo("Emailing Log File");
FileSenderFactory.SendFiles(getApplicationContext(), this);
Session.setReadyToBeAutoSent(true);
SetupAutoSendTimers();
}
}
protected void ForceEmailLogFile()
{
Utilities.LogDebug("GpsLoggingService.ForceEmailLogFile");
if (AppSettings.isAutoSendEnabled() && Session.getCurrentFileName() != null && Session.getCurrentFileName().length() > 0)
{
if (IsMainFormVisible())
{
Utilities.ShowProgress(mainServiceClient.GetActivity(), getString(R.string.autosend_sending),
getString(R.string.please_wait));
}
Utilities.LogInfo("Force emailing Log File");
FileSenderFactory.SendFiles(getApplicationContext(), this);
}
}
/**
* Sets the activity form for this service. The activity form needs to
* implement IGpsLoggerServiceClient.
*
* @param mainForm The calling client
*/
protected static void SetServiceClient(IGpsLoggerServiceClient mainForm)
{
mainServiceClient = mainForm;
}
/**
* Gets preferences chosen by the user and populates the AppSettings object.
* Also sets up email timers if required.
*/
private void GetPreferences()
{
Utilities.LogDebug("GpsLoggingService.GetPreferences");
Utilities.PopulateAppSettings(getApplicationContext());
Utilities.LogDebug("Session.getAutoSendDelay: " + Session.getAutoSendDelay());
Utilities.LogDebug("AppSettings.getAutoSendDelay: " + AppSettings.getAutoSendDelay());
if (Session.getAutoSendDelay() != AppSettings.getAutoSendDelay())
{
Utilities.LogDebug("Old autoSendDelay - " + String.valueOf(Session.getAutoSendDelay())
+ "; New -" + String.valueOf(AppSettings.getAutoSendDelay()));
Session.setAutoSendDelay(AppSettings.getAutoSendDelay());
SetupAutoSendTimers();
}
}
/**
* Resets the form, resets file name if required, reobtains preferences
*/
protected void StartLogging()
{
Utilities.LogDebug("GpsLoggingService.StartLogging");
Session.setAddNewTrackSegment(true);
if (Session.isStarted())
{
return;
}
Utilities.LogInfo("Starting logging procedures");
try
{
startForeground(NOTIFICATION_ID, new Notification());
}
catch (Exception ex)
{
System.out.print(ex.getMessage());
}
Session.setStarted(true);
GetPreferences();
Notify();
ResetCurrentFileName(true);
ClearForm();
StartGpsManager();
}
/**
* Asks the main service client to clear its form.
*/
private void ClearForm()
{
if (IsMainFormVisible())
{
mainServiceClient.ClearForm();
}
}
/**
* Stops logging, removes notification, stops GPS manager, stops email timer
*/
public void StopLogging()
{
Utilities.LogDebug("GpsLoggingService.StopLogging");
Session.setAddNewTrackSegment(true);
Utilities.LogInfo("Stopping logging");
Session.setStarted(false);
// Email log file before setting location info to null
AutoSendLogFileOnStop();
CancelAlarm();
Session.setCurrentLocationInfo(null);
Session.setLocationHistory(null);
stopForeground(true);
RemoveNotification();
StopAlarm();
StopGpsManager();
StopMainActivity();
}
/**
* Manages the notification in the status bar
*/
private void Notify()
{
Utilities.LogDebug("GpsLoggingService.Notify");
if (AppSettings.shouldShowInNotificationBar())
{
gpsNotifyManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
ShowNotification();
}
else
{
RemoveNotification();
}
}
/**
* Hides the notification icon in the status bar if it's visible.
*/
private void RemoveNotification()
{
Utilities.LogDebug("GpsLoggingService.RemoveNotification");
try
{
if (Session.isNotificationVisible())
{
gpsNotifyManager.cancelAll();
}
}
catch (Exception ex)
{
Utilities.LogError("RemoveNotification", ex);
}
finally
{
Session.setNotificationVisible(false);
}
}
/**
* Shows a notification icon in the status bar for GPS Logger
*/
private void ShowNotification()
{
Utilities.LogDebug("GpsLoggingService.ShowNotification");
// What happens when the notification item is clicked
Intent contentIntent = new Intent(this, GpsMainActivity.class);
PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, contentIntent,
android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
Notification nfc = new Notification(R.drawable.ic_moap_gps_tracker, null, System.currentTimeMillis());
nfc.flags |= Notification.FLAG_ONGOING_EVENT;
NumberFormat nf = new DecimalFormat("###.######");
String contentText = getString(R.string.moapgpstracker_still_running);
if (Session.hasValidLocation())
{
contentText = nf.format(Session.getCurrentLatitude()) + ","
+ nf.format(Session.getCurrentLongitude());
}
nfc.setLatestEventInfo(getApplicationContext(), getString(R.string.moapgpstracker_still_running),
contentText, pending);
gpsNotifyManager.notify(NOTIFICATION_ID, nfc);
Session.setNotificationVisible(true);
}
/**
* Starts the location manager. There are two location managers - GPS and
* Cell Tower. This code determines which manager to request updates from
* based on user preference and whichever is enabled. If GPS is enabled on
* the phone, that is used. But if the user has also specified that they
* prefer cell towers, then cell towers are used. If neither is enabled,
* then nothing is requested.
*/
private void StartGpsManager()
{
Utilities.LogDebug("GpsLoggingService.StartGpsManager");
GetPreferences();
if (gpsLocationListener == null)
{
gpsLocationListener = new GeneralLocationListener(this);
}
if (towerLocationListener == null)
{
towerLocationListener = new GeneralLocationListener(this);
}
gpsLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
towerLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
CheckTowerAndGpsStatus();
if (Session.isGpsEnabled() && !AppSettings.shouldPreferCellTower())
{
Utilities.LogInfo("Requesting GPS location updates");
// gps satellite based
gpsLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
1000, 0,
gpsLocationListener);
gpsLocationManager.addGpsStatusListener(gpsLocationListener);
Session.setUsingGps(true);
}
else if (Session.isTowerEnabled())
{
Utilities.LogInfo("Requesting tower location updates");
Session.setUsingGps(false);
// Cell tower and wifi based
towerLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
1000, 0,
towerLocationListener);
}
else
{
Utilities.LogInfo("No provider available");
Session.setUsingGps(false);
SetStatus(R.string.gpsprovider_unavailable);
SetFatalMessage(R.string.gpsprovider_unavailable);
StopLogging();
return;
}
SetStatus(R.string.started);
}
/**
* This method is called periodically to determine whether the cell tower /
* gps providers have been enabled, and sets class level variables to those
* values.
*/
private void CheckTowerAndGpsStatus()
{
Session.setTowerEnabled(towerLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER));
Session.setGpsEnabled(gpsLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER));
}
/**
* Stops the location managers
*/
private void StopGpsManager()
{
Utilities.LogDebug("GpsLoggingService.StopGpsManager");
if (towerLocationListener != null)
{
Utilities.LogDebug("Removing towerLocationManager updates");
towerLocationManager.removeUpdates(towerLocationListener);
}
if (gpsLocationListener != null)
{
Utilities.LogDebug("Removing gpsLocationManager updates");
gpsLocationManager.removeUpdates(gpsLocationListener);
gpsLocationManager.removeGpsStatusListener(gpsLocationListener);
}
SetStatus(getString(R.string.stopped));
}
/**
* Sets the current file name based on user preference.
*/
private void ResetCurrentFileName(boolean newStart)
{
Utilities.LogDebug("GpsLoggingService.ResetCurrentFileName");
String newFileName = Session.getUserName()+Session.getCurrentFileName();
if (AppSettings.shouldCreateNewFileOnceADay())
{
// 20100114.gpx
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
newFileName = sdf.format(new Date());
Session.setCurrentFileName(Session.getUserName()+newFileName);
}
else if (newStart)
{
// 20100114183329.gpx
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
newFileName = sdf.format(new Date());
Session.setCurrentFileName(Session.getUserName()+newFileName);
}
if (IsMainFormVisible())
{
mainServiceClient.onFileName(newFileName);
}
}
/**
* Gives a status message to the main service client to display
*
* @param status The status message
*/
void SetStatus(String status)
{
if (IsMainFormVisible())
{
mainServiceClient.OnStatusMessage(status);
}
}
/**
* Gives an error message to the main service client to display
*
* @param messageId ID of string to lookup
*/
void SetFatalMessage(int messageId)
{
if (IsMainFormVisible())
{
mainServiceClient.OnFatalMessage(getString(messageId));
}
}
/**
* Gets string from given resource ID, passes to SetStatus(String)
*
* @param stringId ID of string to lookup
*/
private void SetStatus(int stringId)
{
String s = getString(stringId);
SetStatus(s);
}
/**
* Notifies main form that logging has stopped
*/
void StopMainActivity()
{
if (IsMainFormVisible())
{
mainServiceClient.OnStopLogging();
}
}
/**
* Stops location manager, then starts it.
*/
void RestartGpsManagers()
{
Utilities.LogDebug("GpsLoggingService.RestartGpsManagers");
StopGpsManager();
StartGpsManager();
}
/**
* This event is raised when the GeneralLocationListener has a new location.
* This method in turn updates notification, writes to file, reobtains
* preferences, notifies main service client and resets location managers.
*
* @param loc Location object
*/
void OnLocationChanged(Location loc)
{
if (!Session.isStarted())
{
Utilities.LogDebug("OnLocationChanged called, but Session.isStarted is false");
StopLogging();
return;
}
Utilities.LogDebug("GpsLoggingService.OnLocationChanged");
long currentTimeStamp = System.currentTimeMillis();
// Wait some time even on 0 frequency so that the UI doesn't lock up
if ((currentTimeStamp - Session.getLatestTimeStamp()) < 1000)
{
return;
}
// Don't do anything until the user-defined time has elapsed
if ((currentTimeStamp - Session.getLatestTimeStamp()) < (AppSettings.getMinimumSeconds() * 1000))
{
return;
}
//Don't do anything until the user-defined distance has been traversed
if (AppSettings.getMinimumDistanceInMeters() > 0 && Session.hasValidLocation())
{
double distanceTraveled = Utilities.CalculateDistance(loc.getLatitude(), loc.getLongitude(),
Session.getCurrentLatitude(), Session.getCurrentLongitude());
if (AppSettings.getMinimumDistanceInMeters() > distanceTraveled)
{
SetStatus("Only " + String.valueOf(Math.floor(distanceTraveled)) + " m traveled.");
StopManagerAndResetAlarm();
return;
}
}
Utilities.LogInfo("New location obtained");
ResetCurrentFileName(false);
Session.setLatestTimeStamp(System.currentTimeMillis());
Session.setCurrentLocationInfo(loc);
if(Session.getLocationHistory() == null){
Session.setLocationHistory(new ArrayList<Location>());
}
Session.getLocationHistory().add(loc);
SetDistanceTraveled(loc);
Notify();
WriteToFile(loc);
GetPreferences();
StopManagerAndResetAlarm();
if (IsMainFormVisible())
{
mainServiceClient.OnLocationUpdate(loc);
}
}
private void SetDistanceTraveled(Location loc)
{
// Distance
if (Session.getPreviousLocationInfo() == null)
{
Session.setPreviousLocationInfo(loc);
}
// Calculate this location and the previous location location and add to the current running total distance.
// NOTE: Should be used in conjunction with 'distance required before logging' for more realistic values.
double distance = Utilities.CalculateDistance(
Session.getPreviousLatitude(),
Session.getPreviousLongitude(),
loc.getLatitude(),
loc.getLongitude());
Session.setPreviousLocationInfo(loc);
Session.setTotalTravelled(Session.getTotalTravelled() + distance);
}
protected void StopManagerAndResetAlarm()
{
Utilities.LogDebug("GpsLoggingService.StopManagerAndResetAlarm");
StopGpsManager();
SetAlarmForNextPoint();
}
private void StopAlarm()
{
Utilities.LogDebug("GpsLoggingService.StopAlarm");
Intent i = new Intent(this, GpsLoggingService.class);
i.putExtra("getnextpoint", true);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
nextPointAlarmManager.cancel(pi);
}
private void SetAlarmForNextPoint()
{
Utilities.LogDebug("GpsLoggingService.SetAlarmForNextPoint");
Intent i = new Intent(this, GpsLoggingService.class);
i.putExtra("getnextpoint", true);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
nextPointAlarmManager.cancel(pi);
nextPointAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + AppSettings.getMinimumSeconds() * 1000, pi);
}
/**
* Calls file helper to write a given location to a file.
*
* @param loc Location object
*/
private void WriteToFile(Location loc)
{
Utilities.LogDebug("GpsLoggingService.WriteToFile");
List<ILogger> loggers = FileLoggerFactory.GetFileLoggers();
Session.setAddNewTrackSegment(false);
for (ILogger logger : loggers)
{
try
{
logger.write(loc);
Session.setAllowDescription(true);
Session.clearAchievedAnnotations();
}
catch (Exception e)
{
SetStatus(R.string.could_not_write_to_file);
}
}
}
/**
* Informs the main service client of the number of visible satellites.
*
* @param count Number of Satellites
*/
void SetSatelliteInfo(int count)
{
if (IsMainFormVisible())
{
mainServiceClient.OnSatelliteCount(count);
}
}
private boolean IsMainFormVisible()
{
return mainServiceClient != null;
}
}