From 3359a30907341b4218f6233909aa72f60d29130f Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sun, 21 Nov 2021 13:53:16 +0100 Subject: [PATCH 01/13] TrackRecordingService: first process the TrackPoint, then update the notification. --- .../opentracks/services/TrackRecordingManager.java | 3 +++ .../opentracks/services/TrackRecordingService.java | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index a9d3a134b..c18326cd1 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -147,6 +147,9 @@ class TrackRecordingManager { return new Marker.Id(ContentUris.parseId(uri)); } + /** + * @return TrackPoint was stored? + */ boolean onNewTrackPoint(TrackPoint trackPoint) { //Storing trackPoint diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index e05126515..2c81fddb5 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; /** @@ -360,8 +359,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 From 45125bcda314c0cc044ed2c60b36241d49d6726e Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sun, 21 Nov 2021 14:14:55 +0100 Subject: [PATCH 02/13] Refactor: rename LocationHandler to GPSHandler. --- .../io/file/importer/ExportImportTest.java | 2 +- ...onHandlerTest.java => GPSHandlerTest.java} | 4 ++-- .../handlers/TrackPointCreatorTest.java | 2 +- .../services/TrackRecordingService.java | 6 ++--- .../{LocationHandler.java => GPSHandler.java} | 8 +++---- .../services/handlers/TrackPointCreator.java | 22 +++++++++---------- 6 files changed, 22 insertions(+), 22 deletions(-) rename src/androidTest/java/de/dennisguse/opentracks/services/handlers/{LocationHandlerTest.java => GPSHandlerTest.java} (98%) rename src/main/java/de/dennisguse/opentracks/services/handlers/{LocationHandler.java => GPSHandler.java} (95%) 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..def9c5762 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 @@ -423,6 +423,6 @@ public class ExportImportTest { }); 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/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..911a282a9 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/handlers/TrackPointCreatorTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/handlers/TrackPointCreatorTest.java @@ -26,7 +26,7 @@ public class TrackPointCreatorTest { private TrackPointCreator.Callback server; @Mock - private LocationHandler locationHandler; + private GPSHandler locationHandler; @Mock private SharedPreferences sharedPreferences; diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 2c81fddb5..ace7d127d 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -72,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; @@ -114,11 +116,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; 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 95% 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..2ce546cfe 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; } 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..3572fd974 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(); @@ -62,7 +62,7 @@ public class TrackPointCreator { //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() { @@ -82,7 +82,7 @@ public class TrackPointCreator { } public void stop() { - locationHandler.onStop(); + gpsHandler.onStop(); if (remoteSensorManager != null) { remoteSensorManager.stop(); @@ -98,7 +98,7 @@ public class TrackPointCreator { } public void onSharedPreferenceChanged(String key) { - locationHandler.onSharedPreferenceChanged(key); + gpsHandler.onSharedPreferenceChanged(key); } public void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) { @@ -123,7 +123,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()); @@ -166,8 +166,8 @@ public class TrackPointCreator { } @VisibleForTesting - public LocationHandler getLocationHandler() { - return locationHandler; + public GPSHandler getGpsHandler() { + return gpsHandler; } void sendGpsStatus(GpsStatusValue gpsStatusValue) { From 24b638aa3016a23131927801ce0ac8db062aef94 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 24 Nov 2021 19:42:05 +0100 Subject: [PATCH 03/13] Add TrackPoint type for BLE sensor only data (i.e., no GPS). Part of #500. --- .../opentracks/content/data/TrackPoint.java | 3 +- .../content/data/TrackPointsColumns.java | 2 +- .../provider/CustomSQLiteOpenHelper.java | 39 ++++++++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) 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..63299f078 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 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(); + } } From ba66c4500595fa456b92ff3ee72a4272a0a3eba7 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sun, 21 Nov 2021 15:10:13 +0100 Subject: [PATCH 04/13] Recording data from BLE sensors alone (need to provide distance). GPS is optional in this case, but no coordinates will be available. Naive implementation tries to store data every second using the UI timer. Part of #500. --- .../opentracks/services/handlers/TrackPointCreatorTest.java | 4 ---- .../opentracks/services/TrackRecordingManager.java | 2 +- .../opentracks/services/TrackRecordingService.java | 2 ++ .../dennisguse/opentracks/services/handlers/GPSHandler.java | 4 ++++ .../opentracks/services/handlers/TrackPointCreator.java | 6 +++++- 5 files changed, 12 insertions(+), 6 deletions(-) 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 911a282a9..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; @@ -28,9 +27,6 @@ public class TrackPointCreatorTest { @Mock private GPSHandler locationHandler; - @Mock - private SharedPreferences sharedPreferences; - private TrackPointCreator subject; @Before diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index c18326cd1..46d18f5d8 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -142,7 +142,7 @@ 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)); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index ace7d127d..96cafce0d 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -87,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. diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java index 2ce546cfe..341618724 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java @@ -158,6 +158,10 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener 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 3572fd974..aabceb84d 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -101,7 +101,7 @@ public class TrackPointCreator { 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 +110,10 @@ public class TrackPointCreator { } } + public void onNewTrackPointWithoutGPS() { + onNewTrackPoint(new TrackPoint(TrackPoint.Type.SENSORPOINT, createNow()), gpsHandler.getThresholdHorizontalAccuracy()); + } + public TrackPoint createSegmentStartManual() { return TrackPoint.createSegmentStartManualWithTime(createNow()); } From f8cc25789530d254fd3d0c6ed2061b3c2cc7101c Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 3 Dec 2021 23:44:23 +0100 Subject: [PATCH 05/13] KMZ/KML: Export/Import SENSORPOINTs. Part of #500. --- .../io/file/importer/ExportImportTest.java | 98 ++++++++++++------- .../opentracks/content/data/TrackPoint.java | 2 +- .../io/file/exporter/GPXTrackExporter.java | 5 + .../io/file/exporter/KMLTrackExporter.java | 3 + .../io/file/importer/GpxTrackImporter.java | 4 +- .../io/file/importer/KmlTrackImporter.java | 8 +- .../services/TrackRecordingManager.java | 47 ++++++--- .../services/TrackRecordingService.java | 7 +- .../services/handlers/TrackPointCreator.java | 5 + .../services/sensors/AltitudeSumManager.java | 21 ++-- 10 files changed, 145 insertions(+), 55 deletions(-) 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 def9c5762..53d8fcd2b 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 @@ -115,7 +115,7 @@ public class ExportImportTest { service.getTrackPointCreator().setClock(Clock.systemUTC()); } - 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 +123,20 @@ 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); + 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 +145,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)); @@ -176,7 +188,7 @@ public class ExportImportTest { @LargeTest @Test public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException { - setUp(true); + setUp(); // given Track track = contentProviderUtils.getTrack(trackId); @@ -213,22 +225,39 @@ 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(originalTrackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), 0.01); assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01); + + assertEquals(originalTrackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01); assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); // 4. markers @@ -238,7 +267,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 +293,7 @@ public class ExportImportTest { @LargeTest @Test public void gpx() throws TimeoutException, IOException { - setUp(true); + setUp(); // given Track track = contentProviderUtils.getTrack(trackId); @@ -297,7 +326,7 @@ 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(2).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); TrackPointAssert a = new TrackPointAssert() .setDelta(0.05) @@ -316,12 +345,12 @@ public class ExportImportTest { assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime()); // Distance - assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance()); + assertEquals(Distance.of(50), importedTrackStatistics.getTotalDistance()); //TODO Should be 60 due to SensorPoint // Speed assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed()); - assertEquals(Speed.of(15), importedTrackStatistics.getAverageSpeed()); - assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed()); + assertEquals(Speed.of(12.5), importedTrackStatistics.getAverageSpeed()); + assertEquals(Speed.of(12.5), importedTrackStatistics.getAverageMovingSpeed()); // Altitude assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01); @@ -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,6 +435,24 @@ public class ExportImportTest { return sensorDataSet; } }); + } + + 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); + + AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager(); + altitudeSumManager.setAltitudeGain_m(altitudeGain); + altitudeSumManager.setAltitudeLoss_m(altitudeGain); trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET"))); trackPointCreator.getGpsHandler().onLocationChanged(location); 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 63299f078..ebc2444cc 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java @@ -311,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/io/file/exporter/GPXTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java index 91bdb95d0..adfe89005 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 @@ -151,6 +151,9 @@ public class GPXTrackExporter implements TrackExporter { wroteSegment = true; writeTrackPoint(trackPoint); break; + case SENSORPOINT: + //TODO We need somehow to compute the sensor data (mainly sensorDistance if present) and add it to the TRACKPOINT (if no segment followed in between)? + break; case TRACKPOINT: if (!wroteSegment) { // Might happen for older data (pre v3.15.0) @@ -159,6 +162,8 @@ public class GPXTrackExporter 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/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 46d18f5d8..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; } @@ -160,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()) { @@ -212,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 96cafce0d..754228cf1 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -318,7 +318,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 { @@ -458,6 +458,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/TrackPointCreator.java b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java index aabceb84d..192528e91 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -152,6 +152,11 @@ public class TrackPointCreator { return Instant.now(clock); } + @VisibleForTesting + public AltitudeSumManager getAltitudeSumManager() { + return altitudeSumManager; + } + @Deprecated @VisibleForTesting public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) { 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; From 89f9f25c55106631201cb7a8cd8a14316f7089b7 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 3 Dec 2021 23:59:42 +0100 Subject: [PATCH 06/13] GPX: export track statistics using TrackStatsExtensionv1. Fixes #652. --- .../io/file/exporter/GPXTrackExporter.java | 14 ++++++++++++++ .../opentracks/stats/TrackStatistics.java | 4 ++++ 2 files changed, 18 insertions(+) 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 adfe89005..9305f19f5 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 @@ -31,6 +31,7 @@ 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; /** @@ -205,6 +206,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" @@ -261,6 +263,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(""); } } 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. From 84c51ba8bb29686bc500d2732e92793dec310840 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sat, 4 Dec 2021 21:35:23 +0100 Subject: [PATCH 07/13] GPX: export cumulative sensor data from SENSORPOINTs. Part of #500. --- .../io/file/importer/ExportImportTest.java | 28 +++++--- .../io/file/exporter/GPXTrackExporter.java | 66 ++++++++++++++----- 2 files changed, 67 insertions(+), 27 deletions(-) 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 53d8fcd2b..da1adcaf3 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 @@ -133,6 +133,7 @@ public class ExportImportTest { // 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); @@ -255,10 +256,10 @@ public class ExportImportTest { assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01); assertEquals(originalTrackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), 0.01); - assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01); + assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01); assertEquals(originalTrackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01); - assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); + assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); // 4. markers assertMarkers(); @@ -326,6 +327,9 @@ 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(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() @@ -345,18 +349,18 @@ public class ExportImportTest { assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime()); // Distance - assertEquals(Distance.of(50), importedTrackStatistics.getTotalDistance()); //TODO Should be 60 due to SensorPoint + assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance()); // Speed assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed()); - assertEquals(Speed.of(12.5), importedTrackStatistics.getAverageSpeed()); - assertEquals(Speed.of(12.5), importedTrackStatistics.getAverageMovingSpeed()); + assertEquals(Speed.of(15), importedTrackStatistics.getAverageSpeed()); + assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed()); // 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(); @@ -437,6 +441,12 @@ public class ExportImportTest { }); } + 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(); @@ -450,9 +460,7 @@ public class ExportImportTest { location.setSpeed(speed); location.setAltitude(altitude); - AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager(); - altitudeSumManager.setAltitudeGain_m(altitudeGain); - altitudeSumManager.setAltitudeLoss_m(altitudeGain); + mockAltitudeChange(trackPointCreator, altitudeGain); trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET"))); trackPointCreator.getGpsHandler().onLocationChanged(location); 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 9305f19f5..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,7 +24,12 @@ 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; @@ -123,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(); @@ -150,10 +157,12 @@ public class GPXTrackExporter implements TrackExporter { if (wroteSegment) writeCloseSegment(); writeOpenSegment(); wroteSegment = true; - writeTrackPoint(trackPoint); + + writeTrackPoint(trackPoint, sensorPoints); + sensorPoints.clear(); break; case SENSORPOINT: - //TODO We need somehow to compute the sensor data (mainly sensorDistance if present) and add it to the TRACKPOINT (if no segment followed in between)? + sensorPoints.add(trackPoint); break; case TRACKPOINT: if (!wroteSegment) { @@ -161,13 +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. @@ -293,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(""); @@ -304,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) + "\""; } From ea5ac186d0ba5d225833439036fb38d7150ffc96 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sat, 4 Dec 2021 21:44:32 +0100 Subject: [PATCH 08/13] README: add sensor-only feature. Part of #500. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From 77f56a5376d20833af4f23e2d525a0ba9307b840 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Thu, 23 Dec 2021 21:36:05 +0100 Subject: [PATCH 09/13] Test: disable automatic creation of TrackPoints for tests. Part of #500. --- .../services/TrackRecordingServiceTest.java | 8 ++++++++ .../services/TrackRecordingServiceTestLocation.java | 7 +++++++ .../services/TrackRecordingServiceTestLooper.java | 13 +++++++------ .../TrackRecordingServiceTestStatistics.java | 1 + 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index 814f01232..92bbb6337 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java @@ -211,6 +211,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,6 +242,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); @@ -280,6 +282,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 +325,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 +356,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 +412,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( diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java index 177b437b5..fe84ec121 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java @@ -106,6 +106,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_movingAccurate() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); // when @@ -179,6 +180,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_slowMovingAccurate() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); // when @@ -224,6 +226,7 @@ public class TrackRecordingServiceTestLocation { public void testOnLocationChangedAsync_idle() { // given Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); // when @@ -269,6 +272,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 +333,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 +408,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 +480,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..5cac4f870 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java @@ -1,5 +1,10 @@ package de.dennisguse.opentracks.services; +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.ContentProvider; import android.content.Context; import android.content.SharedPreferences; @@ -31,13 +36,8 @@ 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}. @@ -195,6 +195,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..2326f7c37 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java @@ -118,6 +118,7 @@ public class TrackRecordingServiceTestStatistics { // given service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault())); Track.Id trackId = service.startNewTrack(); + service.stopUpdateRecordingData(); service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager); Function assertMovingTime = expected -> { From 9af213fd12480b2945d6016db2bfa76339631ee6 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 27 Dec 2021 08:12:52 +0100 Subject: [PATCH 10/13] Bugfix: selected interval may be null. --- .../de/dennisguse/opentracks/fragments/IntervalsFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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; From c7bbaddc06ae0ef5cbea03fd1ca9c8fdcfa473d0 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 27 Dec 2021 12:59:25 +0100 Subject: [PATCH 11/13] Cleanup: fix various issues in tests. --- .../provider/CustomContentProviderUtilsTest.java | 16 +++++++++------- .../services/TrackRecordingServiceTest.java | 7 ++----- .../TrackRecordingServiceTestLocation.java | 7 ------- .../TrackRecordingServiceTestLooper.java | 6 +----- .../TrackRecordingServiceTestStatistics.java | 5 ----- .../services/TrackRecordingService.java | 14 ++++++++++++++ 6 files changed, 26 insertions(+), 29 deletions(-) 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/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index 92bbb6337..a4851df9d 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java @@ -111,11 +111,6 @@ public class TrackRecordingServiceTest { @Before public void setUp() { - // Set up the mock content resolver - ContentProvider customContentProvider = new CustomContentProvider() { - }; - customContentProvider.attachInfo(context, null); - contentProviderUtils = new ContentProviderUtils(context); // Let's use default values. @@ -249,6 +244,7 @@ public class TrackRecordingServiceTest { // 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()); @@ -256,6 +252,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()); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java index fe84ec121..32120a42c 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,7 +30,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.content.sensor.SensorDataHeartRate; import de.dennisguse.opentracks.content.sensor.SensorDataRunning; import de.dennisguse.opentracks.content.sensor.SensorDataSet; @@ -79,11 +77,6 @@ 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(); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java index 5cac4f870..9c61a1beb 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java @@ -44,6 +44,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics; * * @author Bartlomiej Niechwiej */ +//TODO Check that those tests are really testing something! @RunWith(AndroidJUnit4.class) public class TrackRecordingServiceTestLooper { @@ -67,11 +68,6 @@ public class TrackRecordingServiceTestLooper { @Before public void setUp() { - // Set up the mock content resolver - ContentProvider customContentProvider = new CustomContentProvider() { - }; - customContentProvider.attachInfo(context, null); - contentProviderUtils = new ContentProviderUtils(context); // Let's use default values. diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java index 2326f7c37..a4d1ad74a 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java @@ -81,11 +81,6 @@ 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(); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 754228cf1..d8bcb00f8 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -161,13 +161,16 @@ public class TrackRecordingService extends Service implements TrackPointCreator. @Override public void onDestroy() { + handler.removeCallbacksAndMessages(null); //Some tests do not finish the recording completely handler = null; trackPointCreator.stop(); trackPointCreator = null; + trackRecordingManager = null; // Reverse order from onCreate showNotification(false); //TODO Why? + notificationManager = null; PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); @@ -180,6 +183,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; @@ -187,6 +191,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(); } From d6cc7a0321c0a26f2062788998c80247a22f25e6 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 27 Dec 2021 23:19:56 +0100 Subject: [PATCH 12/13] TrackPointCreator: only try fill TrackPoint with sensor data if started. --- .../opentracks/services/handlers/TrackPointCreator.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 192528e91..91fb2528d 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -58,6 +58,10 @@ 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 @@ -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); From 7d0d87e962af5fc12b2e0cf577b9c4453df3c2ff Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 27 Dec 2021 23:33:02 +0100 Subject: [PATCH 13/13] Tests: ServiceTestRule does not (always) restart the service. Thus, resetting it's status is not happening and then leading to failures in subsequent tests. Fixes #1058. --- .../io/file/importer/ExportImportTest.java | 26 +++++++-------- .../services/TrackRecordingServiceTest.java | 33 ++++++++++--------- .../TrackRecordingServiceTestLocation.java | 7 ++-- .../TrackRecordingServiceTestLooper.java | 17 ++-------- .../TrackRecordingServiceTestStatistics.java | 30 +++++------------ .../services/TrackRecordingService.java | 25 +++++++------- 6 files changed, 54 insertions(+), 84 deletions(-) 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 da1adcaf3..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,21 +99,26 @@ 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() throws TimeoutException { @@ -175,16 +181,6 @@ 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 diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index a4851df9d..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,25 +108,15 @@ public class TrackRecordingServiceTest { }; @Before - public void setUp() { + 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); @@ -466,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 32120a42c..fa0c662af 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java @@ -36,7 +36,6 @@ 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. @@ -80,16 +79,14 @@ public class TrackRecordingServiceTestLocation { 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); } diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java index 9c61a1beb..345671de5 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import android.content.ContentProvider; import android.content.Context; import android.content.SharedPreferences; import android.os.Looper; @@ -35,7 +34,6 @@ 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.settings.PreferencesUtils; import de.dennisguse.opentracks.stats.TrackStatistics; @@ -67,24 +65,15 @@ public class TrackRecordingServiceTestLooper { } @Before - public void setUp() { + 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); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStatistics.java index a4d1ad74a..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; /** @@ -82,21 +76,14 @@ public class TrackRecordingServiceTestStatistics { @Before public void setUp() throws TimeoutException { 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); } /** @@ -104,13 +91,12 @@ 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(); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index d8bcb00f8..a23e5e012 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -99,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)) { @@ -149,21 +152,13 @@ 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; @@ -172,8 +167,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator. showNotification(false); //TODO Why? notificationManager = null; - PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - try { voiceAnnouncementManager.shutdown(); } finally { @@ -435,6 +428,12 @@ public class TrackRecordingService extends Service implements TrackPointCreator. return trackPointCreator; } + @Deprecated + @VisibleForTesting + public TrackRecordingManager getTrackRecordingManager() { + return trackRecordingManager; + } + public LiveData getGpsStatusObservable() { return gpsStatusObservable; }