diff --git a/README.md b/README.md index 77acb4f14..94ba3474f 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ _OpenTracks_ is a sport tracking application that completely respects your priva * __Photos and Markers:__ mark interesting locations while tracking * __Export:__ * export tracks either as KMZ (incl. photos), KML, or GPX - * export automatically after each recording (e.g., to sync) + * export automatically after each recording (e.g., to sync via [Nextcloud](https://nextcloud.com/)) * avoid duplication: each exported file contain a random unique identifier (i.e., `opentracks:trackid`) * __Altitude:__ * gain/loss via barometric sensor (if present) @@ -89,6 +89,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva * cycling: cadence * cycling: power meter * running: speed and cadence + * support for BLE sensor training only (i.e., without GPS) for indoor training + An overview of tested sensors: [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md) ### Gadgetbridge integration diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java index 5d360ed5b..cce0c769f 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java @@ -15,6 +15,13 @@ */ package de.dennisguse.opentracks.content.provider; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -57,13 +64,6 @@ import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.UUIDUtils; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.when; - /** * A unit test for {@link ContentProviderUtils}. * @@ -220,6 +220,8 @@ public class CustomContentProviderUtilsTest { assertEquals(0, tracksPointsCursor.getCount()); markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID); assertEquals(0, markerCursor.getCount()); + + //TODO Close all cursors } /** 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 e5af1422a..fb681c958 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 @@ -55,6 +55,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.exporter.TrackExporter; import de.dennisguse.opentracks.services.TrackRecordingService; +import de.dennisguse.opentracks.services.TrackRecordingServiceTest; import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.services.sensors.AltitudeSumManager; import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; @@ -98,24 +99,29 @@ public class ExportImportTest { private TrackImporter trackImporter; @Before - public void fileSetup() throws IOException { + public void fileSetup() throws IOException, TimeoutException { + TrackRecordingServiceTest.resetService(mServiceRule, context); + tmpFile = File.createTempFile("test", "test", context.getFilesDir()); tmpFileUri = Uri.fromFile(tmpFile); trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(10), Distance.of(200), true); + + TrackRecordingServiceTest.resetService(mServiceRule, context); } @After - public void FileTearDown() throws TimeoutException { + public void tearDown() throws TimeoutException { tmpFile.deleteOnExit(); tmpFileUri = null; - TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class))) - .getService(); - service.getTrackPointCreator().setClock(Clock.systemUTC()); + // Ensure that the database is empty after every test + contentProviderUtils.deleteAllTracks(context); + + TrackRecordingServiceTest.resetService(mServiceRule, context); } - public void setUp(boolean hasSensorDistance) throws TimeoutException { + public void setUp() throws TimeoutException { TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class))) .getService(); @@ -123,13 +129,21 @@ public class ExportImportTest { trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET"))); trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); - Distance sensorDistance = hasSensorDistance ? Distance.of(10) : null; // recording distance interval + Distance sensorDistance = Distance.of(10); // recording distance interval - sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance); + mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); + sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1); service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null); - sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:04Z"), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance); - sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance); + + // A sensor-only TrackPoint + mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); + mockAltitudeChange(trackPointCreator, 1); + sendSensor(trackPointCreator, Instant.parse("2020-02-02T02:02:04Z")); + + mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); + sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.002, 10, 15, 10, 0); service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET"))); @@ -138,12 +152,17 @@ public class ExportImportTest { trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET"))); service.resumeCurrentTrack(); + service.stopUpdateRecordingData(); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:21Z"), ZoneId.of("CET"))); - sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance); + mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); + sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.003, 10, 15, 10, 0); - sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance); - sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance); + mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); + sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0); + + mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); + sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET"))); trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context)); @@ -162,21 +181,11 @@ public class ExportImportTest { assertEquals(2, markers.size()); } - @After - public void tearDown() { - if (trackId != null) { - contentProviderUtils.deleteTrack(context, trackId); - } - if (importTrackId != null) { - contentProviderUtils.deleteTrack(context, importTrackId); - } - } - //TODO Does not test images @LargeTest @Test public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException { - setUp(true); + setUp(); // given Track track = contentProviderUtils.getTrack(trackId); @@ -213,23 +222,40 @@ public class ExportImportTest { assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime()); assertEquals(Instant.parse("2020-02-02T02:02:24Z"), importedTrackStatistics.getStopTime()); - assertEquals(track.getTrackStatistics().getTotalTime(), importedTrackStatistics.getTotalTime()); + TrackStatistics originalTrackStatistics = track.getTrackStatistics(); + + assertEquals(originalTrackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime()); assertEquals(Duration.ofSeconds(8), importedTrackStatistics.getTotalTime()); + + assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime()); assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime()); // Distance + assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance()); assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance()); // Speed + assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed()); assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed()); + + assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed()); assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageSpeed()); + + assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed()); assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed()); // Altitude + assertEquals(originalTrackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01); assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01); + + assertEquals(originalTrackStatistics.getMaxAltitude(), importedTrackStatistics.getMaxAltitude(), 0.01); assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01); - assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01); - assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); + + assertEquals(originalTrackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), 0.01); + assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01); + + assertEquals(originalTrackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01); + assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); // 4. markers assertMarkers(); @@ -238,7 +264,7 @@ public class ExportImportTest { @LargeTest @Test(expected = ImportAlreadyExistsException.class) public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException, IOException { - setUp(false); + setUp(); // given SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); @@ -264,7 +290,7 @@ public class ExportImportTest { @LargeTest @Test public void gpx() throws TimeoutException, IOException { - setUp(true); + setUp(); // given Track track = contentProviderUtils.getTrack(trackId); @@ -297,7 +323,10 @@ public class ExportImportTest { // Therefore, the track segmentation is changes. List trackPointsWithCoordinates = trackPoints.stream().filter(it -> TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(it.getType()) || TrackPoint.Type.TRACKPOINT.equals(it.getType())).collect(Collectors.toList()); trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); - trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); + trackPointsWithCoordinates.get(1).setSensorDistance(Distance.of(20)); + trackPointsWithCoordinates.get(1).setAltitudeGain(1f); + trackPointsWithCoordinates.get(1).setAltitudeLoss(1f); + trackPointsWithCoordinates.get(2).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); TrackPointAssert a = new TrackPointAssert() .setDelta(0.05) @@ -326,8 +355,8 @@ public class ExportImportTest { // Altitude assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01); assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01); - assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01); - assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); + assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01); + assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); // 4. markers assertMarkers(); @@ -336,7 +365,7 @@ public class ExportImportTest { @LargeTest @Test(expected = ImportAlreadyExistsException.class) public void gpx_duplicate_trackUUID() throws TimeoutException, IOException { - setUp(false); + setUp(); // given SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); @@ -380,22 +409,7 @@ public class ExportImportTest { } } - private void sendLocation(TrackPointCreator trackPointCreator, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) { - Location location = new Location("mock"); - location.setLatitude(latitude); - location.setLongitude(longitude); - location.setAccuracy(accuracy); - location.setSpeed(speed); - location.setAltitude(altitude); - - trackPointCreator.setAltitudeSumManager(new AltitudeSumManager() { - @Override - public void fill(@NonNull TrackPoint trackPoint) { - trackPoint.setAltitudeGain(altitudeGain); - trackPoint.setAltitudeLoss(altitudeGain); - } - }); - + private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, Float heartRate, Float cyclingCadence, Float power) { trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) { @Override public SensorDataSet fill(@NonNull TrackPoint trackPoint) { @@ -421,8 +435,30 @@ public class ExportImportTest { return sensorDataSet; } }); + } + + private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) { + AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager(); + altitudeSumManager.setAltitudeGain_m(altitudeGain); + altitudeSumManager.setAltitudeLoss_m(altitudeGain); + } + + private void sendSensor(TrackPointCreator trackPointCreator, Instant time) { + trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET"))); + trackPointCreator.onNewTrackPointWithoutGPS(); + } + + private void sendLocation(TrackPointCreator trackPointCreator, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain) { + Location location = new Location("mock"); + location.setLatitude(latitude); + location.setLongitude(longitude); + location.setAccuracy(accuracy); + location.setSpeed(speed); + location.setAltitude(altitude); + + mockAltitudeChange(trackPointCreator, altitudeGain); trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET"))); - trackPointCreator.getLocationHandler().onLocationChanged(location); + trackPointCreator.getGpsHandler().onLocationChanged(location); } } \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index 814f01232..7670016fb 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.content.ContentProvider; import android.content.Context; import android.content.Intent; import android.os.IBinder; @@ -60,7 +59,6 @@ import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; -import de.dennisguse.opentracks.content.provider.CustomContentProvider; import de.dennisguse.opentracks.io.file.importer.TrackPointAssert; import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.services.sensors.AltitudeSumManager; @@ -110,30 +108,15 @@ public class TrackRecordingServiceTest { }; @Before - public void setUp() { - // Set up the mock content resolver - ContentProvider customContentProvider = new CustomContentProvider() { - }; - customContentProvider.attachInfo(context, null); - + public void setUp() throws TimeoutException { contentProviderUtils = new ContentProviderUtils(context); - // Let's use default values. - PreferencesUtils.clear(); - - // Ensure that the database is empty before every test - contentProviderUtils.deleteAllTracks(context); + tearDown(); } @After public void tearDown() throws TimeoutException { - // Reset service (if some previous test failed) - TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context))) - .getService(); - if (service.isRecording() || service.isPaused()) { - service.endCurrentTrack(); - } - service.getTrackPointCreator().setClock(Clock.systemUTC()); + TrackRecordingServiceTest.resetService(mServiceRule, context); // Ensure that the database is empty after every test contentProviderUtils.deleteAllTracks(context); @@ -211,6 +194,7 @@ public class TrackRecordingServiceTest { trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET"))); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); trackPointCreator.stopGPS(); trackPointCreator.setAltitudeSumManager(altitudeSumManager); @@ -241,12 +225,14 @@ public class TrackRecordingServiceTest { trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET"))); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); trackPointCreator.stopGPS(); trackPointCreator.setAltitudeSumManager(altitudeSumManager); // when trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET"))); service.pauseCurrentTrack(); + service.stopUpdateRecordingData(); // then assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount()); @@ -254,6 +240,7 @@ public class TrackRecordingServiceTest { //when trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET"))); service.resumeTrack(trackId); + service.stopUpdateRecordingData(); // then assertTrue(service.isRecording()); @@ -280,6 +267,7 @@ public class TrackRecordingServiceTest { trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET"))); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); trackPointCreator.stopGPS(); trackPointCreator.setAltitudeSumManager(altitudeSumManager); @@ -322,6 +310,7 @@ public class TrackRecordingServiceTest { trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET"))); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); trackPointCreator.stopGPS(); trackPointCreator.setAltitudeSumManager(altitudeSumManager); @@ -352,10 +341,13 @@ public class TrackRecordingServiceTest { TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context))) .getService(); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); + assertTrue(service.isRecording()); // when Track.Id newTrackId = service.startNewTrack(); + service.stopUpdateRecordingData(); // then assertNotNull(trackId); @@ -405,6 +397,7 @@ public class TrackRecordingServiceTest { trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET"))); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); assertTrue(service.isRecording()); trackPointCreator.onNewTrackPoint( @@ -461,4 +454,19 @@ public class TrackRecordingServiceTest { trackRecordingService.getTrackPointCreator().onNewTrackPoint(trackPoint, Distance.of(50)); } + + //TODO Workaround as service is not stopped on API23; thus sharedpreferences are not reset between tests. + //TODO Anyhow, the service should re-create all it's resources if a recording starts and makes sure that there is no leftovers from previous recordings. + @Deprecated + public static void resetService(ServiceTestRule mServiceRule, Context context) throws TimeoutException { + // Let's use default values. + PreferencesUtils.clear(); + + // Reset service (if some previous test failed) + TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class))) + .getService(); + service.endCurrentTrack(); + service.getTrackPointCreator().setClock(Clock.systemUTC()); + service.sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); + } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java index 177b437b5..fa0c662af 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java @@ -2,7 +2,6 @@ package de.dennisguse.opentracks.services; import static org.junit.Assert.assertFalse; -import android.content.ContentProvider; import android.content.Context; import android.os.Looper; @@ -31,14 +30,12 @@ import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; -import de.dennisguse.opentracks.content.provider.CustomContentProvider; import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate; import de.dennisguse.opentracks.content.sensor.SensorDataRunning; import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.io.file.importer.TrackPointAssert; import de.dennisguse.opentracks.services.sensors.AltitudeSumManager; import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; -import de.dennisguse.opentracks.settings.PreferencesUtils; /** * Tests insert location. @@ -79,24 +76,17 @@ public class TrackRecordingServiceTestLocation { @Before public void setUp() throws TimeoutException { - // Set up the mock content resolver - ContentProvider customContentProvider = new CustomContentProvider() { - }; - customContentProvider.attachInfo(context, null); - contentProviderUtils = new ContentProviderUtils(context); tearDown(); - // Let's use default values. - PreferencesUtils.clear(); - service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context))) .getService(); service.getTrackPointCreator().stopGPS(); } @After - public void tearDown() { + public void tearDown() throws TimeoutException { + TrackRecordingServiceTest.resetService(mServiceRule, context); // Ensure that the database is empty after every test contentProviderUtils.deleteAllTracks(context); } @@ -106,6 +96,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_movingAccurate() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); // when @@ -179,6 +170,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_slowMovingAccurate() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); // when @@ -224,6 +216,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_idle() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); // when @@ -269,6 +262,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_idle_withMovement() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); service.getTrackPointCreator().stopGPS(); @@ -329,6 +323,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_idle_withSensorData() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context) { @@ -403,6 +398,7 @@ public class TrackRecordingServiceTestLocation { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); altitudeSumManager.stop(service); @@ -474,6 +470,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_segment() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); // when diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java index 008fb48b7..345671de5 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java @@ -1,6 +1,10 @@ package de.dennisguse.opentracks.services; -import android.content.ContentProvider; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import android.content.Context; import android.content.SharedPreferences; import android.os.Looper; @@ -30,20 +34,15 @@ import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; -import de.dennisguse.opentracks.content.provider.CustomContentProvider; -import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.settings.PreferencesUtils; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import de.dennisguse.opentracks.stats.TrackStatistics; /** * Tests for the track recording service, which require a {@link Looper}. * * @author Bartlomiej Niechwiej */ +//TODO Check that those tests are really testing something! @RunWith(AndroidJUnit4.class) public class TrackRecordingServiceTestLooper { @@ -66,29 +65,15 @@ public class TrackRecordingServiceTestLooper { } @Before - public void setUp() { - // Set up the mock content resolver - ContentProvider customContentProvider = new CustomContentProvider() { - }; - customContentProvider.attachInfo(context, null); - + public void setUp() throws TimeoutException { contentProviderUtils = new ContentProviderUtils(context); - // Let's use default values. - PreferencesUtils.clear(); - - // Ensure that the database is empty before every test - contentProviderUtils.deleteAllTracks(context); + tearDown(); } @After public void tearDown() throws TimeoutException { - // Reset service (if some previous test failed) - TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context))) - .getService(); - if (service.isRecording() || service.isPaused()) { - service.endCurrentTrack(); - } + TrackRecordingServiceTest.resetService(mServiceRule, context); // Ensure that the database is empty after every test contentProviderUtils.deleteAllTracks(context); @@ -195,6 +180,7 @@ public class TrackRecordingServiceTestLooper { // Start a track. Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); assertNotNull(trackId); assertTrue(service.isRecording()); Track track = contentProviderUtils.getTrack(trackId); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java index b85564f76..f366fa924 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java @@ -3,10 +3,7 @@ package de.dennisguse.opentracks.services; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import android.content.ContentProvider; import android.content.Context; -import android.content.Intent; -import android.os.Build; import android.os.Looper; import androidx.annotation.NonNull; @@ -18,7 +15,6 @@ import androidx.test.rule.ServiceTestRule; import org.junit.After; import org.junit.AfterClass; -import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; @@ -38,9 +34,7 @@ import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; -import de.dennisguse.opentracks.content.provider.CustomContentProvider; import de.dennisguse.opentracks.services.sensors.AltitudeSumManager; -import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.stats.TrackStatistics; /** @@ -81,27 +75,15 @@ public class TrackRecordingServiceTestStatistics { @Before public void setUp() throws TimeoutException { - // Set up the mock content resolver - ContentProvider customContentProvider = new CustomContentProvider() { - }; - customContentProvider.attachInfo(context, null); - contentProviderUtils = new ContentProviderUtils(context); + tearDown(); - - // Let's use default values. - PreferencesUtils.clear(); - - service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context))) - .getService(); - service.getTrackPointCreator().stopGPS(); } @After public void tearDown() throws TimeoutException { - TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class))) - .getService(); - service.getTrackPointCreator().setClock(Clock.systemUTC()); + TrackRecordingServiceTest.resetService(mServiceRule, context); + contentProviderUtils.deleteAllTracks(context); } /** @@ -109,15 +91,15 @@ public class TrackRecordingServiceTestStatistics { */ @MediumTest @Test - public void movingtime_with_pauses() { - Assume.assumeTrue( - "Test fails on API23; reproducible on CI and some machines.", - Build.VERSION.SDK_INT > 23 - ); - + public void movingtime_with_pauses() throws TimeoutException { // given + TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context))) + .getService(); + service.getTrackPointCreator().stopGPS(); + service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault())); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); Function assertMovingTime = expected -> { diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/handlers/LocationHandlerTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/handlers/GPSHandlerTest.java similarity index 98% rename from src/androidTest/java/de/dennisguse/opentracks/services/handlers/LocationHandlerTest.java rename to src/androidTest/java/de/dennisguse/opentracks/services/handlers/GPSHandlerTest.java index 0db2ed2cd..b4b52d69f 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/handlers/LocationHandlerTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/handlers/GPSHandlerTest.java @@ -26,7 +26,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.settings.PreferencesUtils; @RunWith(MockitoJUnitRunner.class) -public class LocationHandlerTest { +public class GPSHandlerTest { private final Context context = ApplicationProvider.getApplicationContext(); @@ -34,7 +34,7 @@ public class LocationHandlerTest { private TrackPointCreator trackPointCreator; @InjectMocks - private LocationHandler locationHandler; + private GPSHandler locationHandler; @BeforeClass public static void preSetUp() { diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/handlers/TrackPointCreatorTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/handlers/TrackPointCreatorTest.java index ee005bb65..c5bd6f527 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/handlers/TrackPointCreatorTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/handlers/TrackPointCreatorTest.java @@ -3,7 +3,6 @@ package de.dennisguse.opentracks.services.handlers; import static org.mockito.Mockito.verify; import android.content.Context; -import android.content.SharedPreferences; import org.junit.After; import org.junit.Before; @@ -26,10 +25,7 @@ public class TrackPointCreatorTest { private TrackPointCreator.Callback server; @Mock - private LocationHandler locationHandler; - - @Mock - private SharedPreferences sharedPreferences; + private GPSHandler locationHandler; private TrackPointCreator subject; diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java index 09b1473d8..ebc2444cc 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java @@ -63,7 +63,8 @@ public class TrackPoint { SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume) SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint - TRACKPOINT(0), //Just GPS data. + TRACKPOINT(0), //Just GPS data and may contain BLE sensor data + SENSORPOINT(2), //Just BLE sensor data SEGMENT_END_MANUAL(1); //End of a segment @@ -310,7 +311,7 @@ public class TrackPoint { } @Nullable - public Distance distanceToPrevious(TrackPoint previous) { + public Distance distanceToPrevious(@Nullable TrackPoint previous) { if (hasSensorDistance()) { return getSensorDistance(); } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java index 9f4e5bc41..640915486 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java @@ -77,7 +77,7 @@ public interface TrackPointsColumns extends BaseColumns { + SENSOR_POWER + " FLOAT, " + ALTITUDE_GAIN + " FLOAT, " + ALTITUDE_LOSS + " FLOAT, " - + TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), " + + TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1, 2)), " + SENSOR_DISTANCE + " FLOAT, " + "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE" + ")"; diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java index c71b00503..4377725c1 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java @@ -26,7 +26,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName(); - private static final int DATABASE_VERSION = 31; + private static final int DATABASE_VERSION = 32; public CustomSQLiteOpenHelper(Context context) { this(context, ((Startup) context.getApplicationContext()).getDatabaseName()); @@ -83,6 +83,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 31: upgradeFrom30to31(db); break; + case 32: + upgradeFrom31to32(db); + break; default: throw new RuntimeException("Not implemented: upgrade to " + toVersion); @@ -119,6 +122,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 30: downgradeFrom31to30(db); break; + case 31: + downgradeFrom32to31(db); + break; default: throw new RuntimeException("Not implemented: downgrade to " + toVersion); } @@ -425,4 +431,35 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.setTransactionSuccessful(); db.endTransaction(); } + + /** + * Add TrackPoint type SENSORPOINT (2). + */ + private void upgradeFrom31to32(SQLiteDatabase db) { + db.beginTransaction(); + + db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old"); + db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1, 2)), sensor_distance FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type, sensor_distance FROM trackpoints_old"); + db.execSQL("DROP TABLE trackpoints_old"); + + db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void downgradeFrom32to31(SQLiteDatabase db) { + db.beginTransaction(); + + db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old"); + db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1)), sensor_distance FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type, sensor_distance FROM trackpoints_old"); + db.execSQL("DROP TABLE trackpoints_old"); + + db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } } diff --git a/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java index 7bcef98c6..d2831f3fa 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java @@ -182,7 +182,9 @@ public class IntervalsFragment extends Fragment { } private synchronized void updateIntervals(boolean metricUnits, IntervalStatisticsModel.IntervalOption selectedInterval) { - boolean update = metricUnits != this.metricUnits || !selectedInterval.sameMultiplier(this.selectedInterval); + boolean update = metricUnits != this.metricUnits + || selectedInterval == null + || !selectedInterval.sameMultiplier(this.selectedInterval); this.metricUnits = metricUnits; this.selectedInterval = selectedInterval; diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java index 91bdb95d0..7a97d170b 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java @@ -24,13 +24,19 @@ import androidx.annotation.NonNull; import java.io.OutputStream; import java.io.PrintWriter; import java.text.NumberFormat; +import java.util.LinkedList; +import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.TrackPointIterator; +import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.StringUtils; /** @@ -122,6 +128,8 @@ public class GPXTrackExporter implements TrackExporter { boolean wroteTrack = false; boolean wroteSegment = false; + LinkedList sensorPoints = new LinkedList<>(); + try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) { while (trackPointIterator.hasNext()) { if (Thread.interrupted()) throw new InterruptedException(); @@ -149,7 +157,12 @@ public class GPXTrackExporter implements TrackExporter { if (wroteSegment) writeCloseSegment(); writeOpenSegment(); wroteSegment = true; - writeTrackPoint(trackPoint); + + writeTrackPoint(trackPoint, sensorPoints); + sensorPoints.clear(); + break; + case SENSORPOINT: + sensorPoints.add(trackPoint); break; case TRACKPOINT: if (!wroteSegment) { @@ -157,11 +170,21 @@ public class GPXTrackExporter implements TrackExporter { writeOpenSegment(); wroteSegment = true; } - writeTrackPoint(trackPoint); + + writeTrackPoint(trackPoint, sensorPoints); + sensorPoints.clear(); break; + default: + throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType()); } } + if (!sensorPoints.isEmpty()) { + //TODO We might miss to export data; this happens if there are SENSORPOINTs after the final TRACKPOINT of a track. + //For segments the data is added to the next segment. + Log.d(TAG, "SENSORPOINTs after final TRACKPOINT; this data is not exported."); + } + if (wroteSegment) { // Should not be necessary as tracks should end with SEGMENT_END_MANUAL. // Anyhow, make sure that the last segment is closed. @@ -200,6 +223,7 @@ public class GPXTrackExporter implements TrackExporter { printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""); printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\""); printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v2\""); + printWriter.println("xmlns:gpxtrkx=\"http://www.garmin.com/xmlschemas/TrackStatsExtension/v1\""); printWriter.println("xmlns:pwr=\"http://www.garmin.com/xmlschemas/PowerExtension/v1\""); printWriter.println("xsi:schemaLocation=" + "\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" @@ -256,6 +280,18 @@ public class GPXTrackExporter implements TrackExporter { printWriter.println(""); printWriter.println("c0c0c0"); printWriter.println("" + track.getUuid() + ""); + + TrackStatistics trackStatistics = track.getTrackStatistics(); + printWriter.println(""); + printWriter.println("" + trackStatistics.getTotalDistance().toM() + ""); + printWriter.println("" + trackStatistics.getTotalTime().getSeconds() + ""); + printWriter.println("" + trackStatistics.getMovingTime().getSeconds() + ""); + printWriter.println("" + trackStatistics.getStoppedTime().getSeconds() + ""); + printWriter.println("" + trackStatistics.getMaxSpeed().toMPS() + ""); + printWriter.println("" + trackStatistics.getTotalAltitudeGain() + ""); + printWriter.println("" + trackStatistics.getTotalAltitudeLoss() + ""); + printWriter.println(""); + printWriter.println(""); } } @@ -274,7 +310,7 @@ public class GPXTrackExporter implements TrackExporter { printWriter.println(""); } - public void writeTrackPoint(TrackPoint trackPoint) { + public void writeTrackPoint(TrackPoint trackPoint, List sensorPoints) { if (printWriter != null) { printWriter.println(""); @@ -285,44 +321,59 @@ public class GPXTrackExporter implements TrackExporter { printWriter.println(""); - if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) { - printWriter.println(""); + { + String trackPointExtensionContent = ""; if (trackPoint.hasSpeed()) { - printWriter.println("" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + ""); + trackPointExtensionContent += "" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "\n"; } if (trackPoint.hasHeartRate()) { - printWriter.println("" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + ""); + trackPointExtensionContent += "" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "\n"; } if (trackPoint.hasCadence()) { - printWriter.println("" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + ""); + trackPointExtensionContent += "" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "\n"; } if (trackPoint.hasPower()) { - printWriter.println("" + POWER_FORMAT.format(trackPoint.getPower()) + ""); + trackPointExtensionContent += "" + POWER_FORMAT.format(trackPoint.getPower()) + "\n"; } - if (trackPoint.hasAltitudeGain()) { - printWriter.println("" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeGain()) + ""); + Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null); + if (cumulativeGain != null) { + trackPointExtensionContent += ("" + ALTITUDE_FORMAT.format(cumulativeGain) + "\n"); } - if (trackPoint.hasAltitudeLoss()) { - printWriter.println("" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeLoss()) + ""); + Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null); + if (cumulativeLoss != null) { + trackPointExtensionContent += ("" + ALTITUDE_FORMAT.format(cumulativeLoss) + "\n"); } - if (trackPoint.hasSensorDistance()) { - printWriter.println("" + DISTANCE_FORMAT.format(trackPoint.getSensorDistance().toM()) + ""); + Double cumulativeDistance = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null); + if (cumulativeDistance != null) { + trackPointExtensionContent += ("" + ALTITUDE_FORMAT.format(cumulativeDistance) + "\n"); } - printWriter.println(""); + if (!trackPointExtensionContent.isEmpty()) { + printWriter.println(""); + printWriter.print(trackPointExtensionContent); + printWriter.println(""); + } } printWriter.println(""); } } + private Double cumulateSensorData(TrackPoint trackPoint, List sensorPoints, Function map) { + return Stream.concat(sensorPoints.stream(), Stream.of(trackPoint)) + .map(map) + .filter(Objects::nonNull) + .reduce((gain, next) -> gain + next) + .orElse(null); + } + private String formatLocation(double latitude, double longitude) { return "lat=\"" + COORDINATE_FORMAT.format(latitude) + "\" lon=\"" + COORDINATE_FORMAT.format(longitude) + "\""; } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java index fa5b31647..59e342bb2 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java @@ -181,6 +181,7 @@ public class KMLTrackExporter implements TrackExporter { writeCloseSegment(); wroteSegment = false; break; + case SENSORPOINT: case TRACKPOINT: if (!wroteSegment) { // Might happen for older data (pre v3.15.0) @@ -189,6 +190,8 @@ public class KMLTrackExporter implements TrackExporter { } writeTrackPoint(trackPoint); break; + default: + throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType()); } } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java index fc0bf3616..71f1b395c 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java @@ -243,7 +243,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac private void onTrackSegmentEnd() { if (currentSegment.isEmpty()) { - Log.w(TAG, "No locations in current segment."); + Log.w(TAG, "No TrackPoints in current segment."); return; } @@ -256,7 +256,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac private TrackPoint createTrackPoint() throws ParsingException { - Instant parsedTime = null; + Instant parsedTime; try { parsedTime = StringUtils.parseTime(time); } catch (Exception e) { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java index 2b0f4fba4..0a7cdf8a2 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java @@ -295,15 +295,21 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac TrackPoint trackPoint; if (i == 0) { + //first if (location == null) { trackPoint = TrackPoint.createSegmentStartManualWithTime(time); } else { trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, location, time); } } else if (i == locationList.size() - 1 && location == null) { + //last trackPoint = TrackPoint.createSegmentEndWithTime(time); } else { - trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, location, time); + if (location == null) { + trackPoint = new TrackPoint(TrackPoint.Type.SENSORPOINT, time); + } else { + trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, location, time); + } } if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) { diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index a9d3a134b..45e42df5d 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -36,9 +36,11 @@ class TrackRecordingManager { private Track.Id trackId; private TrackStatisticsUpdater trackStatisticsUpdater; + //TDOO use lastStoredTrackPoint? private boolean currentSegmentHasTrackPoint; private TrackPoint lastTrackPoint; private TrackPoint lastStoredTrackPoint; + private TrackPoint lastStoredTrackPointWithLocation; TrackRecordingManager(Context context) { this.context = context; @@ -80,15 +82,20 @@ class TrackRecordingManager { trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics()); insertTrackPoint(trackId, segmentStartTrackPoint); - currentSegmentHasTrackPoint = false; - lastTrackPoint = null; - lastStoredTrackPoint = null; } - void pause(TrackPointCreator trackPointCreator) { - insertTrackPoint(trackId, trackPointCreator.createSegmentEnd()); currentSegmentHasTrackPoint = false; lastTrackPoint = null; lastStoredTrackPoint = null; + lastStoredTrackPointWithLocation = null; + } + + void pause(TrackPointCreator trackPointCreator) { + insertTrackPoint(trackId, trackPointCreator.createSegmentEnd()); + + currentSegmentHasTrackPoint = false; + lastTrackPoint = null; + lastStoredTrackPoint = null; + lastStoredTrackPointWithLocation = null; } void end(TrackPointCreator trackPointCreator) { @@ -97,9 +104,11 @@ class TrackRecordingManager { trackId = null; trackStatisticsUpdater = null; + + currentSegmentHasTrackPoint = false; lastTrackPoint = null; lastStoredTrackPoint = null; - currentSegmentHasTrackPoint = false; + lastStoredTrackPointWithLocation = null; } Pair> get(TrackPointCreator trackPointCreator) { @@ -131,7 +140,7 @@ class TrackRecordingManager { name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1); } - if (lastStoredTrackPoint == null) { + if (lastStoredTrackPointWithLocation == null) { Log.i(TAG, "Could not create a marker as trackPoint is unknown."); return null; } @@ -142,11 +151,14 @@ class TrackRecordingManager { photoUrl = photoUrl != null ? photoUrl : ""; // Insert marker - Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPoint, photoUrl); + Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPointWithLocation, photoUrl); Uri uri = contentProviderUtils.insertMarker(marker); return new Marker.Id(ContentUris.parseId(uri)); } + /** + * @return TrackPoint was stored? + */ boolean onNewTrackPoint(TrackPoint trackPoint) { //Storing trackPoint @@ -157,18 +169,31 @@ class TrackRecordingManager { return true; } - Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastStoredTrackPoint); - if (distanceToLastTrackLocation != null) { - if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) { + Distance distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint); + if (distanceToLastStoredTrackPoint != null) { + if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) { trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); insertTrackPoint(trackId, trackPoint); return true; } - if (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving()) { + if (distanceToLastStoredTrackPoint.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving()) { insertTrackPoint(trackId, trackPoint); return true; } + + if (trackPoint.hasLocation()) { + if (lastStoredTrackPointWithLocation == null) { + insertTrackPoint(trackId, trackPoint); + return true; + } + + Distance distanceToLastStoredTrackPointWithLocation = trackPoint.distanceToPrevious(lastStoredTrackPointWithLocation); + if (distanceToLastStoredTrackPointWithLocation != null && distanceToLastStoredTrackPointWithLocation.greaterOrEqualThan(recordingDistanceInterval)) { + insertTrackPoint(trackId, trackPoint); + return true; + } + } } if (lastStoredTrackPoint != null && trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) { @@ -209,6 +234,9 @@ class TrackRecordingManager { contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics()); lastStoredTrackPoint = trackPoint; + if (trackPoint.hasLocation()) { + lastStoredTrackPointWithLocation = lastStoredTrackPoint; + } } catch (SQLiteException e) { /* * Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY). diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index e05126515..a23e5e012 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -18,7 +18,6 @@ package de.dennisguse.opentracks.services; import android.app.PendingIntent; import android.app.Service; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; @@ -50,11 +49,11 @@ import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager; import de.dennisguse.opentracks.services.handlers.GpsStatusValue; import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.services.tasks.VoiceAnnouncementManager; +import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.SettingsActivity; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.ExportUtils; import de.dennisguse.opentracks.util.IntentUtils; -import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.util.SystemUtils; /** @@ -73,6 +72,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator. public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null); public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE; + private final Binder binder = new Binder(); + // The following variables are setFrequency in onCreate: private VoiceAnnouncementManager voiceAnnouncementManager; private TrackRecordingServiceNotificationManager notificationManager; @@ -86,6 +87,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator. @Override public void run() { updateRecordingDataWhileRecording(); + trackPointCreator.onNewTrackPointWithoutGPS(); //TODO Should not be called every second, right? + with do some duplicate computation with updateRecordingDataWhileRecording(). + Handler localHandler = TrackRecordingService.this.handler; if (localHandler == null) { // when this happens, no recording is running and we should not send any notifications. @@ -96,7 +99,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator. } }; - private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { + @Deprecated + //TODO Workaround as service is not stopped on API23; thus sharedpreferences are not reset between tests. + @VisibleForTesting + final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (PreferencesUtils.isKey(R.string.stats_units_key, key)) { @@ -115,11 +121,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator. } }; - // The following variables are setFrequency when recording: + // The following variables are set when recording: private WakeLock wakeLock; - private final Binder binder = new Binder(); - private TrackPointCreator trackPointCreator; //TODO Move to TrackRecordingManager? private RecordingStatus recordingStatus; @@ -148,27 +152,20 @@ public class TrackRecordingService extends Service implements TrackPointCreator. PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - return START_STICKY; - } - - @Override - public Binder onBind(Intent intent) { - return binder; - } - @Override public void onDestroy() { + handler.removeCallbacksAndMessages(null); //Some tests do not finish the recording completely handler = null; + PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + trackPointCreator.stop(); trackPointCreator = null; + trackRecordingManager = null; // Reverse order from onCreate showNotification(false); //TODO Why? - - PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + notificationManager = null; try { voiceAnnouncementManager.shutdown(); @@ -179,6 +176,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator. // This should be the next to last operation wakeLock = SystemUtils.releaseWakeLock(wakeLock); + updateRecordingStatus(STATUS_DEFAULT); recordingStatusObservable = null; gpsStatusObservable = null; recordingDataObservable = null; @@ -186,6 +184,16 @@ public class TrackRecordingService extends Service implements TrackPointCreator. super.onDestroy(); } + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_STICKY; + } + + @Override + public Binder onBind(Intent intent) { + return binder; + } + public boolean isRecording() { return recordingStatus.isRecording(); } @@ -317,7 +325,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator. * @param trackStopped true if track is stopped, false if track is paused */ private void endRecording(boolean trackStopped) { - handler.removeCallbacks(updateRecordingData); + stopUpdateRecordingData(); if (!trackStopped) { updateRecordingDataWhileRecording(); } else { @@ -360,8 +368,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator. return false; } + boolean stored = trackRecordingManager.onNewTrackPoint(trackPoint); notificationManager.updateTrackPoint(this, trackRecordingManager.getTrackStatistics(), trackPoint, thresholdHorizontalAccuracy); - return trackRecordingManager.onNewTrackPoint(trackPoint); + return stored; } @Override @@ -419,6 +428,12 @@ public class TrackRecordingService extends Service implements TrackPointCreator. return trackPointCreator; } + @Deprecated + @VisibleForTesting + public TrackRecordingManager getTrackRecordingManager() { + return trackRecordingManager; + } + public LiveData getGpsStatusObservable() { return gpsStatusObservable; } @@ -456,6 +471,11 @@ public class TrackRecordingService extends Service implements TrackPointCreator. recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second)); } + @VisibleForTesting + public void stopUpdateRecordingData() { + handler.removeCallbacks(updateRecordingData); + } + public LiveData getRecordingStatusObservable() { return recordingStatusObservable; } diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java similarity index 94% rename from src/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java rename to src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java index 2e1506d5a..341618724 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java @@ -15,13 +15,13 @@ import java.time.Duration; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.TrackPoint; -import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.settings.PreferencesUtils; +import de.dennisguse.opentracks.util.LocationUtils; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) -public class LocationHandler implements LocationListener, GpsStatus.GpsStatusListener { +public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener { - private final String TAG = LocationHandler.class.getSimpleName(); + private final String TAG = GPSHandler.class.getSimpleName(); private LocationManager locationManager; private final TrackPointCreator trackPointCreator; @@ -30,7 +30,7 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis private Distance thresholdHorizontalAccuracy; private TrackPoint lastTrackPoint; - public LocationHandler(TrackPointCreator trackPointCreator) { + public GPSHandler(TrackPointCreator trackPointCreator) { this.trackPointCreator = trackPointCreator; } @@ -158,6 +158,10 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis return lastTrackPoint; } + Distance getThresholdHorizontalAccuracy() { + return thresholdHorizontalAccuracy; + } + /** * Called from {@link GpsStatus} to inform that GPS status has changed from prevStatus to currentStatus. * 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 c5c5d6f59..91fb2528d 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -31,25 +31,25 @@ public class TrackPointCreator { @NonNull private Clock clock = Clock.systemUTC(); - private final LocationHandler locationHandler; + private final GPSHandler gpsHandler; private BluetoothRemoteSensorManager remoteSensorManager; private AltitudeSumManager altitudeSumManager; public TrackPointCreator(Callback service) { this.service = service; - this.locationHandler = new LocationHandler(this); + this.gpsHandler = new GPSHandler(this); } @VisibleForTesting - TrackPointCreator(LocationHandler locationHandler, Callback service) { + TrackPointCreator(GPSHandler gpsHandler, Callback service) { this.service = service; - this.locationHandler = locationHandler; + this.gpsHandler = gpsHandler; } public void start(@NonNull Context context) { this.context = context; - locationHandler.onStart(context); + gpsHandler.onStart(context); remoteSensorManager = new BluetoothRemoteSensorManager(context); remoteSensorManager.start(); @@ -58,11 +58,15 @@ public class TrackPointCreator { altitudeSumManager.start(context); } + private boolean isStarted() { + return context != null; + } + @Deprecated //There should be a cooler way to do this; we want to send fake locations without getting affected by real GPS data. @VisibleForTesting public void stopGPS() { - locationHandler.onStop(); + gpsHandler.onStop(); } public void resetSensorData() { @@ -75,6 +79,10 @@ public class TrackPointCreator { } private SensorDataSet fill(TrackPoint trackPoint) { + if (!isStarted()) { + Log.w(TAG, "Not started, should not be called."); + return null; + } SensorDataSet sensorDataSet = remoteSensorManager.fill(trackPoint); altitudeSumManager.fill(trackPoint); @@ -82,7 +90,7 @@ public class TrackPointCreator { } public void stop() { - locationHandler.onStop(); + gpsHandler.onStop(); if (remoteSensorManager != null) { remoteSensorManager.stop(); @@ -98,10 +106,10 @@ public class TrackPointCreator { } public void onSharedPreferenceChanged(String key) { - locationHandler.onSharedPreferenceChanged(key); + gpsHandler.onSharedPreferenceChanged(key); } - public void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) { + public synchronized void onNewTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Distance thresholdHorizontalAccuracy) { fill(trackPoint); boolean stored = service.newTrackPoint(trackPoint, thresholdHorizontalAccuracy); @@ -110,6 +118,10 @@ public class TrackPointCreator { } } + public void onNewTrackPointWithoutGPS() { + onNewTrackPoint(new TrackPoint(TrackPoint.Type.SENSORPOINT, createNow()), gpsHandler.getThresholdHorizontalAccuracy()); + } + public TrackPoint createSegmentStartManual() { return TrackPoint.createSegmentStartManualWithTime(createNow()); } @@ -123,7 +135,7 @@ public class TrackPointCreator { public Pair createCurrentTrackPoint(@Nullable TrackPoint lastValidTrackPoint) { TrackPoint currentTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, createNow()); - TrackPoint lastTrackPoint = locationHandler.getLastTrackPoint(); + TrackPoint lastTrackPoint = gpsHandler.getLastTrackPoint(); if (lastTrackPoint != null && lastTrackPoint.hasLocation()) { currentTrackPoint.setSpeed(lastTrackPoint.getSpeed()); @@ -148,6 +160,11 @@ public class TrackPointCreator { return Instant.now(clock); } + @VisibleForTesting + public AltitudeSumManager getAltitudeSumManager() { + return altitudeSumManager; + } + @Deprecated @VisibleForTesting public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) { @@ -166,8 +183,8 @@ public class TrackPointCreator { } @VisibleForTesting - public LocationHandler getLocationHandler() { - return locationHandler; + public GPSHandler getGpsHandler() { + return gpsHandler; } void sendGpsStatus(GpsStatusValue gpsStatusValue) { diff --git a/src/main/java/de/dennisguse/opentracks/services/sensors/AltitudeSumManager.java b/src/main/java/de/dennisguse/opentracks/services/sensors/AltitudeSumManager.java index 94ac27816..f67e852ab 100644 --- a/src/main/java/de/dennisguse/opentracks/services/sensors/AltitudeSumManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/sensors/AltitudeSumManager.java @@ -65,29 +65,38 @@ public class AltitudeSumManager implements SensorEventListener { trackPoint.setAltitudeLoss(altitudeLoss_m); } - public @Nullable - Float getAltitudeGain_m() { + @Nullable + public Float getAltitudeGain_m() { return isConnected ? altitudeGain_m : null; } + @VisibleForTesting + public void setAltitudeGain_m(float altitudeGain_m) { + this.altitudeGain_m = altitudeGain_m; + } @VisibleForTesting public void addAltitudeGain_m(float altitudeGain_m) { this.altitudeGain_m = this.altitudeGain_m == null ? 0f : this.altitudeGain_m; - this.altitudeGain_m += altitudeGain_m ; + this.altitudeGain_m += altitudeGain_m; } @VisibleForTesting public void addAltitudeLoss_m(Float altitudeLoss_m) { this.altitudeLoss_m = this.altitudeLoss_m == null ? 0f : this.altitudeLoss_m; - this.altitudeLoss_m += altitudeLoss_m ; + this.altitudeLoss_m += altitudeLoss_m; } - public @Nullable - Float getAltitudeLoss_m() { + @Nullable + public Float getAltitudeLoss_m() { return isConnected ? altitudeLoss_m : null; } + @VisibleForTesting + public void setAltitudeLoss_m(float altitudeLoss_m) { + this.altitudeLoss_m = altitudeLoss_m; + } + public void reset() { Log.d(TAG, "Reset"); altitudeGain_m = null; diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java index 112284bf6..54230b298 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java @@ -203,6 +203,10 @@ public class TrackStatistics { movingTime = movingTime.plus(time); } + public Duration getStoppedTime() { + return totalTime.minus(movingTime); + } + /** * Gets the average speed. * This calculation only takes into account the displacement until the last point that was accounted for in statistics.