diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 817684f..b1bd0ee 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -36,5 +36,13 @@ + + \ No newline at end of file diff --git a/app/src/main/java/github/luv/mockgeofix/CommandDispatcher.java b/app/src/main/java/github/luv/mockgeofix/CommandDispatcher.java index 18b1d90..3d01f1b 100644 --- a/app/src/main/java/github/luv/mockgeofix/CommandDispatcher.java +++ b/app/src/main/java/github/luv/mockgeofix/CommandDispatcher.java @@ -50,7 +50,12 @@ protected void _dispatch(SocketChannel client, String command) { if ( passwordCommand.passwordRequired() && (! passwordCommand.loggedIn(client)) ) { ResponseWriter.notLoggedIn(client); } else { - geoCommand.execute(client, command); + try { + geoCommand.execute(client, command); + } catch (Throwable ex) { + // anything goes wrong, just ignore this command + Log.e(TAG, "error during executing geo command: "+ex.toString()); + } } } else if (cmd.equals("help")) { helpCommand.execute(client, command); diff --git a/app/src/main/java/github/luv/mockgeofix/MockLocationProvider.java b/app/src/main/java/github/luv/mockgeofix/MockLocationProvider.java index fa44fee..947d850 100644 --- a/app/src/main/java/github/luv/mockgeofix/MockLocationProvider.java +++ b/app/src/main/java/github/luv/mockgeofix/MockLocationProvider.java @@ -46,12 +46,15 @@ static public void simulate(double longitude, double latitude, double altitude, getInstance()._simulate(longitude, latitude, altitude, satellites); } - @SuppressWarnings("UnusedDeclaration") static public void simulate(Location location) { getInstance()._verifyInitiated(); getInstance()._simulate(location); } + static public Location getLocation() { + return new Location(locationProviderName); + } + protected void _init(Context context) { if (mContext != null) { throw new AssertionError(TAG+".init called twice!"); @@ -93,6 +96,12 @@ protected void _simulate(double longitude, double latitude, double altitude, int } protected void _simulate(Location location) { + if (!location.hasAccuracy()) { + location.setAccuracy(accuracy); + } + if (!location.hasAltitude()) { + location.setAltitude(0); + } try { Method locationJellyBeanFixMethod = Location.class.getMethod("makeComplete"); if (locationJellyBeanFixMethod != null) { diff --git a/app/src/main/java/github/luv/mockgeofix/command/GeoNmeaCommand.java b/app/src/main/java/github/luv/mockgeofix/command/GeoNmeaCommand.java index 75f0ade..0d2c3ab 100644 --- a/app/src/main/java/github/luv/mockgeofix/command/GeoNmeaCommand.java +++ b/app/src/main/java/github/luv/mockgeofix/command/GeoNmeaCommand.java @@ -1,17 +1,202 @@ package github.luv.mockgeofix.command; +import android.annotation.SuppressLint; import android.content.Context; +import android.location.Location; +import android.util.Log; import java.nio.channels.SocketChannel; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import github.luv.mockgeofix.MockLocationProvider; +import github.luv.mockgeofix.util.ResponseWriter; public class GeoNmeaCommand implements Command { Context mContext; + String TAG = "GeoNmeaCommand"; + GeoNmeaCommand(Context context) { mContext = context; } @Override public void execute(SocketChannel client, String command) { + String[] args = command.split(" "); + String[] nmeaFields; + + try { + nmeaFields = args[2].split(","); + } catch (IndexOutOfBoundsException ex) { + ResponseWriter.writeLine(client, + "KO: NMEA sentence missing, try 'help geo nmea'"); + return; + } + + String type; + try { + type = nmeaFields[0]; + } catch (IndexOutOfBoundsException ex) { + // ok because that's what the emulator does as well + ResponseWriter.ok(client); + return; + } + + if (type.equals("$GPRMC")) { + processGPRMC(client, nmeaFields, command); + } else if (type.equals("$GPGGA")) { + processGPGGA(client, nmeaFields, command); + } else { + // ok because that's what the emulator does as well + ResponseWriter.ok(client); + } + } + + public void processGPRMC(SocketChannel client, String[] nmeaFields, String command) { + // example: $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 + /* example fields: + 225446 Time of fix 22:54:46 UTC + A Status A=active or V=Void + 4916.45,N Latitude 49 deg. 16.45 min North + 12311.12,W Longitude 123 deg. 11.12 min West + 000.5 Speed over ground, Knots + 054.7 Track angle in degrees, True + 191194 Date of fix 19 November 1994 + 020.3,E Magnetic variation 20.3 deg East + optional field: mode indicator + (can be A=autonomous, D=differential, E=Estimated, N=not valid, S=Simulator) + *68 mandatory checksum + */ + + String strTime, strStatus, strLatitude, strLatitudeQual, strLongitude, strLongitudeQual, + strSpeed, strTrackAngle, strDate; + try { + strTime = nmeaFields[1]; + strStatus = nmeaFields[2]; + strLatitude = nmeaFields[3]; + strLatitudeQual = nmeaFields[4]; + strLongitude = nmeaFields[5]; + strLongitudeQual = nmeaFields[6]; + strSpeed = nmeaFields[7]; + strTrackAngle = nmeaFields[8]; + strDate = nmeaFields[9]; + } catch (IndexOutOfBoundsException ex) { + // ok because that's what the emulator does as well + Log.i(TAG, "Ignoring Nmea sentence: too few fields: "+command); + ResponseWriter.ok(client); + return; + } + + if (!checkChecksum(command)) { + ResponseWriter.ok(client); + Log.i(TAG, "Ignoring Nmea sentence: invalid checksum: "+command); + return; + } + + if (strStatus.toLowerCase().equals("v")) { + ResponseWriter.ok(client); + Log.i(TAG, "Ignoring Nmea sentence: Status is 'V' (void): "+command); + return; + } + + long timestamp; + double latitude; + double longitude; + float speed; + float bearing; + try { + timestamp = convertTimeAndDate(strTime, strDate); + } catch(ParseException ex) { + ResponseWriter.ok(client); + Log.i(TAG, "Ignoring Nmea sentence: Can't parse date or time: "+command); + return; + } + + try { + latitude = convertLatitude(strLatitude, strLatitudeQual); + longitude = convertLongitude(strLongitude, strLongitudeQual); + } catch(NumberFormatException ex) { + ResponseWriter.ok(client); + Log.i(TAG, "Ignoring Nmea sentence: Can't parse latitude or longitude: "+command); + return; + } + + try { + speed = Float.valueOf(strSpeed) * (float)0.51444; + } catch(NumberFormatException ex) { + ResponseWriter.ok(client); + Log.i(TAG, "Ignoring Nmea sentence: Can't parse speed: "+command); + return; + } + + try { + // this might be completely wrong + bearing = Float.valueOf(strTrackAngle); + } catch(NumberFormatException ex) { + ResponseWriter.ok(client); + Log.i(TAG, "Ignoring Nmea sentence: Can't parse track angle: "+command); + return; + } + + Location location = MockLocationProvider.getLocation(); + location.setTime(timestamp); + location.setLatitude(latitude); + location.setLongitude(longitude); + location.setSpeed(speed); + location.setBearing(bearing); + + MockLocationProvider.simulate(location); + Log.d(TAG, "nmea sentence processed (lat, long: "+String.valueOf(latitude)+ + " , "+String.valueOf(longitude)+")"); + + ResponseWriter.ok(client); + } + + public void processGPGGA(SocketChannel client, String[] nmeaFields, String command) { + ResponseWriter.writeLine(client, "KO: Not Implemented Yet"); + } + + public boolean checkChecksum(String command) { + int from = command.indexOf("$"); + int to = command.indexOf("*"); + if (from == -1 || to == -1) { + return false; + } + String checkString = command.substring(from+1,to); + String checksum = command.substring(to+1); + int sum = 0; + for (int i=0; i