/*
* Geopaparazzi - Digital field mapping on Android based devices
* Copyright (C) 2010 HydroloGIS (www.hydrologis.com)
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.geopaparazzi.library.gps;
import java.util.ArrayList;
import java.util.List;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.GpsStatus;
import android.location.GpsStatus.Listener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import eu.geopaparazzi.library.R;
import eu.geopaparazzi.library.database.GPLog;
import eu.geopaparazzi.library.util.LibraryConstants;
import eu.geopaparazzi.library.util.PositionUtilities;
import eu.geopaparazzi.library.util.activities.ProximityIntentReceiver;
import eu.geopaparazzi.library.util.debug.Debug;
import eu.geopaparazzi.library.util.debug.TestMock;
/**
* Singleton that takes care of gps matters.
*
* @author Andrea Antonello (www.hydrologis.com)
*/
@SuppressWarnings("nls")
public class GpsManager implements LocationListener, Listener {
/**
* GPS time interval.
*/
public static int WAITSECONDS = 1;
private static GpsManager gpsManager;
private GpsStatus mStatus;
private List<GpsManagerListener> listeners = new ArrayList<GpsManagerListener>();
/**
* The object responsible to log traces into the database.
*/
private static GpsDatabaseLogger gpsLogger;
/**
* The last taken gps location.
*/
private GpsLocation lastGpsLocation = null;
/**
* The previous gps location or null if no gps location was taken yet.
*
* <p>This changes with every {@link #onLocationChanged(Location)}.</p>
*/
private Location previousLoc = null;
private LocationManager locationManager;
private boolean gpsStarted = false;
private SharedPreferences preferences;
private boolean useNetworkPositions;
private boolean isMockMode;
private long lastLocationupdateMillis;
private boolean gotFix;
private GpsManager( Context context ) {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
preferences = PreferenceManager.getDefaultSharedPreferences(context);
useNetworkPositions = preferences.getBoolean(LibraryConstants.PREFS_KEY_GPS_USE_NETWORK_POSITION, false);
isMockMode = preferences.getBoolean(LibraryConstants.PREFS_KEY_MOCKMODE, false);
}
public synchronized static GpsManager getInstance( Context context ) {
if (gpsManager == null) {
gpsManager = new GpsManager(context);
gpsManager.checkLoggerExists(context);
gpsManager.checkGps(context);
gpsManager.gpsStart();
log("STARTED LISTENING");
}
// woke up from death and has the manager already but isn't listening any more
if (!gpsManager.isGpsListening()) {
gpsManager = new GpsManager(context);
gpsManager.checkLoggerExists(context);
gpsManager.checkGps(context);
gpsManager.gpsStart();
log("STARTED LISTENING AFTER REVIEW");
}
return gpsManager;
}
/**
* Add a listener to gps.
*
* @param listener the listener to add.
*/
public void addListener( GpsManagerListener listener ) {
synchronized (listeners) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
}
/**
* Remove a listener to gps.
*
* @param listener the listener to remove.
*/
public void removeListener( GpsManagerListener listener ) {
synchronized (listeners) {
listeners.remove(listener);
}
}
public LocationManager getLocationManager() {
return locationManager;
}
public void addProximityAlert( Context context, double lat, double lon, float radius ) {
String PROX_ALERT_INTENT = "com.javacodegeeks.android.lbs.ProximityAlert";
Intent intent = new Intent(PROX_ALERT_INTENT);
PendingIntent proximityIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
locationManager.addProximityAlert(lat, lon, radius, -1, proximityIntent);
IntentFilter filter = new IntentFilter(PROX_ALERT_INTENT);
context.registerReceiver(new ProximityIntentReceiver(), filter);
}
/**
* Disposes the GpsManager and with it all connected services.
*/
public void dispose( Context context ) {
if (isDatabaseLogging()) {
stopDatabaseLogging(context);
}
if (locationManager != null && gpsStarted) {
locationManager.removeUpdates(gpsManager);
locationManager.removeGpsStatusListener(gpsManager);
for( GpsManagerListener activity : listeners ) {
activity.gpsStop();
}
}
if (TestMock.isOn) {
TestMock.stopMocking(locationManager);
}
gpsStarted = false;
log("GpsManager disposed.");
}
public boolean hasLoggerShutdown() {
return gpsLogger.isShutdown();
}
/**
* Starts listening to the gps provider.
*/
private void gpsStart() {
if (Debug.doMock || isMockMode) {
log("Gps started using Mock locations");
TestMock.startMocking(locationManager, gpsManager);
} else {
log("Gps started.");
float minDistance = 0.2f;
long waitForSecs = WAITSECONDS;
// boolean doAtAndroidLevel = false;//
// preferences.getBoolean(PREFS_KEY_GPSDOATANDROIDLEVEL,
// // true);
// if (doAtAndroidLevel) {
// String minDistanceStr = preferences.getString(PREFS_KEY_GPSLOGGINGDISTANCE,
// String.valueOf(GPS_LOGGING_DISTANCE));
// try {
// minDistance = Float.parseFloat(minDistanceStr);
// } catch (Exception e) {
// // ignore and use default
// }
// String intervalStr = preferences.getString(PREFS_KEY_GPSLOGGINGINTERVAL,
// String.valueOf(GPS_LOGGING_INTERVAL));
// try {
// waitForSecs = Long.parseLong(intervalStr);
// } catch (Exception e) {
// // ignore and use default
// }
// }
if (useNetworkPositions) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, waitForSecs * 1000l, minDistance,
gpsManager);
} else {
locationManager
.requestLocationUpdates(LocationManager.GPS_PROVIDER, waitForSecs * 1000l, minDistance, gpsManager);
}
locationManager.addGpsStatusListener(gpsManager);
}
gpsStarted = true;
for( GpsManagerListener activity : listeners ) {
activity.gpsStart();
}
}
/**
* Checks if the GPS is switched on.
*
* <p>Does not say if the GPS is supplying valid data.</p>
*
* @return <code>true</code> if the GPS is switched on.
*/
public boolean isEnabled() {
if (locationManager == null) {
return false;
}
boolean gpsIsEnabled;
if (useNetworkPositions) {
gpsIsEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} else {
gpsIsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
logABS("Gps is enabled: " + gpsIsEnabled);
return gpsIsEnabled;
}
/**
* Checks if the GPS has a valid fix, i.e. valid data to serve.
*
* @return <code>true</code> if the GPS is in a usable logging state.
*/
public boolean hasFix() {
if (Debug.doMock || isMockMode) {
if (TestMock.isOn)
return true;
}
return gotFix;
}
/**
* Checks if the GPS is currently recording a log.
*
* @return <code>true</code> if the GPS is currently used to record data.
*/
public boolean isDatabaseLogging() {
if (gpsLogger == null) {
return false;
}
return gpsLogger.isDatabaseLogging();
}
public boolean isGpsListening() {
return gpsStarted;
}
public void checkGps( final Context context ) {
if (!isEnabled()) {
String prompt = context.getResources().getString(R.string.prompt_gpsenable);
String ok = context.getResources().getString(android.R.string.yes);
String cancel = context.getResources().getString(android.R.string.no);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(prompt).setCancelable(false).setPositiveButton(ok, new DialogInterface.OnClickListener(){
public void onClick( DialogInterface dialog, int id ) {
Intent gpsOptionsIntent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
context.startActivity(gpsOptionsIntent);
}
});
builder.setNegativeButton(cancel, new DialogInterface.OnClickListener(){
public void onClick( DialogInterface dialog, int id ) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
public GpsLocation getLocation() {
if (lastGpsLocation == null)
return null;
synchronized (lastGpsLocation) {
return lastGpsLocation;
}
}
public int getCurrentRunningGpsLogPointsNum() {
return gpsLogger.getCurrentPointsNum();
}
public long getCurrentRecordedLogId() {
if (gpsLogger == null) {
return -1l;
}
return gpsLogger.getCurrentRecordedLogId();
}
public int getCurrentRunningGpsLogDistance() {
return gpsLogger.getCurrentDistance();
}
/**
* Returns the points of the gps log, if one is being recorded or <code>null</code>.
*
* @return the gps log or <code>null</code>.
*/
public List<double[]> getCurrentRecordedGpsLog() {
return gpsLogger.getCurrentRecordedLog();
}
private void checkLoggerExists( Context context ) {
if (gpsLogger == null) {
gpsLogger = new GpsDatabaseLogger(context);
}
}
/**
* Start gps logging.
*
* @param logName a name for the new gps log or <code>null</code>.
* @param dbHelper the db helper.
*/
public void startDatabaseLogging( Context context, String logName, IGpsLogDbHelper dbHelper ) {
checkLoggerExists(context);
addListener(gpsLogger);
gpsLogger.startDatabaseLogging(logName, dbHelper);
}
/**
* Stop gps logging.
*/
public void stopDatabaseLogging( Context context ) {
checkLoggerExists(context);
gpsLogger.stopDatabaseLogging();
removeListener(gpsLogger);
}
public void onLocationChanged( Location loc ) {
if (loc == null)
return;
lastGpsLocation = new GpsLocation(loc);
synchronized (lastGpsLocation) {
lastLocationupdateMillis = SystemClock.elapsedRealtime();
// Logger.d(gpsManager,
// "Position update: " + gpsLoc.getLongitude() + "/" + gpsLoc.getLatitude() + "/" + gpsLoc.getAltitude()); //$NON-NLS-1$ //$NON-NLS-2$
lastGpsLocation.setPreviousLoc(previousLoc);
// save last known location
double recLon = lastGpsLocation.getLongitude();
double recLat = lastGpsLocation.getLatitude();
double recAlt = lastGpsLocation.getAltitude();
PositionUtilities.putGpsLocationInPreferences(preferences, recLon, recLat, recAlt);
previousLoc = loc;
for( GpsManagerListener activity : listeners ) {
activity.onLocationChanged(lastGpsLocation);
}
}
}
public void onStatusChanged( String provider, int status, Bundle extras ) {
for( GpsManagerListener activity : listeners ) {
activity.onStatusChanged(provider, status, extras);
}
}
public void onProviderEnabled( String provider ) {
for( GpsManagerListener activity : listeners ) {
activity.onProviderEnabled(provider);
}
}
public void onProviderDisabled( String provider ) {
for( GpsManagerListener activity : listeners ) {
activity.onProviderDisabled(provider);
}
}
public void onGpsStatusChanged( int event ) {
mStatus = locationManager.getGpsStatus(mStatus);
// check fix
boolean tmpGotFix = GpsStatusInfo.checkFix(gotFix, lastLocationupdateMillis, event);
if (!tmpGotFix) {
// check if it is just standing still
GpsStatusInfo info = new GpsStatusInfo(mStatus);
int satForFixCount = info.getSatUsedInFixCount();
if (satForFixCount > 2) {
tmpGotFix = true;
// updating loc update, assuming the still filter is giving troubles
lastLocationupdateMillis = SystemClock.elapsedRealtime();
}
}
gotFix = tmpGotFix;
for( GpsManagerListener activity : listeners ) {
activity.onGpsStatusChanged(event, mStatus);
}
}
private static void log( String msg ) {
if (GPLog.LOG_HEAVY)
GPLog.addLogEntry("GPSMANAGER", null, null, msg);
}
private static void logABS( String msg ) {
if (GPLog.LOG_ABSURD)
GPLog.addLogEntry("GPSMANAGER", null, null, msg);
}
}