mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
Merge pull request #1033 from OpenTracksApp/indoor#500
Storing data from sensors without GPS
This commit is contained in:
@@ -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
|
||||
|
||||
+9
-7
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+86
-50
@@ -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<TrackPoint> 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);
|
||||
}
|
||||
}
|
||||
+28
-20
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-12
@@ -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
|
||||
|
||||
+11
-25
@@ -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);
|
||||
|
||||
+9
-27
@@ -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<Integer, Void> assertMovingTime = expected -> {
|
||||
|
||||
+2
-2
@@ -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() {
|
||||
+1
-5
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
+ ")";
|
||||
|
||||
+38
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<TrackPoint> 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("<extensions>");
|
||||
printWriter.println("<topografix:color>c0c0c0</topografix:color>");
|
||||
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
|
||||
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
printWriter.println("<gpxtrkx:TrackStatsExtension>");
|
||||
printWriter.println("<gpxtrkx:Distance>" + trackStatistics.getTotalDistance().toM() + "</gpxtrkx:Distance>");
|
||||
printWriter.println("<gpxtrkx:TimerTime>" + trackStatistics.getTotalTime().getSeconds() + "</gpxtrkx:TimerTime>");
|
||||
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.getMovingTime().getSeconds() + "</gpxtrkx:MovingTime>");
|
||||
printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>");
|
||||
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.getMaxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
|
||||
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.getTotalAltitudeGain() + "</gpxtrkx:Ascent>");
|
||||
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.getTotalAltitudeLoss() + "</gpxtrkx:Descent>");
|
||||
printWriter.println("</gpxtrkx:TrackStatsExtension>");
|
||||
|
||||
printWriter.println("</extensions>");
|
||||
}
|
||||
}
|
||||
@@ -274,7 +310,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("</trkseg>");
|
||||
}
|
||||
|
||||
public void writeTrackPoint(TrackPoint trackPoint) {
|
||||
public void writeTrackPoint(TrackPoint trackPoint, List<TrackPoint> sensorPoints) {
|
||||
if (printWriter != null) {
|
||||
|
||||
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
||||
@@ -285,44 +321,59 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
|
||||
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
|
||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||
{
|
||||
String trackPointExtensionContent = "";
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>");
|
||||
trackPointExtensionContent += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>\n";
|
||||
}
|
||||
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
|
||||
trackPointExtensionContent += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>\n";
|
||||
}
|
||||
|
||||
if (trackPoint.hasCadence()) {
|
||||
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "</gpxtpx:cad>");
|
||||
trackPointExtensionContent += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "</gpxtpx:cad>\n";
|
||||
}
|
||||
|
||||
if (trackPoint.hasPower()) {
|
||||
printWriter.println("<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower()) + "</pwr:PowerInWatts>");
|
||||
trackPointExtensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower()) + "</pwr:PowerInWatts>\n";
|
||||
}
|
||||
|
||||
if (trackPoint.hasAltitudeGain()) {
|
||||
printWriter.println("<opentracks:gain>" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeGain()) + "</opentracks:gain>");
|
||||
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null);
|
||||
if (cumulativeGain != null) {
|
||||
trackPointExtensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
|
||||
}
|
||||
|
||||
if (trackPoint.hasAltitudeLoss()) {
|
||||
printWriter.println("<opentracks:loss>" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeLoss()) + "</opentracks:loss>");
|
||||
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null);
|
||||
if (cumulativeLoss != null) {
|
||||
trackPointExtensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
printWriter.println("<opentracks:distance>" + DISTANCE_FORMAT.format(trackPoint.getSensorDistance().toM()) + "</opentracks:distance>");
|
||||
Double cumulativeDistance = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null);
|
||||
if (cumulativeDistance != null) {
|
||||
trackPointExtensionContent += ("<opentracks:distance>" + ALTITUDE_FORMAT.format(cumulativeDistance) + "</opentracks:distance>\n");
|
||||
}
|
||||
|
||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
||||
if (!trackPointExtensionContent.isEmpty()) {
|
||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||
printWriter.print(trackPointExtensionContent);
|
||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
||||
}
|
||||
}
|
||||
|
||||
printWriter.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
|
||||
private Double cumulateSensorData(TrackPoint trackPoint, List<TrackPoint> sensorPoints, Function<TrackPoint, Double> 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) + "\"";
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Track, Pair<TrackPoint, SensorDataSet>> 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).
|
||||
|
||||
@@ -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<GpsStatusValue> 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<RecordingStatus> getRecordingStatusObservable() {
|
||||
return recordingStatusObservable;
|
||||
}
|
||||
|
||||
+8
-4
@@ -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.
|
||||
*
|
||||
@@ -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<TrackPoint, SensorDataSet> 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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user