/**
* Copyright (C) 2013 - 2015 the enviroCar community
* <p>
* This file is part of the enviroCar app.
* <p>
* The enviroCar app 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.
* <p>
* The enviroCar app 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.
* <p>
* You should have received a copy of the GNU General Public License along
* with the enviroCar app. If not, see http://www.gnu.org/licenses/.
*/
package org.envirocar.app.handler;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import com.squareup.otto.Bus;
import org.envirocar.core.events.gps.GpsDOP;
import org.envirocar.core.events.gps.GpsDOPEvent;
import org.envirocar.core.events.gps.GpsLocationChangedEvent;
import org.envirocar.core.events.gps.GpsSatelliteFix;
import org.envirocar.core.events.gps.GpsSatelliteFixEvent;
import org.envirocar.core.events.gps.GpsStateChangedEvent;
import org.envirocar.core.injection.InjectApplicationScope;
import org.envirocar.core.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* TODO JavaDoc
*
* @author dewall
*/
@Singleton
public class LocationHandler {
private static final Logger LOGGER = Logger.getLogger(LocationHandler.class);
private static final int MAX_TIMEFRAME = 1000 * 60;
private static final String GPGSA = "$GPGSA";
private static final String NMEA_SEP = ",";
protected final LocationListener mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
LOGGER.warn("New Location Update");
mLastBestLocation = location;
mBus.post(new GpsLocationChangedEvent(mLastBestLocation));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
LOGGER.info(String.format("onStatusChanged(): %s + %s", provider, "" + status));
}
@Override
public void onProviderEnabled(String provider) {
LOGGER.info(String.format("onProviderEnabled(): %s", provider));
}
@Override
public void onProviderDisabled(String provider) {
LOGGER.info(String.format("onProviderDisabled(): %s", provider));
}
};
/**
* Used for receiving NMEA sentences from the GPS.
*/
private final GpsStatus.NmeaListener mNmeaListener = new GpsStatus.NmeaListener() {
@Override
public void onNmeaReceived(long timestamp, String nmea) {
// eg2.: $GPGSA,A,3,19,28,14,18,27,22,31,39,,,,,1.7,1.0,1.3*35
if (nmea.startsWith(GPGSA)) {
boolean fix = true;
if (nmea.charAt(7) == ',' || nmea.charAt(9) == '1') {
fix = false; // no GPS fix, skip.
}
int checksumIndex = nmea.lastIndexOf("*");
String[] values;
if (checksumIndex > 0) {
values = nmea.substring(0, checksumIndex).split(NMEA_SEP);
} else {
return; // no checksum, skip.
}
int numberOfSats = resolveSatelliteCount(values);
// Set the last satellite fix for GPS.
mLastGpsSatelliteFix = new GpsSatelliteFix(numberOfSats, fix);
// fire an event on the GPS status (fix and number of sats)
mBus.post(new GpsSatelliteFixEvent(numberOfSats, fix));
Double pdop = null, hdop = null, vdop = null;
if (values.length > 15) {
pdop = parseDopString(values[15]);
}
if (values.length > 16) {
hdop = parseDopString(values[16]);
}
if (values.length > 17) {
vdop = parseDopString(values[17]);
}
// Only if positional, horizontal, and vertical DOP is available, then
// set the DOP accordingly.
if (pdop != null || hdop != null || vdop != null) {
// set the new last GPS dop.
mLastGpsDOP = new GpsDOP(pdop, hdop, vdop);
// fire an event on the GPS DOP
mBus.post(new GpsDOPEvent(pdop, hdop, vdop));
}
}
}
/**
* Resolves the number of satellites.
*
* @param values
* @return number of satellites.
*/
private int resolveSatelliteCount(String[] values) {
if (values == null || values.length < 3) {
return 0;
}
int result = 0;
for (int i = 3; i < 15; i++) {
if (i > values.length - 1) {
break;
}
if (!values[i].trim().isEmpty()) {
result++;
}
}
return result;
}
/**
*
* @param string
* @return
*/
private Double parseDopString(String string) {
if (string == null || string.isEmpty()) return null;
try {
return Double.parseDouble(string.trim());
} catch (RuntimeException e) {
// TODO no exception catching?
}
return null;
}
};
// Injected variables.
private final Context mContext;
private final Bus mBus;
private LocationManager mLocationManager;
// Location fields
private Location mLastLocationUpdate;
private Location mLastBestLocation;
// the last satellite fix of GPS events.
private GpsSatelliteFix mLastGpsSatelliteFix;
// Dultion of Precision (DOP) to specify multiplicative effect of
// navigation satellite geometry on positional measurement precision.
private GpsDOP mLastGpsDOP;
private BroadcastReceiver mGPSStateReceiver;
/**
* Constructor.
*
* @param context the context of the current scope.
*/
@Inject
public LocationHandler(@InjectApplicationScope Context context, Bus bus) {
this.mContext = context;
this.mBus = bus;
// Sets the current Location updates to null.
this.mLastLocationUpdate = null;
this.mLastBestLocation = null;
// Get the LocationManager
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
// and initialize the last known location.
initLastKnownLocation();
// Register a new broadcast receiver for state transitions related to GPS.
IntentFilter filter = new IntentFilter();
filter.addAction(LocationManager.PROVIDERS_CHANGED_ACTION);
mGPSStateReceiver = new GpsStateReceiver(isGPSEnabled());
context.registerReceiver(mGPSStateReceiver, filter);
}
/**
* @return true if GPS is enabled.
*/
public boolean isGPSEnabled() {
return mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
/**
*
*/
public void startLocating() {
LOGGER.info("startLocating()");
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
mLocationManager.addNmeaListener(mNmeaListener);
}
/**
*
*/
public void stopLocating() {
LOGGER.info("stopLocating()");
mLocationManager.removeUpdates(mLocationListener);
mLocationManager.removeNmeaListener(mNmeaListener);
}
// @Produce
public GpsLocationChangedEvent produceLocationChangedEvent() {
if (mLastBestLocation == null)
return null;
return new GpsLocationChangedEvent(mLastBestLocation);
}
// @Produce
public GpsDOPEvent produceGpsDOPEvent() {
if (mLastGpsDOP == null)
return null;
return new GpsDOPEvent(mLastGpsDOP);
}
// @Produce
public GpsSatelliteFixEvent produceGpsSatelliteFixEvent() {
if (mLastGpsSatelliteFix == null)
return null;
return new GpsSatelliteFixEvent(mLastGpsSatelliteFix);
}
/**
* Initializes the last known location on start of the application. First,
* it tries to receive the last known GPS location. If this is null, the
* last known network location is considered.
*/
private void initLastKnownLocation() {
// Sets the last known location as an initial value
if (mLastBestLocation == null) {
mLastBestLocation = mLocationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}
if (mLastBestLocation == null) {
mLastBestLocation = mLocationManager
.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
}
/**
* GPSStateReceiver
*/
private final class GpsStateReceiver extends BroadcastReceiver {
private boolean previousState;
/**
* Constructor.
*
* @param currentState tbe current state of the GPS module.
*/
public GpsStateReceiver(boolean currentState) {
this.previousState = currentState;
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(LocationManager.PROVIDERS_CHANGED_ACTION)) {
// get the current gps state.
boolean isActivated = isGPSEnabled();
// if the previous state is different to the current state, then fire a new event.
if (previousState != isActivated) {
mBus.post(new GpsStateChangedEvent(isActivated));
previousState = isActivated;
}
}
}
}
}