diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index 9669a2cea..f86bad646 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -65,6 +65,7 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower; import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate; +import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.handlers.TrackPointCreator; @@ -604,6 +605,6 @@ public class ExportImportTest { mockAltitudeChange(trackPointCreator, altitudeGain); trackPointCreator.setClock(time); - trackPointCreator.onChange(location); + trackPointCreator.getSensorManager().onChanged(new Raw<>(location)); } } \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java index 82c75ce8e..09d041464 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java @@ -415,6 +415,10 @@ public class TrackRecordingServiceRecordingTest { .setAltitudeGain(0f) .setAltitudeLoss(0f), new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) + .setLatitude(45.00002) + .setLongitude(35) + .setHorizontalAccuracy(Distance.of(1)) + .setSpeed(Speed.of(15)) .setAltitudeGain(0f) .setAltitudeLoss(0f) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); @@ -667,6 +671,9 @@ public class TrackRecordingServiceRecordingTest { .setSpeed(Speed.of(5)) .setSensorDistance(Distance.of(0)), new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) + .setLatitude(45.001) + .setLongitude(35) + .setHorizontalAccuracy(Distance.of(1)) .setSensorDistance(Distance.of(11)) .setSpeed(Speed.of(5)) .setSensorDistance(Distance.of(0)) diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/handlers/GPSManagerTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/handlers/GPSManagerTest.java deleted file mode 100644 index 2021c424d..000000000 --- a/src/androidTest/java/de/dennisguse/opentracks/services/handlers/GPSManagerTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package de.dennisguse.opentracks.services.handlers; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.location.Location; -import android.os.Handler; -import android.os.Looper; - -import androidx.test.core.app.ApplicationProvider; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -import java.time.Instant; - -import de.dennisguse.opentracks.data.models.Distance; -import de.dennisguse.opentracks.settings.PreferencesUtils; - -@RunWith(MockitoJUnitRunner.class) -public class GPSManagerTest { - - private final Context context = ApplicationProvider.getApplicationContext(); - - @Mock - private TrackPointCreator trackPointCreator; - - @InjectMocks - private GPSManager locationHandler; - - @BeforeClass - public static void preSetUp() { - // Prepare looper for Android's message queue - if (Looper.myLooper() == null) Looper.prepare(); - } - - @Before - public void setUp() { - // Let's use default values. - PreferencesUtils.clear(); - - Mockito.when(trackPointCreator.createNow()) - .thenReturn(Instant.now()); - - locationHandler.start(context, new Handler()); - } - - /** - * When a valid location changed in LocationHandler -> newTrackPoint service method is called. - */ - @Test - public void testOnLocationChanged_okay() { - // when - locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis())); - - // then - verify(trackPointCreator, times(1)).onChange(any(Location.class)); - } - - /** - * When location changed in LocationHandler with bad location -> newTrackPoint service method is not called. - */ - @Test - public void testOnLocationChanged_badLocation() { - // given - float latitude = 91f; - - // when - // bad latitude - locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis())); - - // then - verify(trackPointCreator, times(0)).onChange(any(Location.class)); - } - - /** - * When location changed in LocationHandler with poor accuracy -> newTrackPoint service method is not called. - */ - @Test - public void testOnLocationChanged_poorAccuracy() { - // given - Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(); - - // when - locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis())); - - // then - // no newTrackPoint called - verify(trackPointCreator, times(0)).onChange(any(Location.class)); - } - - @Test - public void testOnLocationChanged_movingInaccurate() { - // when - locationHandler.onLocationChanged(createLocation(45.0, 35.0, 5, 15, System.currentTimeMillis())); - locationHandler.onLocationChanged(createLocation(45.1, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis())); - locationHandler.onLocationChanged(createLocation(45.2, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis())); - locationHandler.onLocationChanged(createLocation(45.3, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis())); - locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis())); - - // then - verify(trackPointCreator, times(1)).onChange(any(Location.class)); - } - - /** - * Creates a location with parameters and returns the Location object. - */ - private static Location createLocation(double latitude, double longitude, float accuracy, long speed, long time) { - Location location = new Location("gps"); - location.setLongitude(longitude); - location.setLatitude(latitude); - location.setAccuracy(accuracy); - location.setSpeed(speed); - location.setTime(time); - location.setBearing(3.0f); - return location; - } -} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java b/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java index a4815ba0b..8a2908b24 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java @@ -30,7 +30,6 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange @Override public void onConnect(Aggregator aggregator) { sensorDataSet.add(aggregator); - observer.onChange(new SensorDataSet(sensorDataSet)); } @Override @@ -42,13 +41,11 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange @Override public void onDisconnect(Aggregator aggregator) { sensorDataSet.add(aggregator); - observer.onChange(new SensorDataSet(sensorDataSet)); } @Override public void onRemove(Aggregator aggregator) { sensorDataSet.remove(aggregator); - observer.onChange(new SensorDataSet(sensorDataSet)); } }; @@ -67,7 +64,7 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange throw new RuntimeException("SensorManager cannot be started twice; stop first."); } - gpsManager = new GPSManager(observer); //TODO Pass listener + gpsManager = new GPSManager(observer, listener); altitudeSumManager = new GainManager(listener); bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener); @@ -104,7 +101,6 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange sensorDataSet.reset(); } - @Deprecated @VisibleForTesting public void onChanged(Raw data) { listener.onChange(data); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java index 82ae79c2f..91be573e1 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java @@ -6,7 +6,7 @@ import java.time.Instant; import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager; -public abstract class Aggregator { +public abstract class Aggregator { protected Raw previous; diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorGPS.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorGPS.java new file mode 100644 index 000000000..71c2aca87 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorGPS.java @@ -0,0 +1,28 @@ +package de.dennisguse.opentracks.sensors.sensorData; + +import android.location.Location; + +import androidx.annotation.NonNull; + +public class AggregatorGPS extends Aggregator { + + public AggregatorGPS(String sensorAddress) { + super(sensorAddress); + } + + @Override + protected void computeValue(Raw current) { + value = current.value(); + } + + @Override + public void reset() { + value = null; + } + + @NonNull + @Override + protected Location getNoneValue() { + return new Location("none"); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Raw.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Raw.java index 5f88bb229..125d2268f 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Raw.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Raw.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; import java.time.Instant; -public record Raw( +public record Raw( @NonNull T value, @NonNull Instant time diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java index 692aecf77..8b423fdba 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java @@ -1,5 +1,6 @@ package de.dennisguse.opentracks.sensors.sensorData; +import android.location.Location; import android.util.Log; import android.util.Pair; @@ -40,16 +41,20 @@ public final class SensorDataSet { @VisibleForTesting public AggregatorBarometer barometer; + public AggregatorGPS gps; + public SensorDataSet() { } public SensorDataSet(SensorDataSet toCopy) { + //TODO This is not a copy constructor anymore, but it should be - aggregators are no value objects this.heartRate = toCopy.heartRate; this.cyclingCadence = toCopy.cyclingCadence; this.cyclingDistanceSpeed = toCopy.cyclingDistanceSpeed; this.cyclingPower = toCopy.cyclingPower; this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence; this.barometer = toCopy.barometer; + this.gps = toCopy.gps; } public Pair getHeartRate() { @@ -93,7 +98,7 @@ public final class SensorDataSet { } public void update(@NonNull Raw data) { - Record value = data.value(); + Object value = data.value(); if (value instanceof HeartRate) { this.heartRate.add((Raw) data); @@ -122,6 +127,10 @@ public final class SensorDataSet { this.barometer.add((Raw) data); return; } + if (value instanceof Location) { + this.gps.add((Raw) data); + return; + } throw new UnsupportedOperationException(data.getClass().getCanonicalName() + " " + data.value().getClass().getCanonicalName()); } @@ -138,9 +147,14 @@ public final class SensorDataSet { this.cyclingPower = null; this.runningDistanceSpeedCadence = null; this.barometer = null; + this.gps = null; } public void fillTrackPoint(TrackPoint trackPoint) { + if (gps != null && gps.hasValue()) { + trackPoint.setLocation(gps.getValue()); + } + if (getHeartRate() != null) { trackPoint.setHeartRate(getHeartRate().first); } @@ -180,6 +194,7 @@ public final class SensorDataSet { if (cyclingPower != null) cyclingPower.reset(); if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset(); if (barometer != null) barometer.reset(); + if (gps != null) gps.reset(); } private void set(@NonNull Aggregator type, @Nullable Aggregator sensorData) { @@ -209,6 +224,10 @@ public final class SensorDataSet { barometer = (AggregatorBarometer) sensorData; return; } + if (type instanceof AggregatorGPS) { + gps = (AggregatorGPS) sensorData; + return; + } throw new UnsupportedOperationException(type.getClass().getCanonicalName()); } diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java index c000292f9..c2b6dce93 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java @@ -20,6 +20,9 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.sensors.SensorConnector; +import de.dennisguse.opentracks.sensors.SensorManager; +import de.dennisguse.opentracks.sensors.sensorData.AggregatorGPS; +import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.PermissionRequester; @@ -32,6 +35,8 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS private static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER; private TrackPointCreator trackPointCreator; + + private SensorManager.SensorDataChangedObserver listener; private Context context; private Handler handler; @@ -40,8 +45,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS private Duration gpsInterval; private Distance thresholdHorizontalAccuracy; - public GPSManager(TrackPointCreator trackPointCreator) { + public GPSManager(TrackPointCreator trackPointCreator, SensorManager.SensorDataChangedObserver listener) { this.trackPointCreator = trackPointCreator; + this.listener = listener; } public void start(@NonNull Context context, @NonNull Handler handler) { @@ -52,6 +58,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS gpsStatusManager = new GpsStatusManager(context, this, handler); locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + + listener.onConnect(new AggregatorGPS("internal")); + registerLocationListener(); gpsStatusManager.start(); } @@ -72,6 +81,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS gpsStatusManager.stop(); gpsStatusManager = null; + listener.onDisconnect(new AggregatorGPS("internal")); + listener = null; + trackPointCreator = null; } @@ -126,7 +138,7 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS return; } - trackPointCreator.onChange(location); + listener.onChange(new Raw<>(location)); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java index f779e6ddc..6e521404a 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -2,7 +2,6 @@ package de.dennisguse.opentracks.services.handlers; import android.content.Context; import android.content.SharedPreferences; -import android.location.Location; import android.os.Handler; import android.util.Log; import android.util.Pair; @@ -69,10 +68,6 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh this.context = null; } - public synchronized void onChange(@NonNull Location location) { - onNewTrackPoint(new TrackPoint(location, createNow())); - } - /** * Got a new TrackPoint from Bluetooth only; contains no GPS location. */