Use system time instead of GPS-provided time.

#316.
This commit is contained in:
Dennis Guse
2020-12-22 21:41:04 +01:00
parent fcc17413e3
commit bc66cd33a9
8 changed files with 49 additions and 106 deletions
@@ -190,9 +190,15 @@ public class ChartFragmentTest {
*/ */
@Test @Test
public void testCreatePendingPoint_time() { public void testCreatePendingPoint_time() {
// given
chartFragment.setChartByDistance(false); chartFragment.setChartByDistance(false);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setTime(TrackStubUtils.INITIAL_TIME); //Keep old TrackPoint behavior of having time=0 for this test
// when
ChartPoint point = chartFragment.createPendingPoint(trackPoint1); ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
// then
assertEquals(0.0, point.getTimeOrDistance(), 0.01); assertEquals(0.0, point.getTimeOrDistance(), 0.01);
long timeSpan = 222; long timeSpan = 222;
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint(); TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
@@ -3,10 +3,12 @@ package de.dennisguse.opentracks.services.handlers;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.location.Location; import android.location.Location;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@@ -32,6 +34,12 @@ public class LocationHandlerTest {
@InjectMocks @InjectMocks
private LocationHandler locationHandler; private LocationHandler locationHandler;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@Before @Before
public void setUp() { public void setUp() {
// Let's use default values. // Let's use default values.
@@ -80,7 +88,6 @@ public class LocationHandlerTest {
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context); int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
// when // when
// poor latitude
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis())); locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
// then // then
@@ -1,53 +0,0 @@
package de.dennisguse.opentracks.util;
import org.junit.Test;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals;
public class TrackPointUtilsTest {
@Test
public void fixTime_none() {
// given
long time = System.currentTimeMillis();
TrackPoint trackPoint = new TrackPoint();
trackPoint.setTime(time);
// when
TrackPointUtils.fixTime(trackPoint);
// then
assertEquals(time, trackPoint.getTime());
}
@Test
public void fixTime_0() {
// given
long time = System.currentTimeMillis();
TrackPoint trackPoint = new TrackPoint();
trackPoint.setTime(0);
// when
TrackPointUtils.fixTime(trackPoint);
// then
assertEquals(time, trackPoint.getTime(), 1000);
}
@Test
public void fixTime_gpsWeekRollover() {
// given
long time = System.currentTimeMillis();
TrackPoint trackPoint = new TrackPoint();
trackPoint.setTime(time - 1024 * UnitConversions.ONE_WEEK_MS);
// when
TrackPointUtils.fixTime(trackPoint);
// then
assertEquals(time, trackPoint.getTime());
}
}
@@ -27,7 +27,8 @@ import java.util.Objects;
/** /**
* This class extends the standard Android location with extra information. * This class extends the standard Android location with extra information.
* <p> * <p>
* NOTE: default location will be latitude=0.0 and longitude=0.0 (this is not meaningful). * NOTE: default location will be latitude=0.0 and longitude=0.0.
* For {@link TrackPoint}s with `type == null` this is not meaningful.
* <p> * <p>
* NOTE: For Locations provided by the GPS. * NOTE: For Locations provided by the GPS.
* We are replacing the GPS-provided time using the system time. * We are replacing the GPS-provided time using the system time.
@@ -99,6 +100,7 @@ public class TrackPoint {
public TrackPoint(@NonNull Location location) { public TrackPoint(@NonNull Location location) {
this.type = Type.TRACKPOINT; this.type = Type.TRACKPOINT;
this.location = location; this.location = location;
setTime(System.currentTimeMillis());
} }
public TrackPoint(@NonNull Type type, @NonNull Location location) { public TrackPoint(@NonNull Type type, @NonNull Location location) {
@@ -554,8 +554,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy); notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy);
TrackPointUtils.fixTime(trackPoint);
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database //TODO Figure out how to avoid loading the lastValidTrackPoint from the database
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId()); TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
@@ -1,10 +1,12 @@
package de.dennisguse.opentracks.services.handlers; package de.dennisguse.opentracks.services.handlers;
import android.content.Context; import android.content.Context;
import android.location.Location;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
@@ -27,10 +29,12 @@ class GpsStatus {
private GpsStatusListener client; private GpsStatusListener client;
private final Context context; private final Context context;
// Last location. It can be null. @Nullable
private Location lastLocation = null; private TrackPoint lastTrackPoint = null;
@Nullable
// The last valid (not null) location. Null value means that there have not been any location yet. // The last valid (not null) location. Null value means that there have not been any location yet.
private Location lastValidLocation = null; private TrackPoint lastValidTrackPoint = null;
// Flag to prevent GpsStatus checks two or more locations at the same time. // Flag to prevent GpsStatus checks two or more locations at the same time.
private boolean checking = false; private boolean checking = false;
@@ -97,28 +101,28 @@ class GpsStatus {
} }
/** /**
* This method must be called from the client every time a new location is received. * This method must be called from the client every time a new trackPoint is received.
* Receive new location and calculate the new status if needed. * Receive new trackPoint and calculate the new status if needed.
* It look for GPS changes in lastLocation if it's not null. If it's null then look for in lastValidLocation if any. * It look for GPS changes in lastLocation if it's not null. If it's null then look for in lastValidLocation if any.
*/ */
public void onLocationChanged(final Location location) { public void onLocationChanged(final TrackPoint trackPoint) {
if (checking) { if (checking) {
return; return;
} }
checking = true; checking = true;
if (lastLocation != null) { if (lastTrackPoint != null) {
checkStatusFromLastLocation(); checkStatusFromLastLocation();
} else if (lastValidLocation != null) { } else if (lastValidTrackPoint != null) {
checkStatusFromLastValidLocation(); checkStatusFromLastValidLocation();
} }
if (location != null) { if (trackPoint != null) {
// Update location's time to the current time millis when location has been received. // Update trackPoint's time to the current time millis when trackPoint has been received.
location.setTime(System.currentTimeMillis()); trackPoint.setTime(System.currentTimeMillis());
lastValidLocation = location; lastValidTrackPoint = trackPoint;
} }
lastLocation = location; lastTrackPoint = trackPoint;
checking = false; checking = false;
} }
@@ -129,19 +133,19 @@ class GpsStatus {
* Also, it'll run the runnable if signal is bad or stop it if the signal is lost. * Also, it'll run the runnable if signal is bad or stop it if the signal is lost.
*/ */
private void checkStatusFromLastLocation() { private void checkStatusFromLastLocation() {
if (System.currentTimeMillis() - lastLocation.getTime() > signalLostThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_LOST) { if (System.currentTimeMillis() - lastTrackPoint.getTime() > signalLostThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_LOST) {
// Too much time without receiving signal -> signal lost. // Too much time without receiving signal -> signal lost.
GpsStatusValue oldStatus = gpsStatus; GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST; gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST;
sendStatus(oldStatus, gpsStatus); sendStatus(oldStatus, gpsStatus);
stopStatusRunner(); stopStatusRunner();
} else if (lastLocation.getAccuracy() > signalBadThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_BAD) { } else if (lastTrackPoint.getAccuracy() > signalBadThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_BAD) {
// Too little accuracy -> bad signal. // Too little accuracy -> bad signal.
GpsStatusValue oldStatus = gpsStatus; GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_BAD; gpsStatus = GpsStatusValue.GPS_SIGNAL_BAD;
sendStatus(oldStatus, gpsStatus); sendStatus(oldStatus, gpsStatus);
startStatusRunner(); startStatusRunner();
} else if (lastLocation.getAccuracy() <= signalBadThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_FIX) { } else if (lastTrackPoint.getAccuracy() <= signalBadThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_FIX) {
// Gps okay. // Gps okay.
GpsStatusValue oldStatus = gpsStatus; GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_FIX; gpsStatus = GpsStatusValue.GPS_SIGNAL_FIX;
@@ -156,13 +160,13 @@ class GpsStatus {
* If there is any change then it does the change. * If there is any change then it does the change.
*/ */
private void checkStatusFromLastValidLocation() { private void checkStatusFromLastValidLocation() {
if (System.currentTimeMillis() - lastValidLocation.getTime() > signalLostThreshold) { if (System.currentTimeMillis() - lastValidTrackPoint.getTime() > signalLostThreshold) {
// Too much time without locations -> lost signal? (wait signalLostThreshold from last valid location). // Too much time without locations -> lost signal? (wait signalLostThreshold from last valid location).
GpsStatusValue oldStatus = gpsStatus; GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST; gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST;
sendStatus(oldStatus, gpsStatus); sendStatus(oldStatus, gpsStatus);
stopStatusRunner(); stopStatusRunner();
lastValidLocation = null; lastValidTrackPoint = null;
} }
} }
@@ -192,8 +196,8 @@ class GpsStatus {
GpsStatusValue oldStatus = gpsStatus; GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_DISABLED; gpsStatus = GpsStatusValue.GPS_DISABLED;
sendStatus(oldStatus, gpsStatus); sendStatus(oldStatus, gpsStatus);
lastLocation = null; lastTrackPoint = null;
lastValidLocation = null; lastValidTrackPoint = null;
stopStatusRunner(); stopStatusRunner();
} }
} }
@@ -90,18 +90,20 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
*/ */
@Override @Override
public void onLocationChanged(@NonNull Location location) { public void onLocationChanged(@NonNull Location location) {
TrackPoint trackPoint = new TrackPoint(location);
boolean isAccurate = TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy);
boolean isValid = LocationUtils.isValidLocation(location);
if (gpsStatus != null) { if (gpsStatus != null) {
gpsStatus.onLocationChanged(location); gpsStatus.onLocationChanged(trackPoint);
} }
if (!LocationUtils.isValidLocation(location)) { if (!isValid) {
Log.w(TAG, "Ignore newTrackPoint. location is invalid."); Log.w(TAG, "Ignore newTrackPoint. location is invalid.");
return; return;
} }
TrackPoint trackPoint = new TrackPoint(location); if (!isAccurate) {
if (!TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy)) {
Log.d(TAG, "Ignore newTrackPoint. Poor accuracy."); Log.d(TAG, "Ignore newTrackPoint. Poor accuracy.");
return; return;
} }
@@ -1,7 +1,5 @@
package de.dennisguse.opentracks.util; package de.dennisguse.opentracks.util;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
@@ -16,27 +14,6 @@ public class TrackPointUtils {
private TrackPointUtils() { private TrackPointUtils() {
} }
/**
* 1. Ancient fix for phones that do not set the time in {@link android.location.Location}.
* 2. Fix for GPS time rollover happening every 19.7 years: https://en.wikipedia.org/wiki/GPS_Week_Number_Rollover
*/
public static void fixTime(@NonNull TrackPoint trackPoint) {
if (trackPoint.getTime() == 0L) {
Log.w(TAG, "Time of provided location was 0. Using current time.");
trackPoint.setTime(System.currentTimeMillis());
return;
}
{
long timeDiff = Math.abs(trackPoint.getTime() - System.currentTimeMillis());
if (timeDiff > 1023 * UnitConversions.ONE_WEEK_MS) {
Log.w(TAG, "GPS week rollover.");
trackPoint.setTime(trackPoint.getTime() + 1024 * UnitConversions.ONE_WEEK_MS);
}
}
}
public static boolean isMoving(@NonNull TrackPoint trackPoint) { public static boolean isMoving(@NonNull TrackPoint trackPoint) {
return trackPoint.hasSpeed() && trackPoint.getSpeed() >= MAX_NO_MOVEMENT_SPEED; return trackPoint.hasSpeed() && trackPoint.getSpeed() >= MAX_NO_MOVEMENT_SPEED;
} }