mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-09-30 16:52:21 +02:00
+2
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
+7
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
||||
|
||||
public abstract class Aggregator<Input extends Record, Output> {
|
||||
public abstract class Aggregator<Input, Output> {
|
||||
|
||||
protected Raw<Input> previous;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class AggregatorGPS extends Aggregator<Location, Location> {
|
||||
|
||||
public AggregatorGPS(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeValue(Raw<Location> current) {
|
||||
value = current.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
value = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Location getNoneValue() {
|
||||
return new Location("none");
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record Raw<T extends Record>(
|
||||
public record Raw<T>(
|
||||
@NonNull T value,
|
||||
|
||||
@NonNull Instant time
|
||||
|
||||
@@ -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<HeartRate, String> 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<HeartRate>) data);
|
||||
@@ -122,6 +127,10 @@ public final class SensorDataSet {
|
||||
this.barometer.add((Raw<AtmosphericPressure>) data);
|
||||
return;
|
||||
}
|
||||
if (value instanceof Location) {
|
||||
this.gps.add((Raw<Location>) 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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user