/*
* ShareNav - Copyright (c) 2007 Harald Mueller james22 at users dot sourceforge dot net
* - Copyright (c) 2008 Kai Krueger apmonkey at users dot sourceforge dot net
* - Copyright (c) 2008 sk750 at users dot sourceforge dot net
* - Copyright (c) 2012 Jyrki Kuoppala jkpj at users dot sourceforge dot net
*
* 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 2
* 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.
*
* See Copying
*/
package net.sharenav.gps.location;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Vector;
//#if polish.android
import android.os.Bundle;
import android.os.Looper;
import de.enough.polish.android.midlet.MidletBridge;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.location.Criteria;
import android.location.GpsStatus;
import android.location.GpsStatus.Listener;
import android.location.GpsStatus.NmeaListener;
import android.location.GpsSatellite;
import java.util.Iterator;
//#endif
import net.sharenav.gps.Satellite;
import net.sharenav.sharenav.data.Position;
import net.sharenav.util.Logger;
import net.sharenav.util.StringTokenizer;
import de.enough.polish.util.Locale;
/**
* This class implements a location producer which uses the Android Location API
* to get the device's current position.
*/
public class AndroidLocationInput
//#if polish.android
implements GpsStatus.Listener, GpsStatus.NmeaListener, LocationListener, LocationMsgProducer
//#endif
{
//#if polish.android
private final static Logger logger = Logger.getInstance(AndroidLocationInput.class,
Logger.TRACE);
private Thread looperThread;
private LocationManager locationManager = null;
private final LocationMsgReceiverList receiverList;
private NmeaMessage smsg;
Position pos = new Position(0f, 0f, 0f, 0f, 0f, 0, System.currentTimeMillis());
private volatile int numSatellites = 0;
private volatile boolean hasFix = false;
private volatile int currentState = LocationMsgReceiver.STATUS_OFF;
private volatile long lastFixTimestamp;
private volatile int gpsState = 0;
private volatile int lmState = 0;
private Looper locationLooper = null;
private Criteria savedCriteria = null;
private OutputStream rawDataLogger;
private static String provider;
/** Array with information about the satellites */
private Satellite satellites[] = new Satellite[36];
public AndroidLocationInput() {
this.receiverList = new LocationMsgReceiverList();
}
public boolean init(LocationMsgReceiver receiver) {
logger.info("Start AndroidLocation LocationProvider");
this.receiverList.addReceiver(receiver);
looperThread = new Thread(new Runnable() {
public void run() {
Looper.prepare();
locationLooper = Looper.myLooper();
createLocationProvider();
// We may be able to get some additional information such as the number
// of satellites form the NMEA string
smsg = new NmeaMessage(receiverList);
Looper.loop();
}
} );
looperThread.run();
if (locationManager != null) {
return true;
} else {
return false;
}
}
public boolean activate(LocationMsgReceiver receiver) {
// FIXME move activation code (code to enable continuos location feed) here
return true;
}
public boolean deactivate(LocationMsgReceiver receiver) {
return true;
}
/**
* Initializes LocationProvider uses default criteria
*
* @throws Exception
*/
void createLocationProvider() {
logger.trace("enter createLocationProvider()");
if (locationManager == null) {
locationManager = (LocationManager)MidletBridge.instance.getSystemService(Context.LOCATION_SERVICE);
// try out different locationprovider criteria combinations, the
// ones with maximum features first
for (int i = 0; i <= 3; i++) {
try {
Criteria criteria = new Criteria();
switch (i) {
case 0:
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(true);
criteria.setBearingRequired(true);
criteria.setSpeedRequired(true);
break;
case 1:
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setBearingRequired(true);
criteria.setSpeedRequired(true);
break;
case 2:
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(true);
}
provider = locationManager.getBestProvider(criteria, true);
if (provider != null) {
logger.info("Chosen location manager:" + locationManager);
savedCriteria = criteria;
break; // we are using this criteria
}
} catch (Exception e) {
logger.exception(Locale.get("androidlocationinput.unexpectedExceptioninLocProv")/*unexpected exception while probing LocationManager criteria.*/,e);
}
}
if (locationManager != null && provider != null) {
try {
locationManager.requestLocationUpdates(provider, 0, 0, this);
locationManager.addGpsStatusListener(this);
locationManager.addNmeaListener(this);
} catch (Exception e) {
logger.fatal("requestLocationUpdates fail: " + e.getMessage());
receiverList.receiveStatus(LocationMsgReceiver.STATUS_SECEX, 0);
locationManager = null;
}
if (locationManager != null) {
//FIXME
updateSolution(LocationProvider.TEMPORARILY_UNAVAILABLE);
}
} else {
receiverList.locationDecoderEnd(Locale.get("androidlocationinput.nointprovider")/*no internal location provider*/);
//#debug info
logger.info("Cannot create LocationProvider for criteria.");
}
}
//#debug
logger.trace("exit createLocationProvider()");
}
public void locationUpdated(LocationManager manager, Location location) {
locationUpdated(manager, location, false);
}
public void locationUpdated(LocationManager manager, Location location, boolean lastKnown) {
//#debug info
logger.info("updateLocation: " + location);
if (location == null) {
//hasFix = false;
return;
}
lastFixTimestamp = System.currentTimeMillis();
hasFix = true;
if (currentState != LocationProvider.AVAILABLE && currentState != LocationProvider.OUT_OF_SERVICE) {
updateSolution(LocationProvider.AVAILABLE);
}
//#debug debug
logger.debug("received Location: " + location);
pos.latitude = (float) location.getLatitude();
pos.longitude = (float) location.getLongitude();
pos.altitude = (float) location.getAltitude();
pos.course = location.getBearing();
pos.speed = location.getSpeed();
pos.timeMillis = location.getTime();
pos.accuracy = location.getAccuracy();
if (lastKnown) {
pos.type = Position.TYPE_GPS_LASTKNOWN;
} else {
pos.type = Position.TYPE_GPS;
}
receiverList.receivePosition(pos);
// logger.trace("exit locationUpdated(provider,location)");
}
public void providerStateChanged(LocationManager lman, int state) {
//#debug info
logger.info("providerStateChanged(" + lman + "," + state + ")");
updateSolution(state);
}
public void close() {
//#debug
logger.trace("enter close()");
// if (locationManager != null){
// locationManager.setLocationListener(null, -1, -1, -1);
// }
if (locationManager != null) {
locationManager.removeUpdates(this);
locationManager.removeGpsStatusListener(this);
locationManager.removeNmeaListener(this);
if (looperThread != null) {
locationLooper.quit();
}
}
locationManager = null;
receiverList.locationDecoderEnd();
//#debug
logger.trace("exit close()");
}
private void updateSolution(int state) {
logger.info("Update Solution");
currentState = state;
if ((System.currentTimeMillis() - lastFixTimestamp) > 3000) {
hasFix = false;
}
//locationUpdated(locationManager, locationManager.getLastKnownLocation(provider), true);
if (state == LocationProvider.OUT_OF_SERVICE) {
if (receiverList != null) {
receiverList.receiveStatus(LocationMsgReceiver.STATUS_OFF, 0);
receiverList.receiveMessage(Locale.get("androidlocationinput.ProviderStopped")/*provider stopped*/);
}
// some devices, e.g. Samsung Galaxy Note will claim LocationProvider.AVAILABLE
// even when there's no fix, so use a timestamp to detect
} else if (state == LocationProvider.TEMPORARILY_UNAVAILABLE || !hasFix) {
if (receiverList != null) {
receiverList.receiveStatus(LocationMsgReceiver.STATUS_NOFIX, numSatellites);
}
} else if (state == LocationProvider.AVAILABLE || hasFix) {
if (receiverList != null) {
receiverList.receiveStatus(LocationMsgReceiver.STATUS_ON, numSatellites);
}
}
}
public void invalidateFix() {
hasFix = false;
if (currentState != LocationProvider.TEMPORARILY_UNAVAILABLE
&& currentState != LocationProvider.OUT_OF_SERVICE) {
updateSolution(LocationProvider.TEMPORARILY_UNAVAILABLE);
}
}
public void triggerPositionUpdate() {
}
public void onNmeaReceived(long timestamp, String nmeaString) {
//#debug info
logger.info("Using extra NMEA info in Android location provider: " + nmeaString);
// FIXME combine to one, duplicated in Jsr179Input and AndroidLocationInput
if (nmeaString != null) {
if (rawDataLogger != null) {
try {
rawDataLogger.write(nmeaString.getBytes());
rawDataLogger.flush();
} catch (IOException ioe) {
logger.exception(Locale.get("jsr179input.CouldNotWriteGPSLog")/*Could not write raw GPS log*/, ioe);
}
}
Vector messages = StringTokenizer.getVector(nmeaString, "$");
if (messages != null) {
for (int i = 0; i < messages.size(); i++) {
String nmeaMessage = (String) messages.elementAt(i);
if (nmeaMessage == null) {
continue;
}
if (nmeaMessage.startsWith("$")) {
// Cut off $GP from the start
nmeaMessage = nmeaMessage.substring(3);
} else if (nmeaMessage.startsWith("GP")) {
// Cut off GP from the start
nmeaMessage = nmeaMessage.substring(2);
}
int delimiterIdx = nmeaMessage.indexOf("*");
if (delimiterIdx > 0) {
// remove the checksum
nmeaMessage = nmeaMessage.substring(0, delimiterIdx);
}
delimiterIdx = nmeaMessage.indexOf(" ");
if (delimiterIdx > 0) {
// remove trailing whitespace because some mobiles like HTC Touch Diamond 2 with JavaFX
// receive NMEA sentences terminated by a space instead of a star followed by the checksum
nmeaMessage = nmeaMessage.substring(0, delimiterIdx);
}
//#debug info
logger.info("Decoding: " + nmeaMessage);
if ((nmeaMessage != null) && (nmeaMessage.length() > 5)) {
smsg.decodeMessage(nmeaMessage, false, false);
// get *DOP from the message
pos.pdop = smsg.getPosition().pdop;
pos.hdop = smsg.getPosition().hdop;
pos.vdop = smsg.getPosition().vdop;
// disable for now if we have a fix; could be this is incorrect for devices with GPS & GLONASS, and we get this from getGpsStatus()
if (!hasFix) {
numSatellites = smsg.getMAllSatellites();
if (currentState == LocationProvider.OUT_OF_SERVICE) {
updateSolution(LocationProvider.OUT_OF_SERVICE);
} else {
updateSolution(LocationProvider.TEMPORARILY_UNAVAILABLE);
}
}
}
}
}
}
}
public void onGpsStatusChanged(int state) {
GpsStatus gpsStatus = locationManager.getGpsStatus(null);
if (state == GpsStatus.GPS_EVENT_STOPPED) {
hasFix = false;
numSatellites = 0;
updateSolution(LocationProvider.OUT_OF_SERVICE);
} else if ((System.currentTimeMillis() - lastFixTimestamp) > 3000) {
invalidateFix();
}
if (state == GpsStatus.GPS_EVENT_STARTED) {
// FIXME do what's needed
updateSolution(LocationProvider.AVAILABLE);
}
gpsState = state;
if (state == GpsStatus.GPS_EVENT_SATELLITE_STATUS && gpsStatus != null) {
for (int j = 0; j < 36; j++) {
/**
* Resetting all the satellites to non locked
*/
if ((satellites[j] != null)) {
satellites[j].isLocked(false);
}
}
Iterable<GpsSatellite> andSatellites = gpsStatus.getSatellites();
Iterator<GpsSatellite> sat = andSatellites.iterator();
int i = 0;
while (sat.hasNext()) {
GpsSatellite satellite = sat.next();
if (satellites[i] == null) {
satellites[i] = new Satellite();
}
if (i < 36) {
satellites[i].isLocked(satellite.usedInFix());
satellites[i].id = i;
satellites[i].azimut = satellite.getAzimuth();
satellites[i].elev = satellite.getElevation();
satellites[i].snr = (int) satellite.getSnr();
if (satellite.usedInFix()) {
i++;
}
}
}
numSatellites = i;
if (numSatellites > 0) {
receiverList.receiveSatellites(satellites);
}
}
//updateSolution(state);
}
public void onProviderDisabled(String provider) {
hasFix = false;
updateSolution(LocationProvider.OUT_OF_SERVICE);
}
public void onProviderEnabled(String provider) {
updateSolution(LocationProvider.AVAILABLE);
}
public void onStatusChanged(String provider, int state, Bundle b) {
//#debug info
logger.info("onStatusChanged(" + provider + "," + state + ")");
lmState = state;
updateSolution(state);
}
public void onLocationChanged(Location loc) {
locationUpdated(locationManager, loc, false);
}
public void triggerLastKnownPositionUpdate() {
locationUpdated(locationManager, locationManager.getLastKnownLocation(provider), true);
}
public void disableRawLogging() {
if (rawDataLogger != null) {
try {
rawDataLogger.close();
} catch (IOException e) {
logger.exception(Locale.get("androidlocationinput.CouldntCloseRawGpsLogger")/*Couldnt close raw gps logger*/, e);
}
rawDataLogger = null;
}
}
public void enableRawLogging(OutputStream os) {
rawDataLogger = os;
}
public void addLocationMsgReceiver(LocationMsgReceiver receiver) {
receiverList.addReceiver(receiver);
}
public boolean removeLocationMsgReceiver(LocationMsgReceiver receiver) {
return receiverList.removeReceiver(receiver);
}
//#endif
}