From 6be9d2477cfa9f3c6a3d800b291d68717f6c49ba Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 31 Jul 2023 20:07:53 +0200 Subject: [PATCH 1/7] Idle: TrackRecordingManager creates IDLE TrackPoints. Fixes of #1187. --- .../opentracks/data/model/TrackPointTest.java | 17 -- .../io/file/importer/ExportImportTest.java | 22 +- .../file/importer/GPXTrackImporterTest.java | 6 +- .../file/importer/KMLTrackImporterTest.java | 6 +- .../TrackRecordingServiceMarkerTest.java | 1 + .../TrackRecordingServiceRecordingTest.java | 243 +----------------- .../stats/TrackStatisticsUpdaterTest.java | 114 ++++---- src/androidTest/res/raw/csv_export.csv | 1 - .../opentracks/data/models/TrackPoint.java | 8 +- .../io/file/importer/KmlTrackImporter.java | 4 + .../services/TrackRecordingManager.java | 42 ++- .../services/TrackRecordingService.java | 4 +- .../services/handlers/TrackPointCreator.java | 14 +- .../stats/TrackStatisticsUpdater.java | 46 ++-- 14 files changed, 167 insertions(+), 361 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java b/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java index e90456f5a1..1885114a55 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java @@ -1,33 +1,16 @@ package de.dennisguse.opentracks.data.model; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.Test; import java.time.Instant; -import java.time.temporal.ChronoUnit; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.TrackPoint; public class TrackPointTest { - @Test - public void isRecent_true() { - TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.now()); - - assertTrue(tp.isRecent()); - } - - @Test - public void isRecent_false() { - TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.now().minus(2, ChronoUnit.MINUTES)); - - assertFalse(tp.isRecent()); - } - @Test public void distanceToPrevious() { TrackPoint tp1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0)) 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 24f1f3a4e1..ea999b1ae5 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 @@ -182,7 +182,7 @@ public void setUp() throws TimeoutException { track = contentProviderUtils.getTrack(trackId); trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); markers = contentProviderUtils.getMarkers(trackId); - assertEquals(12, trackPoints.size()); + assertEquals(11, trackPoints.size()); assertEquals(2, markers.size()); } @@ -234,21 +234,21 @@ public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOExc assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getTotalTime()); assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime()); - assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime()); + assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getMovingTime()); // Distance assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance()); - assertEquals(222238.70, importedTrackStatistics.getTotalDistance().toM(), 0.01); + assertEquals(222236.70, importedTrackStatistics.getTotalDistance().toM(), 0.01); // Speed assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed()); - assertEquals(55559.67, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(11111.83, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed()); - assertEquals(11111.93, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); + assertEquals(11111.83, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed()); - assertEquals(55559.67, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(11111.83, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); // Altitude assertEquals(originalTrackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01); @@ -347,7 +347,7 @@ public void gpx() throws TimeoutException, IOException { .setSpeed(Speed.of(5)) .setAltitudeLoss(1f) .setAltitudeGain(1f) - .setSensorDistance(Distance.of(14)) + .setSensorDistance(Distance.of(12)) .setHeartRate(69) .setPower(50f) .setCadence(3f) @@ -391,12 +391,12 @@ public void gpx() throws TimeoutException, IOException { assertEquals(Duration.ofSeconds(80), importedTrackStatistics.getMovingTime()); // Distance - assertEquals(222349.85, importedTrackStatistics.getTotalDistance().toM(), 0.01); + assertEquals(222347.85, importedTrackStatistics.getTotalDistance().toM(), 0.01); // Speed - assertEquals(2779.37, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); - assertEquals(2779.37, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); - assertEquals(2779.37, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); + assertEquals(2779.34, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(2779.34, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); + assertEquals(2779.34, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); // Altitude assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01); diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java index 09a35936ff..df1624d2f9 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java @@ -141,8 +141,8 @@ public void gpx_without_speed() throws IOException { // 3. trackstatistics TrackStatistics trackStatistics = importedTrack.getTrackStatistics(); - assertEquals(1.44, trackStatistics.getMaxSpeed().toMPS(), 0.01); - assertEquals(Duration.ofSeconds(53), trackStatistics.getMovingTime()); + assertEquals(0.75, trackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime()); // 4. trackpoints List importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId); @@ -189,7 +189,7 @@ public void gpx_speed_no_namespace() throws IOException { // 3. trackstatistics TrackStatistics trackStatistics = importedTrack.getTrackStatistics(); - assertEquals(4.0, trackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(5.0, trackStatistics.getMaxSpeed().toMPS(), 0.01); assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime()); // 4. trackpoints diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java index 64a3d16b06..228d423ece 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java @@ -186,7 +186,7 @@ public void kml22_with_statistics_marker() throws IOException { .setAltitude(439.1626281738281) .setAltitudeGain(0f) .setSpeed(Speed.of(0.1577)), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-11-28T17:06:47.888Z")) + new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-11-28T17:06:47.888Z")) .setLatitude(12.340057) .setLongitude(1.23405) .setAltitude(421.8070983886719) @@ -200,13 +200,13 @@ public void kml22_with_statistics_marker() throws IOException { .setAltitude(419.93902587890625) .setAltitudeGain(0f) .setSpeed(Speed.of(0)), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-11-28T17:06:56.905Z")) + new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-11-28T17:06:56.905Z")) .setLatitude(12.340057) .setLongitude(1.23405) .setAltitude(419.9036560058594) .setAltitudeGain(0f) .setSpeed(Speed.of(0)), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-11-28T17:07:20.870Z")) + new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-11-28T17:07:20.870Z")) .setLatitude(12.340082) .setLongitude(1.234046) .setAltitude(417.99432373046875) diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java index a1037d278c..5964089c12 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java @@ -146,6 +146,7 @@ public void recording_GPSfix_createsMarker() { assertEquals(0.0, wpt.getLength().toM(), 0.01); assertNotNull(wpt.getLocation()); + trackPointCreator.setClock("2020-02-02T02:02:04Z"); service.endCurrentTrack(); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java index 2ecc7c8db4..1b82e80fdf 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java @@ -122,7 +122,7 @@ public void recording_startStop() { service.endCurrentTrack(); // then - assertEquals(new TrackStatistics(startTime, stopTime, 0, 1, 0, 0, 0f, 0f) + assertEquals(new TrackStatistics(startTime, stopTime, 0, 1, 1, 0, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); new TrackPointAssert().assertEquals(List.of( @@ -150,7 +150,7 @@ public void testRecording_startPauseResume() { service.endCurrentTrack(); // then - assertEquals(new TrackStatistics(startTime, pauseTime, 0, 1, 0, 0, 0f, 0f) + assertEquals(new TrackStatistics(startTime, pauseTime, 0, 1, 1, 0, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); new TrackPointAssert().assertEquals(List.of( @@ -166,7 +166,7 @@ public void testRecording_startPauseResume() { service.resumeTrack(trackId); // then - assertEquals(new TrackStatistics(startTime, resumeTime, 0, 1, 0, 0, 0f, 0f) + assertEquals(new TrackStatistics(startTime, resumeTime, 0, 1, 1, 0, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); new TrackPointAssert().assertEquals(List.of( @@ -299,7 +299,7 @@ public void testRecording_gpsOnly_recordingDistance_above() { TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15); // then - assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f) + assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 1, 15, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); // when @@ -307,7 +307,7 @@ public void testRecording_gpsOnly_recordingDistance_above() { TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0001, 35.0, 1, 15); // then - assertEquals(new TrackStatistics(startTime, gps2, 11.113178253173828f, 4, 3, 15, 0f, 0f) + assertEquals(new TrackStatistics(startTime, gps2, 11.113178253173828f, 4, 4, 15, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); // when @@ -315,7 +315,7 @@ public void testRecording_gpsOnly_recordingDistance_above() { TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0002, 35.0, 1, 15); // then - assertEquals(new TrackStatistics(startTime, gps3, 22.226356506347656, 6, 5, 15, 0f, 0f) + assertEquals(new TrackStatistics(startTime, gps3, 22.226356506347656, 6, 6, 15, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); @@ -325,7 +325,7 @@ public void testRecording_gpsOnly_recordingDistance_above() { service.endCurrentTrack(); // then - assertEquals(new TrackStatistics(startTime, stopTime, 22.226356506347656, 10, 5, 15, 0f, 0f) + assertEquals(new TrackStatistics(startTime, stopTime, 22.226356506347656, 10, 10, 15, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); new TrackPointAssert().assertEquals(List.of( @@ -357,71 +357,6 @@ public void testRecording_gpsOnly_recordingDistance_above() { ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); } - @MediumTest - @Test - public void testRecording_gpsOnly_recordingDistance_above_speed_0() { - // given - String startTime = "2020-02-02T02:02:02Z"; - TrackPointCreator trackPointCreator = service.getTrackPointCreator(); - trackPointCreator.setClock(startTime); - Track.Id trackId = service.startNewTrack(); - trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager); - - // when - String gps1 = "2020-02-02T02:02:03Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 0); - - // then - TrackStatistics gps1statistics = new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f); - assertEquals(gps1statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String gps2 = "2020-02-02T02:02:06Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0001, 35.0, 1, 0); - - // then - assertEquals(gps1statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String gps3 = "2020-02-02T02:02:08Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0002, 35.0, 1, 0); - - - // then - assertEquals(gps1statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String stopTime = "2020-02-02T02:02:12Z"; - trackPointCreator.setClock(stopTime); - service.endCurrentTrack(); - - - // then - assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f) - , contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - new TrackPointAssert().assertEquals(List.of( - new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1)) - .setLatitude(45) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(0)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3)) - .setLatitude(45.0001) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(0)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f) - ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); - } - @MediumTest @Test public void testRecording_gpsOnly_recordingDistance_below() { @@ -437,7 +372,7 @@ public void testRecording_gpsOnly_recordingDistance_below() { TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15); // then - TrackStatistics gps1Statistics = new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f); + TrackStatistics gps1Statistics = new TrackStatistics(startTime, gps1, 0, 1, 1, 15, 0f, 0f); assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); // when @@ -461,7 +396,7 @@ public void testRecording_gpsOnly_recordingDistance_below() { service.endCurrentTrack(); // then - assertEquals(new TrackStatistics(startTime, stopTime, 2.222635507583618, 10, 5, 15, 0f, 0f) + assertEquals(new TrackStatistics(startTime, stopTime, 2.222635507583618, 10, 10, 15, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); new TrackPointAssert().assertEquals(List.of( @@ -486,158 +421,6 @@ public void testRecording_gpsOnly_recordingDistance_below() { ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); } - @MediumTest - @Test - public void testRecording_gpsOnly_recordingDistance_idle() { - // given - String startTime = "2020-02-02T02:02:02Z"; - TrackPointCreator trackPointCreator = service.getTrackPointCreator(); - trackPointCreator.setClock(startTime); - Track.Id trackId = service.startNewTrack(); - trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager); - - // when - String gps1 = "2020-02-02T02:02:03Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 0); - - // then - TrackStatistics gps1Statistics = new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f); - assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String gps2 = "2020-02-02T02:02:06Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 1, 0); - - // then - assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String gps3 = "2020-02-02T02:02:08Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0, 35.0, 1, 0); - - // then - assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - - // when - String stopTime = "2020-02-02T02:02:12Z"; - trackPointCreator.setClock(stopTime); - service.endCurrentTrack(); - - // then - assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f) - , contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - new TrackPointAssert().assertEquals(List.of( - new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1)) - .setLatitude(45) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(0)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3)) - .setLatitude(45.00002) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(0)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f) - ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); - } - - @MediumTest - @Test - public void testRecording_gpsOnly_recordingDistance_idle_movement() { - // given - String startTime = "2020-02-02T02:02:02Z"; - TrackPointCreator trackPointCreator = service.getTrackPointCreator(); - trackPointCreator.setClock(startTime); - Track.Id trackId = service.startNewTrack(); - trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager); - - // when - String gps1 = "2020-02-02T02:02:03Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15); - - // then - assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f) - , contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String gps2 = "2020-02-02T02:02:06Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 1, 0); - - // then - final TrackStatistics gps2statistics = new TrackStatistics(startTime, gps2, 0, 4, 0, 0, 0f, 0f); - assertEquals(gps2statistics - , contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String gps3 = "2020-02-02T02:02:08Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0, 35.0, 1, 0); - - // then - assertEquals(gps2statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - - // when - String gps4 = "2020-02-02T02:02:10Z"; - TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps4, 45.0, 35.0, 1, 15); - - // then - assertEquals(new TrackStatistics(startTime, gps4, 0, 8, 0, 0, 0f, 0f) - , contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - // when - String stopTime = "2020-02-02T02:02:12Z"; - trackPointCreator.setClock(stopTime); - service.endCurrentTrack(); - - // then - assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f) - , contentProviderUtils.getTrack(trackId).getTrackStatistics()); - - new TrackPointAssert().assertEquals(List.of( - new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1)) - .setLatitude(45) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(15)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps2)) - .setLatitude(45) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(0)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3)) - .setLatitude(45) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(0)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps4)) - .setLatitude(45) - .setLongitude(35) - .setHorizontalAccuracy(Distance.of(1)) - .setSpeed(Speed.of(15)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f), - new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) - .setAltitudeGain(0f) - .setAltitudeLoss(0f) - ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); - } - @MediumTest @Test public void testRecording_gpsOnly_recordingDistance_movement_non_idle() { @@ -721,7 +504,7 @@ public void testRecording_gpsOnly_ignore_inaccurate() { service.endCurrentTrack(); // then - assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f) + assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 10, 0, 0f, 0f) , contentProviderUtils.getTrack(trackId).getTrackStatistics()); @@ -749,14 +532,14 @@ public void testRecording_gpsOnly_segment() { TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15); // then - assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f), contentProviderUtils.getTrack(trackId).getTrackStatistics()); + assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 1, 15, 0f, 0f), contentProviderUtils.getTrack(trackId).getTrackStatistics()); // when String gps2 = "2020-02-02T02:02:06Z"; TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.1, 35.0, 1, 15); // then - assertEquals(new TrackStatistics(startTime, gps2, 11113.275390625, 4, 3, 3704.4251302083335f, 0f, 0f).toString(), contentProviderUtils.getTrack(trackId).getTrackStatistics().toString()); + assertEquals(new TrackStatistics(startTime, gps2, 11113.275390625, 4, 4, 2778.31884765625f, 0f, 0f).toString(), contentProviderUtils.getTrack(trackId).getTrackStatistics().toString()); // when @@ -765,7 +548,7 @@ public void testRecording_gpsOnly_segment() { service.endCurrentTrack(); // then - assertEquals(new TrackStatistics(startTime, stopTime, 11113.275390625, 10, 3, 3704.4251302083335f, 0f, 0f).toString(), contentProviderUtils.getTrack(trackId).getTrackStatistics().toString()); + assertEquals(new TrackStatistics(startTime, stopTime, 11113.275390625, 10, 10, 1111.3275390625f, 0f, 0f).toString(), contentProviderUtils.getTrack(trackId).getTrackStatistics().toString()); // then diff --git a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java index 4c797f2c0f..e2cf1e7592 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java @@ -83,35 +83,17 @@ public void addTrackPoint_TestingTrack() { TrackStatistics statistics = subject.getTrackStatistics(); assertEquals(142.26, statistics.getTotalDistance().toM(), 0.01); assertEquals(Duration.ofSeconds(12), statistics.getTotalTime()); - assertEquals(Duration.ofSeconds(10), statistics.getMovingTime()); + assertEquals(Duration.ofSeconds(12), statistics.getMovingTime()); assertEquals(2.5, statistics.getMinAltitude(), 0.01); assertEquals(32.5, statistics.getMaxAltitude(), 0.01); assertEquals(36, statistics.getTotalAltitudeGain(), 0.01); assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01); - assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01); - assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01); + assertEquals(11.85, statistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(11.85, statistics.getAverageMovingSpeed().toMPS(), 0.01); assertEquals(11.85, statistics.getAverageSpeed().toMPS(), 0.01); - assertEquals(106.64f, statistics.getAverageHeartRate().getBPM(), 0.01); - } - - @Test - public void addTrackPoint_distance_from_GPS_not_moving() { - // given - TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); - - TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); - TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000)); - TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000)); - - // when - subject.addTrackPoint(tp1); - subject.addTrackPoint(tp2); - subject.addTrackPoint(tp3); - - // then - assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); + assertEquals(106.834f, statistics.getAverageHeartRate().getBPM(), 0.01); } @Test @@ -165,35 +147,6 @@ public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() { assertEquals(125.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); } - @Test - public void addTrackPoint_distance_from_GPS_not_moving_and_sensor_moving() { - // given - TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); - - TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); - TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000)); - TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000)); - TrackPoint tp4 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000)); - tp4.setSensorDistance(Distance.of(5f)); - TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); - tp5.setSensorDistance(Distance.of(10f)); - - // when - subject.addTrackPoint(tp1); - subject.addTrackPoint(tp2); - subject.addTrackPoint(tp3); - - // then - assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); - - // when - subject.addTrackPoint(tp4); - subject.addTrackPoint(tp5); - - // then - assertEquals(15, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); - } - @Test public void addTrackPoint_distance_from_GPS_moving_and_sensor_disconnecting() { // given @@ -290,6 +243,64 @@ public void addTrackPoint_maxSpeed_multiple_segments() { assertEquals(Speed.of(2f), subject.getTrackStatistics().getMaxSpeed()); } + @Test + public void addTrackPoint_idle_withoutDistance() { + TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); + + // when + subject.addTrackPoints(List.of( + new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)), + new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1)) + .setSpeed(Speed.of(2f)), + new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2)) + .setSpeed(Speed.of(2f)), + + new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40)) + .setHeartRate(50), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45)) + .setHeartRate(50), + new TrackPoint(0, 1, Altitude.WGS84.of(0), Instant.ofEpochSecond(50)), + new TrackPoint(0, 2, Altitude.WGS84.of(0), Instant.ofEpochSecond(55)), + + new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(60)) + )); + + // then + assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getMovingTime()); + } + + @Test + public void addTrackPoint_idle_withDistance() { + TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); + + // when + subject.addTrackPoints(List.of( + new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)), + new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1)) + .setSensorDistance(Distance.of(10)), + new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2)) + .setSensorDistance(Distance.of(10)), + + new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)) + .setSensorDistance(Distance.ofKilometer(1)), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40)) + .setHeartRate(50), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45)) + .setHeartRate(50), + new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(50)) + .setSensorDistance(Distance.of(10)), + new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(55)) + .setSensorDistance(Distance.of(10)), + + new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(60)) + )); + + // then + assertEquals(Duration.ofSeconds(45), subject.getTrackStatistics().getMovingTime()); + assertEquals(Distance.of(1040), subject.getTrackStatistics().getTotalDistance()); + } + @Test public void copy_constructor() { // given @@ -314,7 +325,6 @@ public void copy_constructor() { subject.addTrackPoint(tp5); copy.addTrackPoint(tp5); - // then assertEquals(55.287, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); assertEquals(55.287, copy.getTrackStatistics().getTotalDistance().toM(), 0.01); diff --git a/src/androidTest/res/raw/csv_export.csv b/src/androidTest/res/raw/csv_export.csv index bc32fdb45c..69c0645733 100644 --- a/src/androidTest/res/raw/csv_export.csv +++ b/src/androidTest/res/raw/csv_export.csv @@ -3,7 +3,6 @@ "2020-02-02T03:02:03+01:00","TRACKPOINT",3,14,10,10,,54,1,1,,,, "2020-02-02T03:02:04+01:00","TRACKPOINT",,,,,,54,1,1,10,66,3,50 "2020-02-02T03:02:15+01:00","TRACKPOINT",,,,,,,,,,68,3,50 -"2020-02-02T03:02:16+01:00","TRACKPOINT",,,,,,18,,,2,69,3,50 "2020-02-02T03:02:17+01:00","TRACKPOINT",3,14.001,10,10,,18,0,0,2,69,3,50 "2020-02-02T03:02:18+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,, "2020-02-02T03:03:20+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,, diff --git a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java index 9e9a31bc6e..6ed2262dae 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java @@ -287,11 +287,6 @@ public TrackPoint setSpeed(Speed speed) { this.speed = speed; return this; } - - public boolean isMoving() { - return hasSpeed() && getSpeed().isMoving(); - } - public boolean hasBearing() { return bearing != null; } @@ -352,9 +347,8 @@ public boolean fulfillsAccuracy(Distance thresholdHorizontalAccuracy) { return hasHorizontalAccuracy() && horizontalAccuracy.lessThan(thresholdHorizontalAccuracy); } - //TODO Bearing requires a location; what do we do if we don't have any? public float bearingTo(@NonNull TrackPoint dest) { - return getLocation().bearingTo(dest.getLocation()); + return bearingTo(dest.getLocation()); } //TODO Bearing requires a location; what do we do if we don't have any? 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 9dce6b4070..fffc1f86fe 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 @@ -315,6 +315,10 @@ private void onTrackSegmentEnd() { if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) { trackPoint.setSpeed(Speed.of(sensorSpeedList.get(i))); + + if (TrackPoint.IDLE_SPEED.greaterOrEqualThan(trackPoint.getSpeed())) { + trackPoint.setType(TrackPoint.Type.IDLE); + } } if (i < sensorDistanceList.size() && sensorDistanceList.get(i) != null) { trackPoint.setSensorDistance(Distance.of(sensorDistanceList.get(i))); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index 292b7e5d18..7f77066999 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -5,6 +5,7 @@ import android.content.SharedPreferences; import android.database.sqlite.SQLiteException; import android.net.Uri; +import android.os.Handler; import android.util.Log; import android.util.Pair; @@ -32,11 +33,17 @@ class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChang private static final String TAG = TrackRecordingManager.class.getSimpleName(); + private static final Duration IDLE_TIMEOUT = Duration.ofSeconds(30); + private static final AltitudeCorrectionManager ALTITUDE_CORRECTION_MANAGER = new AltitudeCorrectionManager(); + private final Runnable ON_IDLE = this::onIdle; + private final ContentProviderUtils contentProviderUtils; private final Context context; + private final Handler handler; + private final TrackPointCreator trackPointCreator; private Distance recordingDistanceInterval; @@ -52,9 +59,10 @@ class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChang private TrackPoint lastStoredTrackPoint; private TrackPoint lastStoredTrackPointWithLocation; - TrackRecordingManager(Context context, TrackPointCreator trackPointCreator) { + TrackRecordingManager(Context context, TrackPointCreator trackPointCreator, Handler handler) { this.context = context; this.trackPointCreator = trackPointCreator; + this.handler = handler; contentProviderUtils = new ContentProviderUtils(context); } @@ -154,16 +162,27 @@ public Marker.Id insertMarker(String name, String category, String description, return new Marker.Id(ContentUris.parseId(uri)); } + void onIdle() { + Log.d(TAG, "Becoming idle"); + onNewTrackPoint(trackPointCreator.createIdle()); + } + /** * @return TrackPoint was stored? */ - boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { + synchronized boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { if (trackPoint.hasSpeed()) { lastTrackPointUIWithSpeed = trackPoint; } if (trackPoint.hasAltitude()) { lastTrackPointUIWithAltitude = trackPoint; } + + if (trackPoint.getType() == TrackPoint.Type.IDLE) { + insertTrackPoint(trackPoint, true); + handler.removeCallbacks(ON_IDLE); + return true; + } //Storing trackPoint // Always insert the first segment location @@ -184,10 +203,10 @@ boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { if (!shouldStore) { Log.d(TAG, "Ignoring TrackPoint as it has no distance (and sensor data is not new enough)."); return false; - } else { - insertTrackPoint(trackPoint, true); - return true; } + + insertTrackPoint(trackPoint, true); + return true; } Distance distanceToLastStoredTrackPoint; @@ -200,18 +219,17 @@ boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) { trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); insertTrackPoint(trackPoint, true); + + handler.removeCallbacks(ON_IDLE); + handler.postDelayed(ON_IDLE, IDLE_TIMEOUT.toMillis()); return true; } - if (distanceToLastStoredTrackPoint.greaterOrEqualThan(recordingDistanceInterval) - && trackPoint.isMoving()) { + if (distanceToLastStoredTrackPoint.greaterOrEqualThan(recordingDistanceInterval)) { insertTrackPoint(trackPoint, false); - return true; - } - if (trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) { - // Moving from non-moving to moving or vice versa; required to compute moving time correctly. - insertTrackPoint(trackPoint, true); + handler.removeCallbacks(ON_IDLE); + handler.postDelayed(ON_IDLE, IDLE_TIMEOUT.toMillis()); return true; } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 171ce30777..1255a489cc 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -107,8 +107,8 @@ public void onCreate() { gpsStatusObservable = new MutableLiveData<>(STATUS_GPS_DEFAULT); recordingDataObservable = new MutableLiveData<>(NOT_RECORDING); - trackPointCreator = new TrackPointCreator(this, this, handler); - trackRecordingManager = new TrackRecordingManager(this, trackPointCreator); + trackPointCreator = new TrackPointCreator(this); + trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, handler); voiceAnnouncementManager = new VoiceAnnouncementManager(this); notificationManager = new TrackRecordingServiceNotificationManager(this); 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 382ad2b95c..49384b2919 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -36,16 +36,11 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh private Clock clock = new MonotonicClock(); private SensorManager sensorManager; - public TrackPointCreator(Callback service, Context context, Handler handler) { + public TrackPointCreator(Callback service) { this.service = service; this.sensorManager = new SensorManager(this); } - @VisibleForTesting - TrackPointCreator(Callback service) { - this.service = service; - } - public synchronized void start(@NonNull Context context, @NonNull Handler handler) { this.context = context; @@ -106,6 +101,13 @@ public synchronized TrackPoint createSegmentEnd() { return segmentEnd; } + public synchronized TrackPoint createIdle() { + TrackPoint idle = new TrackPoint(TrackPoint.Type.IDLE, createNow()); + addSensorData(idle); + reset(); + return idle; + } + public Pair createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) { TrackPoint currentTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, createNow()); diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java index 6a7f6dfe23..05e9baa72a 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java @@ -56,6 +56,8 @@ public class TrackStatisticsUpdater { // Current segment's last trackPoint private TrackPoint lastTrackPoint; + private boolean idle; + public TrackStatisticsUpdater() { this(new TrackStatistics()); } @@ -77,6 +79,7 @@ public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) { this.trackStatistics = new TrackStatistics(toCopy.trackStatistics); this.lastTrackPoint = toCopy.lastTrackPoint; + this.idle = idle; resetAverageHeartRate(); } @@ -132,26 +135,35 @@ public void addTrackPoint(TrackPoint trackPoint) { currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM)); } - // Update total distance - if (trackPoint.hasSensorDistance()) { - // Sensor-based distance/speed - currentSegment.addTotalDistance(trackPoint.getSensorDistance()); - } else if (lastTrackPoint != null - && lastTrackPoint.hasLocation() - && trackPoint.hasLocation() && trackPoint.isMoving()) { - // GPS-based distance/speed - // Assumption: we ignore TrackPoints that are not moving as those are likely imprecise GPS measurements - Distance movingDistance = trackPoint.distanceToPrevious(lastTrackPoint); - currentSegment.addTotalDistance(movingDistance); - } + { + // Update total distance + Distance movingDistance = null; + if (trackPoint.hasSensorDistance()) { + movingDistance = trackPoint.getSensorDistance(); + } else if (lastTrackPoint != null + && lastTrackPoint.hasLocation() + && trackPoint.hasLocation()) { + // GPS-based distance/speed + movingDistance = trackPoint.distanceToPrevious(lastTrackPoint); + } + if (movingDistance != null) { + idle = false; + currentSegment.addTotalDistance(movingDistance); + } + if (!idle && !trackPoint.isSegmentManualStart()) { + if (lastTrackPoint != null) { + currentSegment.addMovingTime(trackPoint, lastTrackPoint); + } + } - // Update moving time - if (trackPoint.isMoving() && lastTrackPoint != null && lastTrackPoint.isMoving()) { - currentSegment.addMovingTime(trackPoint, lastTrackPoint); + if (trackPoint.getType() == TrackPoint.Type.IDLE) { + idle = true; + } - // Update max speed - updateSpeed(trackPoint, lastTrackPoint); + if (trackPoint.hasSpeed()) { + updateSpeed(trackPoint); + } } if (trackPoint.isSegmentManualEnd()) { From 00debe2eb5369c02c6cc88bee6c47c86209b1909 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Thu, 3 Aug 2023 19:21:05 +0200 Subject: [PATCH 2/7] Idle: TrackRecordingManager creates IDLE TrackPoints (import/export GPX/KML). Fixes of #1187. --- .../file/exporter/KmlTrackExporterTest.java | 59 ------------------- .../io/file/importer/ExportImportTest.java | 38 ++++++------ .../file/importer/KMLTrackImporterTest.java | 6 +- src/androidTest/res/raw/csv_export.csv | 5 +- .../opentracks/data/models/TrackPoint.java | 1 + .../io/file/exporter/GPXTrackExporter.java | 3 + .../io/file/exporter/KMLTrackExporter.java | 55 +++++++++-------- .../io/file/importer/KmlTrackImporter.java | 19 ++++-- .../services/TrackRecordingManager.java | 6 +- .../stats/TrackStatisticsUpdater.java | 3 +- 10 files changed, 80 insertions(+), 115 deletions(-) delete mode 100644 src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java deleted file mode 100644 index a5d96614dc..0000000000 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.dennisguse.opentracks.io.file.exporter; - -import static org.junit.Assert.assertEquals; - -import android.content.Context; - -import androidx.test.core.app.ApplicationProvider; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.ByteArrayOutputStream; -import java.time.Instant; -import java.time.ZoneOffset; - -import de.dennisguse.opentracks.data.models.TrackPoint; -import de.dennisguse.opentracks.io.file.TrackFileFormat; - -@RunWith(JUnit4.class) -public class KmlTrackExporterTest { - - private final Context context = ApplicationProvider.getApplicationContext(); - - /** - * Sensor data by type should only be created if present in at least on TrackPoint. - */ - @Test - public void writeCloseSegment_only_write_sensordata_if_present() { - String expected = """ - 1970-01-01T00:00:00Z - - 1970-01-01T01:00:00+01:00 - - - - - - - """; - - // given - TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context, null); - kmlTrackWriter.prepare(outputStream); - - kmlTrackWriter.writeTrackPoint(ZoneOffset.UTC, trackPoint); - kmlTrackWriter.writeTrackPoint(ZoneOffset.ofTotalSeconds(3600), trackPoint); - - // when - kmlTrackWriter.writeCloseSegment(); - kmlTrackWriter.close(); - - // then - assertEquals(expected, outputStream.toString()); - } -} \ No newline at end of file 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 ea999b1ae5..2a411518d2 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 @@ -168,9 +168,12 @@ public void setUp() throws TimeoutException { sendLocation(trackPointCreator, "2020-02-02T02:03:22Z", 3, 16, 10, 13, 15, 10, 0); - sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0); + trackPointCreator.setClock("2020-02-02T02:03:30Z"); + service.getTrackRecordingManager().onIdle(); - trackPointCreator.setClock("2020-02-02T02:03:24Z"); + sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3, 16.001, 10, 27, 15, 10, 0); + + trackPointCreator.setClock("2020-02-02T02:04:00Z"); service.endCurrentTrack(); Track track = contentProviderUtils.getTrack(trackId); @@ -182,7 +185,7 @@ public void setUp() throws TimeoutException { track = contentProviderUtils.getTrack(trackId); trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); markers = contentProviderUtils.getMarkers(trackId); - assertEquals(11, trackPoints.size()); + assertEquals(12, trackPoints.size()); assertEquals(2, markers.size()); } @@ -226,29 +229,29 @@ public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOExc // Time assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset()); assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime()); - assertEquals(Instant.parse("2020-02-02T02:03:24Z"), importedTrackStatistics.getStopTime()); + assertEquals(Instant.parse("2020-02-02T02:04:00Z"), importedTrackStatistics.getStopTime()); TrackStatistics originalTrackStatistics = track.getTrackStatistics(); assertEquals(originalTrackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime()); - assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getTotalTime()); + assertEquals(Duration.ofSeconds(56), importedTrackStatistics.getTotalTime()); assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime()); - assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getMovingTime()); + assertEquals(Duration.ofSeconds(26), importedTrackStatistics.getMovingTime()); //TODO Likely too low // Distance assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance()); - assertEquals(222236.70, importedTrackStatistics.getTotalDistance().toM(), 0.01); + assertEquals(222125.53125, importedTrackStatistics.getTotalDistance().toM(), 0.01); //TODO Too low // Speed assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed()); - assertEquals(11111.83, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(8543.29, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed()); - assertEquals(11111.83, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); + assertEquals(3966.52, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed()); - assertEquals(11111.83, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); + assertEquals(8543.28, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); // Altitude assertEquals(originalTrackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01); @@ -368,7 +371,7 @@ public void gpx() throws TimeoutException, IOException { .setAltitudeGain(0f) .setSpeed(Speed.of(15)) .setHorizontalAccuracy(Distance.of(10)), - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:03:23Z")) + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:03:50Z")) .setLatitude(3) .setLongitude(16.001) .setAltitude(10) @@ -379,24 +382,23 @@ public void gpx() throws TimeoutException, IOException { ), actual); // 3. trackstatistics - TrackStatistics trackStatistics = track.getTrackStatistics(); TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics(); // Time assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset()); assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime()); - assertEquals(Instant.parse("2020-02-02T02:03:23Z"), importedTrackStatistics.getStopTime()); + assertEquals(Instant.parse("2020-02-02T02:03:50Z"), importedTrackStatistics.getStopTime()); - assertEquals(Duration.ofSeconds(80), importedTrackStatistics.getTotalTime()); - assertEquals(Duration.ofSeconds(80), importedTrackStatistics.getMovingTime()); + assertEquals(Duration.ofSeconds(107), importedTrackStatistics.getTotalTime()); + assertEquals(Duration.ofSeconds(107), importedTrackStatistics.getMovingTime()); // Distance assertEquals(222347.85, importedTrackStatistics.getTotalDistance().toM(), 0.01); // Speed - assertEquals(2779.34, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); - assertEquals(2779.34, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); - assertEquals(2779.34, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); + assertEquals(2078.01, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(2078.01, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); + assertEquals(2078.01, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); // Altitude assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01); diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java index 228d423ece..64a3d16b06 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java @@ -186,7 +186,7 @@ public void kml22_with_statistics_marker() throws IOException { .setAltitude(439.1626281738281) .setAltitudeGain(0f) .setSpeed(Speed.of(0.1577)), - new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-11-28T17:06:47.888Z")) + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-11-28T17:06:47.888Z")) .setLatitude(12.340057) .setLongitude(1.23405) .setAltitude(421.8070983886719) @@ -200,13 +200,13 @@ public void kml22_with_statistics_marker() throws IOException { .setAltitude(419.93902587890625) .setAltitudeGain(0f) .setSpeed(Speed.of(0)), - new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-11-28T17:06:56.905Z")) + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-11-28T17:06:56.905Z")) .setLatitude(12.340057) .setLongitude(1.23405) .setAltitude(419.9036560058594) .setAltitudeGain(0f) .setSpeed(Speed.of(0)), - new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-11-28T17:07:20.870Z")) + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-11-28T17:07:20.870Z")) .setLatitude(12.340082) .setLongitude(1.234046) .setAltitude(417.99432373046875) diff --git a/src/androidTest/res/raw/csv_export.csv b/src/androidTest/res/raw/csv_export.csv index 69c0645733..cdefb81a11 100644 --- a/src/androidTest/res/raw/csv_export.csv +++ b/src/androidTest/res/raw/csv_export.csv @@ -8,5 +8,6 @@ "2020-02-02T03:03:20+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,, "2020-02-02T03:03:21+01:00","TRACKPOINT",3,14.002,10,10,,54,0,0,,,, "2020-02-02T03:03:22+01:00","SEGMENT_START_AUTOMATIC",3,16,10,10,,54,0,0,,,, -"2020-02-02T03:03:23+01:00","TRACKPOINT",3,16.001,10,10,,54,0,0,,,, -"2020-02-02T03:03:24+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,, \ No newline at end of file +"2020-02-02T03:03:30+01:00","IDLE",,,,,,,,,,,, +"2020-02-02T03:03:50+01:00","TRACKPOINT",3,16.001,10,10,,54,0,0,,,, +"2020-02-02T03:04:00+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,, \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java index 6ed2262dae..af6049ae91 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java @@ -66,6 +66,7 @@ public enum Type { public final int type_db; + Type(int type_db) { this.type_db = type_db; } 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 a4de84eab6..7a7362a051 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 @@ -171,6 +171,9 @@ private void writeTrackPoints(Track track) throws InterruptedException { sensorPoints.add(trackPoint); } } + case IDLE -> { + // Not supported as IDLE-TrackPoints have no location. + } 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 3de5f615a7..7ba3723237 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 @@ -61,6 +61,7 @@ public class KMLTrackExporter implements TrackExporter { public static final String EXTENDED_DATA_TYPE_ACTIVITYTYPE = "type"; + public static final String EXTENDED_DATA_TYPE_TRACKPOINT = "trackpoint_type"; public static final String EXTENDED_DATA_TYPE_SPEED = "speed"; public static final String EXTENDED_DATA_TYPE_DISTANCE = "distance"; public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence"; @@ -83,6 +84,9 @@ public class KMLTrackExporter implements TrackExporter { private final ContentProviderUtils contentProviderUtils; private PrintWriter printWriter; + + private ArrayList trackpointTypeList = new ArrayList<>(); + private final List speedList = new ArrayList<>(); private final List distanceList = new ArrayList<>(); private final List powerList = new ArrayList<>(); @@ -177,12 +181,13 @@ private void writeLocations(Track track) throws InterruptedException { writeCloseSegment(); wroteSegment = false; } - case TRACKPOINT -> { + case TRACKPOINT, IDLE -> { if (!wroteSegment) { // Might happen for older data (pre v3.15.0) writeOpenSegment(); wroteSegment = true; } + writeTrackPoint(track.getZoneOffset(), trackPoint); } default -> @@ -309,6 +314,7 @@ private void writeEndTrack() { @VisibleForTesting void writeOpenSegment() { printWriter.println(""); + trackpointTypeList.clear(); speedList.clear(); distanceList.clear(); powerList.clear(); @@ -324,32 +330,35 @@ void writeOpenSegment() { void writeCloseSegment() { printWriter.println(""); printWriter.println(""); + + writeTrackPointType(trackpointTypeList); + if (speedList.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED); + writeSimpleArraySensorData(speedList, EXTENDED_DATA_TYPE_SPEED); } if (distanceList.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(distanceList, EXTENDED_DATA_TYPE_DISTANCE); + writeSimpleArraySensorData(distanceList, EXTENDED_DATA_TYPE_DISTANCE); } if (powerList.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER); + writeSimpleArraySensorData(powerList, EXTENDED_DATA_TYPE_POWER); } if (cadenceList.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(cadenceList, EXTENDED_DATA_TYPE_CADENCE); + writeSimpleArraySensorData(cadenceList, EXTENDED_DATA_TYPE_CADENCE); } if (heartRateList.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE); + writeSimpleArraySensorData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE); } if (altitudeGainList.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(altitudeGainList, EXTENDED_DATA_TYPE_ALTITUDE_GAIN); + writeSimpleArraySensorData(altitudeGainList, EXTENDED_DATA_TYPE_ALTITUDE_GAIN); } if (altitudeLossList.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(altitudeLossList, EXTENDED_DATA_TYPE_ALTITUDE_LOSS); + writeSimpleArraySensorData(altitudeLossList, EXTENDED_DATA_TYPE_ALTITUDE_LOSS); } if (accuracyHorizontal.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(accuracyHorizontal, EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL); + writeSimpleArraySensorData(accuracyHorizontal, EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL); } if (accuracyVertical.stream().anyMatch(Objects::nonNull)) { - writeSimpleArrayData(accuracyVertical, EXTENDED_DATA_TYPE_ACCURACY_VERTICAL); + writeSimpleArraySensorData(accuracyVertical, EXTENDED_DATA_TYPE_ACCURACY_VERTICAL); } printWriter.println(""); printWriter.println(""); @@ -360,6 +369,8 @@ void writeCloseSegment() { void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) { printWriter.println("" + getTime(zoneOffset, trackPoint.getLocation()) + ""); + trackpointTypeList.add(trackPoint.getType()); + if (trackPoint.hasLocation()) { printWriter.println("" + getCoordinates(trackPoint.getLocation(), " ") + ""); } else { @@ -378,13 +389,7 @@ void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) { accuracyVertical.add(trackPoint.hasVerticalAccuracy() ? (float) trackPoint.getVerticalAccuracy().toM() : null); } - /** - * Writes the simple array data. - * - * @param list a list of simple array data - * @param name the name of the simple array data - */ - private void writeSimpleArrayData(List list, String name) { + private void writeSimpleArraySensorData(List list, String name) { printWriter.println(""); for (int i = 0; i < list.size(); i++) { Float value = list.get(i); @@ -397,14 +402,14 @@ private void writeSimpleArrayData(List list, String name) { printWriter.println(""); } - /** - * Writes a placemark. - * - * @param name the name - * @param activityType the activityType - * @param description the description - * @param location the location - */ + private void writeTrackPointType(List list) { + printWriter.println(""); + for (TrackPoint.Type value : list) { + printWriter.println("" + value.name() + ""); + } + printWriter.println(""); + } + private void writePlacemark(String name, String activityType, String description, Location location, ZoneOffset zoneOffset) { if (location != null) { printWriter.println(""); 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 fffc1f86fe..83dc1d51cf 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 @@ -94,7 +94,9 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac private final ArrayList whenList = new ArrayList<>(); private final ArrayList locationList = new ArrayList<>(); - private String dataType; + private String dataType; //Could be converted to an ENUM + + private final ArrayList trackpointTypeList = new ArrayList<>(); private final ArrayList sensorSpeedList = new ArrayList<>(); private final ArrayList sensorDistanceList = new ArrayList<>(); private final ArrayList sensorCadenceList = new ArrayList<>(); @@ -287,6 +289,7 @@ private void onTrackSegmentStart() { locationList.clear(); whenList.clear(); + trackpointTypeList.clear(); sensorSpeedList.clear(); sensorDistanceList.clear(); sensorHeartRateList.clear(); @@ -313,12 +316,14 @@ private void onTrackSegmentEnd() { trackPoint.setLocation(location); } + if (i < trackpointTypeList.size() && trackpointTypeList.get(i) != null) { + + TrackPoint.Type type = TrackPoint.Type.valueOf(trackpointTypeList.get(i)); + trackPoint.setType(type); + } + if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) { trackPoint.setSpeed(Speed.of(sensorSpeedList.get(i))); - - if (TrackPoint.IDLE_SPEED.greaterOrEqualThan(trackPoint.getSpeed())) { - trackPoint.setType(TrackPoint.Type.IDLE); - } } if (i < sensorDistanceList.size() && sensorDistanceList.get(i) != null) { trackPoint.setSensorDistance(Distance.of(sensorDistanceList.get(i))); @@ -403,6 +408,10 @@ private Location createLocation(String longitude, String latitude, String altitu } private void onExtendedDataValueEnd() throws SAXException { + if (dataType.equals(KMLTrackExporter.EXTENDED_DATA_TYPE_TRACKPOINT)) { + trackpointTypeList.add(content != null ? content.trim() : null); + return; + } Float value = null; if (content != null) { content = content.trim(); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index 7f77066999..fe4bbbd8a0 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -10,6 +10,7 @@ import android.util.Pair; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import java.time.Duration; import java.time.ZoneOffset; @@ -29,7 +30,7 @@ import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.util.TrackNameUtils; -class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChangeListener { +public class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = TrackRecordingManager.class.getSimpleName(); @@ -162,7 +163,8 @@ public Marker.Id insertMarker(String name, String category, String description, return new Marker.Id(ContentUris.parseId(uri)); } - void onIdle() { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public void onIdle() { Log.d(TAG, "Becoming idle"); onNewTrackPoint(trackPointCreator.createIdle()); } diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java index 05e9baa72a..6b56844fc6 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java @@ -79,7 +79,7 @@ public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) { this.trackStatistics = new TrackStatistics(toCopy.trackStatistics); this.lastTrackPoint = toCopy.lastTrackPoint; - this.idle = idle; + this.idle = toCopy.idle; resetAverageHeartRate(); } @@ -181,6 +181,7 @@ private void reset(TrackPoint trackPoint) { currentSegment.reset(trackPoint.getTime()); lastTrackPoint = null; + idle = false; resetAverageHeartRate(); } From 78d2a0722ada75972e777ad7d21aa60feec13e01 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 31 Jul 2023 21:55:55 +0200 Subject: [PATCH 3/7] Idle: TrackRecordingManager creates IDLE TrackPoints (configurable timeout). Fixes of #1187. --- .../TrackRecordingServiceRecordingTest.java | 2 +- .../opentracks/data/models/Speed.java | 5 -- .../services/TrackRecordingManager.java | 15 +++--- .../services/TrackRecordingService.java | 2 +- .../settings/GpsSettingsFragment.java | 4 +- .../opentracks/settings/PreferencesUtils.java | 52 +++++-------------- src/main/res/values-b+es+419/strings.xml | 1 - src/main/res/values-cs/strings.xml | 1 - src/main/res/values-da/strings.xml | 1 - src/main/res/values-de/strings.xml | 1 - src/main/res/values-es/strings.xml | 1 - src/main/res/values-et/strings.xml | 1 - src/main/res/values-eu/strings.xml | 1 - src/main/res/values-fi/strings.xml | 1 - src/main/res/values-fr/strings.xml | 1 - src/main/res/values-ga/strings.xml | 1 - src/main/res/values-gl/strings.xml | 1 - src/main/res/values-hu/strings.xml | 1 - src/main/res/values-it/strings.xml | 1 - src/main/res/values-nb/strings.xml | 1 - src/main/res/values-nl/strings.xml | 1 - src/main/res/values-pl/strings.xml | 1 - src/main/res/values-pt-rBR/strings.xml | 1 - src/main/res/values-pt-rPT/strings.xml | 1 - src/main/res/values-pt/strings.xml | 1 - src/main/res/values-ro/strings.xml | 1 - src/main/res/values-ru/strings.xml | 1 - src/main/res/values-sk/strings.xml | 1 - src/main/res/values-sl/strings.xml | 1 - src/main/res/values-tr/strings.xml | 1 - src/main/res/values-uk/strings.xml | 1 - src/main/res/values-vi/strings.xml | 1 - src/main/res/values-zh-rTW/strings.xml | 1 - src/main/res/values-zh/strings.xml | 1 - src/main/res/values/settings.xml | 21 ++++---- src/main/res/values/strings.xml | 3 +- src/main/res/xml/settings_gps.xml | 8 +-- 37 files changed, 41 insertions(+), 99 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java index 1b82e80fdf..95e35f88e4 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java @@ -88,7 +88,7 @@ public void setUp() throws TimeoutException { contentProviderUtils = new ContentProviderUtils(context); PreferencesUtils.setString(R.string.recording_distance_interval_key, R.string.recording_distance_interval_default); - PreferencesUtils.setString(R.string.idle_speed_key, R.string.idle_speed_default); + PreferencesUtils.setString(R.string.idle_duration_key, R.string.idle_duration_default); service = startService(); } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/Speed.java b/src/main/java/de/dennisguse/opentracks/data/models/Speed.java index 7714b678af..60bd1e84b2 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/Speed.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/Speed.java @@ -2,7 +2,6 @@ import java.time.Duration; -import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.UnitSystem; public record Speed(double speed_mps) { @@ -56,10 +55,6 @@ public boolean isInvalid() { return Double.isNaN(speed_mps) || Double.isInfinite(speed_mps); } - public boolean isMoving() { - return !isInvalid() && greaterThan(PreferencesUtils.getIdleSpeed()); - } - public boolean lessThan(Speed speed) { return !greaterThan(speed); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index fe4bbbd8a0..50e8eb3637 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -34,8 +34,6 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen private static final String TAG = TrackRecordingManager.class.getSimpleName(); - private static final Duration IDLE_TIMEOUT = Duration.ofSeconds(30); - private static final AltitudeCorrectionManager ALTITUDE_CORRECTION_MANAGER = new AltitudeCorrectionManager(); private final Runnable ON_IDLE = this::onIdle; @@ -49,6 +47,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen private Distance recordingDistanceInterval; private Distance maxRecordingDistance; + private Duration idleDuration; private Track.Id trackId; private TrackStatisticsUpdater trackStatisticsUpdater; @@ -69,7 +68,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen Track.Id startNewTrack() { TrackPoint segmentStartTrackPoint = trackPointCreator.createSegmentStartManual(); - // Create new track + ZoneOffset zoneOffset = ZoneOffset.systemDefault().getRules().getOffset(segmentStartTrackPoint.getTime()); Track track = new Track(zoneOffset); trackId = contentProviderUtils.insertTrack(track); @@ -83,7 +82,6 @@ Track.Id startNewTrack() { track.setActivityTypeLocalized(activityTypeLocalized); track.setActivityType(ActivityType.findByLocalizedString(context, activityTypeLocalized)); track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics()); - //TODO Pass TrackPoint track.setName(TrackNameUtils.getTrackName(context, trackId, track.getStartTime())); contentProviderUtils.updateTrack(track); @@ -109,7 +107,7 @@ boolean resumeExistingTrack(@NonNull Track.Id resumeTrackId) { return true; } - void end() { + void endCurrentTrack() { TrackPoint segmentEnd = trackPointCreator.createSegmentEnd(); insertTrackPoint(segmentEnd, true); @@ -223,7 +221,7 @@ synchronized boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { insertTrackPoint(trackPoint, true); handler.removeCallbacks(ON_IDLE); - handler.postDelayed(ON_IDLE, IDLE_TIMEOUT.toMillis()); + handler.postDelayed(ON_IDLE, idleDuration.toMillis()); return true; } @@ -231,7 +229,7 @@ synchronized boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { insertTrackPoint(trackPoint, false); handler.removeCallbacks(ON_IDLE); - handler.postDelayed(ON_IDLE, IDLE_TIMEOUT.toMillis()); + handler.postDelayed(ON_IDLE, idleDuration.toMillis()); return true; } @@ -297,5 +295,8 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin if (PreferencesUtils.isKey(R.string.max_recording_distance_key, key)) { maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(); } + if (PreferencesUtils.isKey(R.string.idle_duration_key, key)) { + idleDuration = PreferencesUtils.getIdleDurationTimeout(); + } } } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 1255a489cc..2ef10e7201 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -214,7 +214,7 @@ public void endCurrentTrack() { // Set recording status updateRecordingStatus(STATUS_DEFAULT); - trackRecordingManager.end(); + trackRecordingManager.endCurrentTrack(); stopUpdateRecordingData(); diff --git a/src/main/java/de/dennisguse/opentracks/settings/GpsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/GpsSettingsFragment.java index eff92ab982..44b087d075 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/GpsSettingsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/settings/GpsSettingsFragment.java @@ -79,7 +79,7 @@ public void onResume() { ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key)); recordingGpsAccuracy.setEntries(PreferencesUtils.getThresholdHorizontalAccuracyEntries()); - ListPreference idleSpeed = findPreference(getString(R.string.idle_speed_key)); - idleSpeed.setEntries(PreferencesUtils.getIdleSpeedEntries()); + ListPreference idleDuration = findPreference(getString(R.string.idle_duration_key)); + idleDuration.setEntries(PreferencesUtils.getIdleDurationEntries()); } } diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index e0303a2619..5a1af8bc9d 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -45,7 +45,6 @@ import de.dennisguse.opentracks.data.models.DistanceFormatter; import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.HeartRateZones; -import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFilenameGenerator; @@ -530,11 +529,12 @@ public static Duration getMinRecordingIntervalDefault() { static String[] getMinRecordingIntervalEntries() { String[] entryValues = resources.getStringArray(R.array.min_recording_interval_values); + long recommended = PreferencesUtils.getMinRecordingIntervalDefault().getSeconds(); String[] entries = new String[entryValues.length]; for (int i = 0; i < entryValues.length; i++) { int value = Integer.parseInt(entryValues[i]); - if (value == PreferencesUtils.getMinRecordingIntervalDefault().getSeconds()) { + if (value == recommended) { entries[i] = resources.getString(R.string.value_smallest_recommended); } else { entries[i] = value < 60 ? resources.getString(R.string.value_integer_second, value) : resources.getString(R.string.value_integer_minute, value / 60); @@ -598,49 +598,25 @@ static String[] getThresholdHorizontalAccuracyEntries() { return entries; } - - public static Speed getIdleSpeed() { - final float DEFAULT = Float.parseFloat(resources.getString(R.string.idle_speed_default)); - float value = getFloat(R.string.idle_speed_key, DEFAULT); - return Speed.ofKMH(value); + public static Duration getIdleDurationTimeout() { + final int DEFAULT = Integer.parseInt(resources.getString(R.string.idle_duration_default)); + int value = getInt(R.string.idle_duration_key, DEFAULT); + return Duration.ofSeconds(value); } - static String[] getIdleSpeedEntries() { - String[] entryValues = resources.getStringArray(R.array.idle_speed_values); + static String[] getIdleDurationEntries() { + String[] entryValues = resources.getStringArray(R.array.idle_duration_values); String[] entries = new String[entryValues.length]; - final float idleSpeedDefault = Float.parseFloat(resources.getString(R.string.idle_speed_default)); - - UnitSystem unitSystem = getUnitSystem(); + final int idleDurationDefault = Integer.parseInt(resources.getString(R.string.idle_duration_default)); for (int i = 0; i < entryValues.length; i++) { - float value = Float.parseFloat(entryValues[i]); + int value = Integer.parseInt(entryValues[i]); - switch (unitSystem) { - case METRIC -> { - if (value == idleSpeedDefault) { - entries[i] = resources.getString(R.string.value_float_kilometer_hour_recommended, value); - } else { - entries[i] = resources.getString(R.string.value_float_kilometer_hour, value); - } - } - case IMPERIAL_FEET, IMPERIAL_METER -> { - double valueMPH = Speed.ofKMH(value).toMPH(); - if (value == idleSpeedDefault) { - entries[i] = resources.getString(R.string.value_float_mile_hour_recommended, valueMPH); - } else { - entries[i] = resources.getString(R.string.value_float_mile_hour, valueMPH); - } - } - case NAUTICAL_IMPERIAL -> { - double valueKnots = Speed.ofKMH(value).toKnots(); - if (value == idleSpeedDefault) { - entries[i] = resources.getString(R.string.value_float_knots_recommended, valueKnots); - } else { - entries[i] = resources.getString(R.string.value_float_knots, valueKnots); - } - } - default -> throw new RuntimeException("Not implemented"); + if (value == idleDurationDefault) { + entries[i] = resources.getString(R.string.value_int_seconds, value); + } else { + entries[i] = value < 60 ? resources.getString(R.string.value_integer_second, value) : resources.getString(R.string.value_integer_minute, value / 60); } } diff --git a/src/main/res/values-b+es+419/strings.xml b/src/main/res/values-b+es+419/strings.xml index 36c1b363a2..9d16d1ac2e 100644 --- a/src/main/res/values-b+es+419/strings.xml +++ b/src/main/res/values-b+es+419/strings.xml @@ -429,7 +429,6 @@ Tiempo, Distancia, Velocidad de la voz Importar, Exportar, Autoexportar Unidades - Umbral de velocidad de ralentí Seleccionar diseño Diseño predeterminado Genérico diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 56796930e2..00f2a9a346 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -477,7 +477,6 @@ limitations under the License. V nastavení systému je třeba ručně povolit aplikaci OpenTracks přístup k blízkým zařízením. %1$.1f mi/h %1$.1f mil/h (doporučeno) - Rychlost pro autopausu %1$.1f km/h %1$.1f km/h (doporučeno) Hodiny diff --git a/src/main/res/values-da/strings.xml b/src/main/res/values-da/strings.xml index eab08bb9e8..aefa73f9d4 100644 --- a/src/main/res/values-da/strings.xml +++ b/src/main/res/values-da/strings.xml @@ -487,7 +487,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj Importere Eksport Klokken - Tærskel for tomgangshastighed %1$.1f mi/h (anbefalet) %1$.1f mi/h Til diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index d69ead9f68..f05d0d4f6c 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -496,7 +496,6 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka GPS Wähle Layout Wähle eine Option - Inaktivitätsgeschwindigkeit GPS Import/Export Bluetooth Sensoren diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 63bf77aef3..5a717228fb 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -507,7 +507,6 @@ limitations under the License. %1$.1f km/h %1$.1f mi/h %1$.1f mi/h (recomendado) - Umbral de velocidad de ralentí Reloj %1$.1f km/h (recomendado) Genérico diff --git a/src/main/res/values-et/strings.xml b/src/main/res/values-et/strings.xml index d3c61babd7..b85dd0d9fe 100644 --- a/src/main/res/values-et/strings.xml +++ b/src/main/res/values-et/strings.xml @@ -450,7 +450,6 @@ limitations under the License. Kell %1$.1f km/h (soovitatav) %1$.1f km/h - Tühikäigu kiiruse lävi Vahemaa intervall GPS Jalgrattasõit diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index c7631a89f1..b41b5efde2 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -485,7 +485,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O Txirrindularitza Sentsoreak, Gurpila Zirkunferentzia, Sentsore Exekutatzen Datu-eremuak errenkadako Pertsonalizatu zure grabazioaren diseinua - Inaktibitatearen abiaduraren muga Denbora-tartea Distantzia tartea Exekutatzen diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml index 7c39998c3a..f7df29ec08 100644 --- a/src/main/res/values-fi/strings.xml +++ b/src/main/res/values-fi/strings.xml @@ -521,7 +521,6 @@ limitations under the License. Automaattinen tiedonsiirto Tallennuksen aloittanut sovellus voi myös käyttää tallennettuja tietoja. Suodatin - Joutokäyntinopeuden kynnysarvo Kadenssi Kello Nykyinen syke diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 5a434d7459..54d27ff420 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -511,7 +511,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio %1$.1f km/h (recommandé) %1$.1f mi/h %1$.1f mi/h (recommandé) - Seuil de vitesse au ralenti Horloge %1$.1f km/h Générique diff --git a/src/main/res/values-ga/strings.xml b/src/main/res/values-ga/strings.xml index fac3a03821..4a9fdbd677 100644 --- a/src/main/res/values-ga/strings.xml +++ b/src/main/res/values-ga/strings.xml @@ -238,7 +238,6 @@ Dáta (áitiúil) Uimhir Ainm rian réamhshocraithe - Tairseach luais díomhaoin Téama Chomhéadain Córas diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index d7798ad76f..79d9da8af1 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -477,7 +477,6 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele Intervalo de distancia OpenTracks require permiso para usar o Bluetooth. Tes que permitirlle a OpenTracks nos axustes do sistema que acceda a Dispositivos Próximos. - Limiar detección movemento %1$.1f km/h %1$.1f km/h %1$.1f km/h (recomendado) diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml index cd3bb8d5de..be72cd72cd 100644 --- a/src/main/res/values-hu/strings.xml +++ b/src/main/res/values-hu/strings.xml @@ -474,7 +474,6 @@ limitations under the License. Ütem Visszaállítás Minden elrendezési beállítás visszaáll az alapértelmezett értékekre. Ez nem törli az eszközön lévő zeneszámokat. - Alapjárati fordulatszám-küszöbérték Elrendezések Írja be az elrendezés nevét a hozzáadáshoz A megadott szűrőhöz nincsenek tevékenységek diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 336218b5f5..1096fb8d4e 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -456,7 +456,6 @@ Infatti, un\'applicazione deve supportare ACTION_VIEW con MIME applica Distanza, Velocità e Cadenza Orologio Intervallo di tempo - Soglia velocità di movimento Intervallo di distanza %1$s, %2$s %1$s° diff --git a/src/main/res/values-nb/strings.xml b/src/main/res/values-nb/strings.xml index 2610e89eb4..cd2911194d 100644 --- a/src/main/res/values-nb/strings.xml +++ b/src/main/res/values-nb/strings.xml @@ -472,7 +472,6 @@ limitations under the License. Eksport %1$.1f km/t %1$.1f km/t (anbefales) - Terskel for tomgangshastighet %1$.1f mi/h %1$.1f mi/h (anbefales) Klokke diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 98ce295d2c..63761ff6fc 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -458,7 +458,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid User Interface Coördinaten Afstandsinterval - Drempelwaarde stationair %1$.1f km/u Publieke API Public API diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index fd575851ed..c819c075c6 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -456,7 +456,6 @@ limitations under the License. Częstość akcji serca Zegar Przedział dystansu - Próg prędkości bezobciążeń Zresetować twoje układy\? Kolarstwo Bieganie diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 59a91040f3..558c4a1f8b 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -427,7 +427,6 @@ Exportar %1$.1f km/h %1$.1f km/h (Recomendado) - Limite de velocidade de marcha lenta Relógio Anúncios de voz Voice announcements diff --git a/src/main/res/values-pt-rPT/strings.xml b/src/main/res/values-pt-rPT/strings.xml index 2120963585..61f51c2793 100644 --- a/src/main/res/values-pt-rPT/strings.xml +++ b/src/main/res/values-pt-rPT/strings.xml @@ -438,7 +438,6 @@ Mantenha o ecrã ligada Durante a gravação, apresente em ecrã inteiro. Durante a gravação, mantenha o ecrã ligado. - Limite de velocidade de marcha lenta Tema UI Sistema Noite diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml index 7376ab8b3e..a0152c7305 100644 --- a/src/main/res/values-pt/strings.xml +++ b/src/main/res/values-pt/strings.xml @@ -465,7 +465,6 @@ limitations under the License. Importar/Exportar Importar, Exportar, Auto Export Unidades - Limite de velocidade de marcha lenta Tema UI Todas as configurações de layout serão revertidas para os valores padrão. Isso não excluirá nenhuma trilha do aparelho. Redefinir diff --git a/src/main/res/values-ro/strings.xml b/src/main/res/values-ro/strings.xml index df1647863e..8bc11f8b4e 100644 --- a/src/main/res/values-ro/strings.xml +++ b/src/main/res/values-ro/strings.xml @@ -457,7 +457,6 @@ limitations under the License. Numele Layout-ului există deja Machete Tastați numele layout pentru a adăuga - Pragul de viteză la ralanti Filtru Selectați Layout Importație/Export diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 7e444affb3..5d255f783f 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -490,7 +490,6 @@ limitations under the License. Часы Настройки активности Сбросить настройки - Порог скорости холостого хода Обычные Выбрать раскладку Раскладка по умолчанию diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index c58e865503..8ca052929e 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -427,7 +427,6 @@ limitations under the License. Počas nahrávania zobrazte štatistiky bez odomknutia zariadenia. Zobrazenie štatistík na uzamknutej obrazovke Všeobecné - Prahová hodnota voľnobehu %1$.1f mi/h Adresár pre export stôp Okamžitý export po tréningu diff --git a/src/main/res/values-sl/strings.xml b/src/main/res/values-sl/strings.xml index e57db822ba..79d3571670 100644 --- a/src/main/res/values-sl/strings.xml +++ b/src/main/res/values-sl/strings.xml @@ -448,7 +448,6 @@ limitations under the License. Ponastavitev Razdalja, hitrost in kadenca Obod kolesa (mm) - Prag prostega teka Sistem OpenTracks Public API je onemogočen: omogočite ga lahko v nastavitvah. Trenutni srčni utrip diff --git a/src/main/res/values-tr/strings.xml b/src/main/res/values-tr/strings.xml index a6f2fad9c3..330d82566f 100644 --- a/src/main/res/values-tr/strings.xml +++ b/src/main/res/values-tr/strings.xml @@ -453,7 +453,6 @@ limitations under the License. Kilit ekranında istatistikleri göster Kayıt esnasında ekranı açık tutar. Kayıt esnasında tam ekranda sunar. - Boşta hız eşiği Kullanıcı arayüzü teması Sistem Gece diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index d144648e01..8e3c1c8a46 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -475,7 +475,6 @@ limitations under the License. Введіть ім\'я макета для додавання Інтервал відстаней Часовий інтервал - Поріг холостого ходу Рухомий Загальний Скинути ваші макети\? diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index 3a02460c0d..0368129a2b 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -469,7 +469,6 @@ limitations under the License. Nhập Xuất Chung chung - Tốc độ ngưỡng %1$.1f mi/h %1$.1f mi/h (khuyến khích) %1$.1f km/h diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 465ab77a5b..6d4f84db44 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -366,7 +366,6 @@ limitations under the License. 日期 (當地) 數字 預設蹤跡名稱 - 怠速閾值 界面主題 系統 diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index 6d5a1a8700..e89b5cc8bc 100644 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -430,7 +430,6 @@ limitations under the License. \n 方法二:通过一个正常的地图应用(例如 OsmAndMAPS.ME)。这些应用需要支持 KMZ 文件格式(只包含位置和时间戳)。事实上,应用需要支持 application/vnd.google-earth.kmz MIME 类型的 ACTION_VIEW 操作。 错误 OpenTracks 需要使用 GPS 的权限。 - 静止速度阈值 布局 丢弃 线程被中断 diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 95ea4ecc1b..f35f4499e2 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -123,19 +123,16 @@ @string/recording_gps_accuracy_poor - idleSpeed - 0.5 - - 0.3 - @string/idle_speed_default - 1 - 1.5 - 2 - 2.5 - 3 - 3.5 - 4 + idleSpeedDuration + 10 + 5 + @string/idle_duration_default + 15 + 30 + 45 + 60 + 120 statsRate diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 15c7eade2c..7cfc916540 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -395,7 +395,7 @@ limitations under the License. Date (local) Number Default track name - Idle speed threshold + Idle threshold UI Theme System Day @@ -557,6 +557,7 @@ limitations under the License. None Off Smallest (recommended) + %1$d s (recommended) {n, plural, =1 {1 hour} diff --git a/src/main/res/xml/settings_gps.xml b/src/main/res/xml/settings_gps.xml index 979d0ac242..6e4bba356c 100644 --- a/src/main/res/xml/settings_gps.xml +++ b/src/main/res/xml/settings_gps.xml @@ -29,10 +29,10 @@ android:title="@string/settings_recording_min_required_accuracy_title" /> \ No newline at end of file From 7d739e364008c290402a3068320027c28c78e6ca Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Tue, 8 Aug 2023 18:19:31 +0200 Subject: [PATCH 4/7] TrackStatisticsUpdater: remove check for max acceleration (was still used for max speed). --- .../stats/TrackStatisticsUpdaterTest.java | 36 ------------------- .../stats/TrackStatisticsUpdater.java | 29 +++------------ 2 files changed, 4 insertions(+), 61 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java index e2cf1e7592..59c9425a97 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java @@ -178,42 +178,6 @@ public void addTrackPoint_distance_from_GPS_moving_and_sensor_disconnecting() { assertEquals(59.18, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); } - - @Test - public void addTrackPoint_maxSpeed_ignore_above_acceleration() { - TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); - assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed()); - - subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0))); - assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed()); - - // Ignore as we set max speed if two consecutive trackpoints were considered moving - subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1)) - .setSpeed(Speed.of(1f))); - assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed()); - - // Update max speed - subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2)) - .setSpeed(Speed.of(1f))); - assertEquals(Speed.of(1f), subject.getTrackStatistics().getMaxSpeed()); - - // Update max speed - subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(12)) - .setSpeed(Speed.of(50f))); - assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed()); - - // Ignore; we were getting slower - subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(13)) - .setSpeed(Speed.of(5f))); - assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed()); - - // Ignore acceleration above 2g - subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(14)) - .setSpeed(Speed.of(500f))); - assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed()); - - } - @Test public void addTrackPoint_maxSpeed_multiple_segments() { TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java index 6b56844fc6..93b5efa526 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java @@ -16,8 +16,6 @@ package de.dennisguse.opentracks.stats; -import android.util.Log; - import androidx.annotation.NonNull; import java.time.Duration; @@ -40,11 +38,6 @@ public class TrackStatisticsUpdater { private static final String TAG = TrackStatisticsUpdater.class.getSimpleName(); - /** - * Ignore any acceleration faster than this. - * Will ignore any speeds that imply acceleration greater than 2g's - */ - private static final double SPEED_MAX_ACCELERATION = 2 * 9.81; private final TrackStatistics trackStatistics; @@ -193,27 +186,13 @@ private void resetAverageHeartRate() { /** * Updates a speed reading while assuming the user is moving. */ - private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) { - if (isValidSpeed(trackPoint, lastTrackPoint)) { - Speed currentSpeed = trackPoint.getSpeed(); - if (currentSpeed.greaterThan(currentSegment.getMaxSpeed())) { - currentSegment.setMaxSpeed(currentSpeed); - } - } else { - Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed()); + private void updateSpeed(@NonNull TrackPoint trackPoint) { + Speed currentSpeed = trackPoint.getSpeed(); + if (currentSpeed.greaterThan(currentSegment.getMaxSpeed())) { + currentSegment.setMaxSpeed(currentSpeed); } } - private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) { - // See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g. - Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); - Speed maxSpeedDifference = Speed.of(Distance.of(SPEED_MAX_ACCELERATION), Duration.ofMillis(1000)) - .mul(timeDifference.toSeconds()); - - Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed()); - return speedDifference.lessThan(maxSpeedDifference); - } - @NonNull @Override public String toString() { From 348cb16256c90c30409952838032a9e0149d95d1 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 25 Aug 2023 07:35:59 +0200 Subject: [PATCH 5/7] ExportImportTest: extract test for checking recording. --- .../io/file/importer/ExportImportTest.java | 152 ++++++++++++------ .../opentracks/data/models/TrackPoint.java | 5 +- 2 files changed, 109 insertions(+), 48 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 2a411518d2..59464b9afc 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 @@ -35,6 +35,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.TimeZone; @@ -185,8 +186,103 @@ public void setUp() throws TimeoutException { track = contentProviderUtils.getTrack(trackId); trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); markers = contentProviderUtils.getMarkers(trackId); - assertEquals(12, trackPoints.size()); - assertEquals(2, markers.size()); + } + + @LargeTest + @Test + public void track() throws TimeoutException { + setUp(); + + Track track = contentProviderUtils.getTrack(trackId); + TrackStatistics trackStatistics = track.getTrackStatistics(); + + assertEquals(ZoneOffset.of("+01:00"), track.getZoneOffset()); + assertEquals(Instant.parse("2020-02-02T02:02:02Z"), trackStatistics.getStartTime()); + assertEquals(Instant.parse("2020-02-02T02:04:00Z"), trackStatistics.getStopTime()); + + assertEquals(Duration.ofSeconds(56), trackStatistics.getTotalTime()); + assertEquals(Duration.ofSeconds(26), trackStatistics.getMovingTime()); //TODO Likely too low + + // Distance + assertEquals(222125.53125, trackStatistics.getTotalDistance().toM(), 0.01); //TODO Too low + + // Speed + assertEquals(8543.29, trackStatistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(3966.52, trackStatistics.getAverageSpeed().toMPS(), 0.01); + assertEquals(8543.28, trackStatistics.getAverageMovingSpeed().toMPS(), 0.01); + + // Altitude + assertEquals(10, trackStatistics.getMinAltitude(), 0.01); + assertEquals(10, trackStatistics.getMaxAltitude(), 0.01); + + assertEquals(2, trackStatistics.getTotalAltitudeGain(), 0.01); + assertEquals(2, trackStatistics.getTotalAltitudeLoss(), 0.01); + + List actual = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); + new TrackPointAssert().assertEquals(List.of( + new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z")) + .setLatitude(3) + .setLongitude(14) + .setAltitude(10) + .setSpeed(Speed.of(15)) + .setAltitudeLoss(1f) + .setAltitudeGain(1f) + .setHorizontalAccuracy(Distance.of(10)), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:04Z")) + .setSensorDistance(Distance.of(10)) + .setSpeed(Speed.of(15)) + .setHeartRate(HeartRate.of(66)) + .setCadence(3) + .setPower(50) + .setAltitudeLoss(1f) + .setAltitudeGain(1f), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:15Z")) + .setHeartRate(HeartRate.of(68)) + .setCadence(3) + .setPower(50), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:17Z")) + .setLatitude(3) + .setLongitude(14.001) + .setAltitude(10) + .setSensorDistance(Distance.of(2)) + .setSpeed(Speed.of(5)) + .setAltitudeLoss(0f) + .setAltitudeGain(0f) + .setHorizontalAccuracy(Distance.of(10)) + .setHeartRate(HeartRate.of(69)) + .setCadence(3) + .setPower(50), + new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:18Z")), + new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:03:20Z")), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:03:21Z")) + .setLatitude(3) + .setLongitude(14.002) + .setAltitude(10) + .setSpeed(Speed.of(15)) + .setAltitudeLoss(0f) + .setAltitudeGain(0f) + .setHorizontalAccuracy(Distance.of(10)), + new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:03:22Z")) + .setLatitude(3) + .setLongitude(16) + .setAltitude(10) + .setSpeed(Speed.of(15)) + .setAltitudeLoss(0f) + .setAltitudeGain(0f) + .setHorizontalAccuracy(Distance.of(10)), + new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-02-02T02:03:30Z")), + new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:03:50Z")) + .setLatitude(3) + .setLongitude(16.001) + .setAltitude(10) + .setSpeed(Speed.of(15)) + .setAltitudeLoss(0f) + .setAltitudeGain(0f) + .setHorizontalAccuracy(Distance.of(10)), + new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:04:00Z")) + + ), actual); } //TODO Does not test marker images @@ -228,43 +324,7 @@ public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOExc // Time assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset()); - assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime()); - assertEquals(Instant.parse("2020-02-02T02:04:00Z"), importedTrackStatistics.getStopTime()); - - TrackStatistics originalTrackStatistics = track.getTrackStatistics(); - - assertEquals(originalTrackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime()); - assertEquals(Duration.ofSeconds(56), importedTrackStatistics.getTotalTime()); - - assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime()); - assertEquals(Duration.ofSeconds(26), importedTrackStatistics.getMovingTime()); //TODO Likely too low - - // Distance - assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance()); - assertEquals(222125.53125, importedTrackStatistics.getTotalDistance().toM(), 0.01); //TODO Too low - - // Speed - assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed()); - assertEquals(8543.29, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01); - - assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed()); - assertEquals(3966.52, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01); - - assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed()); - assertEquals(8543.28, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01); - - // 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(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01); - - assertEquals(originalTrackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01); - assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01); + assertEquals(track.getTrackStatistics(), importedTrackStatistics); // 4. markers assertMarkers(); @@ -489,14 +549,14 @@ private void assertMarkers() { private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) { - SensorDataSet sensorDataSet = new SensorDataSet(); - sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power))); - sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate))); + SensorDataSet sensorDataSet = new SensorDataSet(); + sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power))); + sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate))); - SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class); - Mockito.when(cyclingCadence.hasValue()).thenReturn(true); - Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence)); - sensorDataSet.set(cyclingCadence); + SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class); + Mockito.when(cyclingCadence.hasValue()).thenReturn(true); + Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence)); + sensorDataSet.set(cyclingCadence); if (distance != null && speed != null) { SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class); diff --git a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java index af6049ae91..03eaf6c73b 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java @@ -444,13 +444,14 @@ public String toString() { ", latitude=" + latitude + ", longitude=" + longitude + ", horizontalAccuracy=" + horizontalAccuracy + + ", verticalAccuracy=" + verticalAccuracy + ", altitude=" + altitude + ", speed=" + speed + ", bearing=" + bearing + ", sensorDistance=" + sensorDistance + ", type=" + type + - ", heartRate_bpm=" + heartRate + - ", cadence_rpm=" + cadence + + ", heartRate=" + heartRate + + ", cadence=" + cadence + ", power=" + power + ", altitudeGain_m=" + altitudeGain_m + ", altitudeLoss_m=" + altitudeLoss_m + From 9a50d7bedfc044548c3501793bee2ae45aae1192 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 25 Aug 2023 23:28:25 +0200 Subject: [PATCH 6/7] Idle: move idle into TrackStatistics. --- .../opentracks/stats/TrackStatistics.java | 13 +++++++++++++ .../opentracks/stats/TrackStatisticsUpdater.java | 10 +++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java index 644dd6d4dc..41fa6d8e74 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java @@ -61,6 +61,8 @@ public class TrackStatistics { // The average heart rate seen on this track private HeartRate avgHeartRate = null; + private boolean isIdle; + public TrackStatistics() { reset(); } @@ -81,6 +83,7 @@ public TrackStatistics(TrackStatistics other) { totalAltitudeGain_m = other.totalAltitudeGain_m; totalAltitudeLoss_m = other.totalAltitudeLoss_m; avgHeartRate = other.avgHeartRate; + isIdle = other.isIdle; } @VisibleForTesting @@ -168,6 +171,8 @@ public void reset() { setMaxSpeed(Speed.zero()); setTotalAltitudeGain(null); setTotalAltitudeLoss(null); + + isIdle = false; } public void reset(Instant startTime) { @@ -248,6 +253,14 @@ public Duration getStoppedTime() { return totalTime.minus(movingTime); } + public boolean isIdle() { + return isIdle; + } + + public void setIdle(boolean idle) { + isIdle = idle; + } + public boolean hasAverageHeartRate() { return avgHeartRate != null; } diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java index 93b5efa526..31dc379365 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java @@ -49,8 +49,6 @@ public class TrackStatisticsUpdater { // Current segment's last trackPoint private TrackPoint lastTrackPoint; - private boolean idle; - public TrackStatisticsUpdater() { this(new TrackStatistics()); } @@ -72,7 +70,6 @@ public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) { this.trackStatistics = new TrackStatistics(toCopy.trackStatistics); this.lastTrackPoint = toCopy.lastTrackPoint; - this.idle = toCopy.idle; resetAverageHeartRate(); } @@ -140,18 +137,18 @@ public void addTrackPoint(TrackPoint trackPoint) { movingDistance = trackPoint.distanceToPrevious(lastTrackPoint); } if (movingDistance != null) { - idle = false; + currentSegment.setIdle(false); currentSegment.addTotalDistance(movingDistance); } - if (!idle && !trackPoint.isSegmentManualStart()) { + if (!currentSegment.isIdle() && !trackPoint.isSegmentManualStart()) { if (lastTrackPoint != null) { currentSegment.addMovingTime(trackPoint, lastTrackPoint); } } if (trackPoint.getType() == TrackPoint.Type.IDLE) { - idle = true; + currentSegment.setIdle(true); } if (trackPoint.hasSpeed()) { @@ -174,7 +171,6 @@ private void reset(TrackPoint trackPoint) { currentSegment.reset(trackPoint.getTime()); lastTrackPoint = null; - idle = false; resetAverageHeartRate(); } From 83da4f8f347efc74719319f0f19b7e9f1c9c8b0c Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sat, 23 Sep 2023 18:52:28 +0200 Subject: [PATCH 7/7] Idle: add voice announcement (user configurable). Fixes of #1174 --- .../VoiceAnnouncementUtilsTest.java | 32 ++++----- .../opentracks/AbstractActivity.java | 4 +- .../services/TrackRecordingManager.java | 11 ++- .../services/TrackRecordingService.java | 10 ++- ...VoiceAnnouncement.java => TTSManager.java} | 48 ++----------- .../VoiceAnnouncementManager.java | 72 ++++++++++++++++--- .../announcement/VoiceAnnouncementUtils.java | 7 +- .../AnnouncementsSettingsFragment.java | 2 +- .../opentracks/settings/PreferencesUtils.java | 4 ++ src/main/res/values-b+es+419/strings.xml | 2 +- src/main/res/values-cs/strings.xml | 2 +- src/main/res/values-da/strings.xml | 2 +- src/main/res/values-de/strings.xml | 2 +- src/main/res/values-es/strings.xml | 2 +- src/main/res/values-et/strings.xml | 2 +- src/main/res/values-eu/strings.xml | 2 +- src/main/res/values-fi/strings.xml | 2 +- src/main/res/values-fr/strings.xml | 2 +- src/main/res/values-ga/strings.xml | 2 +- src/main/res/values-gl/strings.xml | 2 +- src/main/res/values-hu/strings.xml | 2 +- src/main/res/values-it/strings.xml | 2 +- src/main/res/values-nb/strings.xml | 2 +- src/main/res/values-nl/strings.xml | 2 +- src/main/res/values-pl/strings.xml | 2 +- src/main/res/values-pt-rBR/strings.xml | 2 +- src/main/res/values-pt-rPT/strings.xml | 2 +- src/main/res/values-pt/strings.xml | 2 +- src/main/res/values-ru/strings.xml | 2 +- src/main/res/values-sk/strings.xml | 2 +- src/main/res/values-sl/strings.xml | 2 +- src/main/res/values-tr/strings.xml | 2 +- src/main/res/values-uk/strings.xml | 2 +- src/main/res/values-vi/strings.xml | 2 +- src/main/res/values-zh-rTW/strings.xml | 2 +- src/main/res/values-zh/strings.xml | 2 +- src/main/res/values/settings.xml | 10 +++ src/main/res/values/strings.xml | 7 +- src/main/res/xml/settings.xml | 2 +- src/main/res/xml/settings_announcements.xml | 61 +++++++++------- 40 files changed, 192 insertions(+), 132 deletions(-) rename src/main/java/de/dennisguse/opentracks/services/announcement/{VoiceAnnouncement.java => TTSManager.java} (73%) diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java index ab805fad6e..aac0d6b10e 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java @@ -63,7 +63,7 @@ public void getAnnouncement_metric_speed() { stats.setTotalAltitudeGain(6000f); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, null, null).toString(); // then assertEquals("Total distance 20.0 kilometers. 1 hour 5 minutes 10 seconds. Average moving speed 18.4 kilometers per hour.", announcement); @@ -79,7 +79,7 @@ public void getAnnouncement_metric_speed_rounding_check() { stats.setTotalAltitudeGain(6000f); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, null, null).toString(); // then assertEquals("Total distance 20.0 kilometers. 1 hour 1 second. Average moving speed 20.0 kilometers per hour.", announcement); @@ -95,7 +95,7 @@ public void getAnnouncement_metric_distance_rounding_check() { stats.setTotalAltitudeGain(6000f); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, null, null).toString(); // then assertEquals("Total distance 20.0 kilometers. 1 hour. Average moving speed 20.0 kilometers per hour.", announcement); @@ -111,7 +111,7 @@ public void getAnnouncement_metric_distance_rounding_check_two() { stats.setTotalAltitudeGain(6000f); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, null, null).toString(); // then assertEquals("Total distance 19.9 kilometers. 1 hour. Average moving speed 19.9 kilometers per hour.", announcement); @@ -133,7 +133,7 @@ public void getAnnouncement_withInterval_metric_speed() { } // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, lastInterval, null).toString(); // then assertEquals("Total distance 14.2 kilometers. 16 minutes 39 seconds. Average moving speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour.", announcement); @@ -149,7 +149,7 @@ public void getAnnouncement_metric_pace() { stats.setTotalAltitudeGain(6000f); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, false, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, false, null, null).toString(); // then assertEquals("Total distance 20.0 kilometers. 1 hour 5 minutes 10 seconds. Pace 3 minutes 15 seconds per kilometer.", announcement); @@ -171,7 +171,7 @@ public void getAnnouncement_withInterval_metric_pace() { } // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, false, lastInterval, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, false, lastInterval, null).toString(); // then assertEquals("Total distance 14.2 kilometers. 16 minutes 39 seconds. Pace 1 minute 10 seconds per kilometer. Lap time 1 minute 10 seconds per kilometer.", announcement); @@ -187,7 +187,7 @@ public void getAnnouncement_imperial_speed() { stats.setTotalAltitudeGain(6000f); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.IMPERIAL_FEET, true, null, null).toString(); // then assertEquals("Total distance 12.4 miles. 1 hour 5 minutes 10 seconds. Average moving speed 11.4 miles per hour.", announcement); @@ -201,7 +201,7 @@ public void getAnnouncement_imperial_speed_1() { stats.setMovingTime(Duration.ofHours(1)); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.IMPERIAL_FEET, true, null, null).toString(); // then assertEquals("Total distance 1.1 miles. 1 hour. Average moving speed 1.1 miles per hour.", announcement); @@ -215,7 +215,7 @@ public void getAnnouncement_imperial_meter_speed_1() { stats.setMovingTime(Duration.ofHours(1)); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_METER, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.IMPERIAL_METER, true, null, null).toString(); // then assertEquals("Total distance 1.1 miles. 1 hour. Average moving speed 1.1 miles per hour.", announcement); @@ -229,7 +229,7 @@ public void getAnnouncement_metric_speed_1() { stats.setMovingTime(Duration.ofHours(1)); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, null, null).toString(); // then assertEquals("Total distance 1.1 kilometers. 1 hour. Average moving speed 1.1 kilometers per hour.", announcement); @@ -251,7 +251,7 @@ public void getAnnouncement_withInterval_imperial_speed() { } // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, true, lastInterval, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.IMPERIAL_FEET, true, lastInterval, null).toString(); // then assertEquals("Total distance 8.8 miles. 16 minutes 39 seconds. Average moving speed 31.8 miles per hour. Lap speed 31.8 miles per hour.", announcement); @@ -267,7 +267,7 @@ public void getAnnouncement_imperial_pace() { stats.setTotalAltitudeGain(6000f); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, false, null, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.IMPERIAL_FEET, false, null, null).toString(); // then assertEquals("Total distance 12.4 miles. 1 hour 5 minutes 10 seconds. Pace 5 minutes 15 seconds per mile.", announcement); @@ -289,7 +289,7 @@ public void getAnnouncement_withInterval_imperial_pace() { } // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, false, lastInterval, null).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.IMPERIAL_FEET, false, lastInterval, null).toString(); // then assertEquals("Total distance 8.8 miles. 16 minutes 39 seconds. Pace 1 minute 53 seconds per mile. Lap time 1 minute 53 seconds per mile.", announcement); @@ -315,7 +315,7 @@ public void getAnnouncement_heart_rate_and_sensor_statistics() { SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null, null); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, sensorStatistics).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, lastInterval, sensorStatistics).toString(); // then assertEquals("Total distance 14.2 kilometers. 16 minutes 39 seconds. Average moving speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement); @@ -345,7 +345,7 @@ public void getAnnouncement_only_lap_heart_rate() { SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null, null); // when - String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, sensorStatistics).toString(); + String announcement = VoiceAnnouncementUtils.createStatistics(context, stats, UnitSystem.METRIC, true, lastInterval, sensorStatistics).toString(); // then assertEquals(" Current heart rate 133 bpm.", announcement); diff --git a/src/main/java/de/dennisguse/opentracks/AbstractActivity.java b/src/main/java/de/dennisguse/opentracks/AbstractActivity.java index 169b55915f..aae4c9b7db 100644 --- a/src/main/java/de/dennisguse/opentracks/AbstractActivity.java +++ b/src/main/java/de/dennisguse/opentracks/AbstractActivity.java @@ -21,7 +21,7 @@ import androidx.appcompat.app.AppCompatActivity; -import de.dennisguse.opentracks.services.announcement.VoiceAnnouncement; +import de.dennisguse.opentracks.services.announcement.TTSManager; /** * @author Jimmy Shih @@ -33,7 +33,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set volume control stream for text to speech - setVolumeControlStream(VoiceAnnouncement.AUDIO_STREAM); + setVolumeControlStream(TTSManager.AUDIO_STREAM); setContentView(getRootView()); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index 50e8eb3637..c0b299e8c6 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -40,6 +40,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen private final ContentProviderUtils contentProviderUtils; private final Context context; + private final IdleObserver idleObserver; private final Handler handler; @@ -59,8 +60,9 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen private TrackPoint lastStoredTrackPoint; private TrackPoint lastStoredTrackPointWithLocation; - TrackRecordingManager(Context context, TrackPointCreator trackPointCreator, Handler handler) { + TrackRecordingManager(Context context, TrackPointCreator trackPointCreator, IdleObserver idleObserver, Handler handler) { this.context = context; + this.idleObserver = idleObserver; this.trackPointCreator = trackPointCreator; this.handler = handler; contentProviderUtils = new ContentProviderUtils(context); @@ -165,6 +167,8 @@ public Marker.Id insertMarker(String name, String category, String description, public void onIdle() { Log.d(TAG, "Becoming idle"); onNewTrackPoint(trackPointCreator.createIdle()); + + idleObserver.onIdle(); } /** @@ -270,6 +274,7 @@ private void insertTrackPointHelper(@NonNull TrackPoint trackPoint) { lastStoredTrackPointWithLocation = lastStoredTrackPoint; } } catch (SQLiteException e) { + // TODO Remove; if this is a problem; use a synchronized method. /* * Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY). * This is expected to happen extremely rarely (if our listener gets invoked twice at about the same time). @@ -299,4 +304,8 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin idleDuration = PreferencesUtils.getIdleDurationTimeout(); } } + + public interface IdleObserver { + void onIdle(); + } } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 2ef10e7201..cddd801328 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -47,7 +47,7 @@ import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.util.SystemUtils; -public class TrackRecordingService extends Service implements TrackPointCreator.Callback, SharedPreferences.OnSharedPreferenceChangeListener { +public class TrackRecordingService extends Service implements TrackPointCreator.Callback, SharedPreferences.OnSharedPreferenceChangeListener, TrackRecordingManager.IdleObserver { private static final String TAG = TrackRecordingService.class.getSimpleName(); @@ -108,7 +108,7 @@ public void onCreate() { recordingDataObservable = new MutableLiveData<>(NOT_RECORDING); trackPointCreator = new TrackPointCreator(this); - trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, handler); + trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this , handler); voiceAnnouncementManager = new VoiceAnnouncementManager(this); notificationManager = new TrackRecordingServiceNotificationManager(this); @@ -296,11 +296,15 @@ private void updateRecordingDataWhileRecording() { // Compute temporary track statistics using sensorData and update time. Pair> data = trackRecordingManager.getDataForUI(); - voiceAnnouncementManager.update(this, data.first); + voiceAnnouncementManager.announceStatisticsIfNeeded(data.first); recordingDataObservable.postValue(new RecordingData(data.first, data.second.first, data.second.second)); } + public void onIdle() { + voiceAnnouncementManager.announceIdle(); + } + @VisibleForTesting public void stopUpdateRecordingData() { handler.removeCallbacks(updateRecordingData); diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java b/src/main/java/de/dennisguse/opentracks/services/announcement/TTSManager.java similarity index 73% rename from src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java rename to src/main/java/de/dennisguse/opentracks/services/announcement/TTSManager.java index e8f10e7ade..2b3dc406de 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/TTSManager.java @@ -31,37 +31,19 @@ import java.util.Locale; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.data.ContentProviderUtils; -import de.dennisguse.opentracks.data.TrackPointIterator; -import de.dennisguse.opentracks.data.models.Distance; -import de.dennisguse.opentracks.data.models.Track; -import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.settings.PreferencesUtils; -import de.dennisguse.opentracks.stats.SensorStatistics; -import de.dennisguse.opentracks.stats.TrackStatistics; -import de.dennisguse.opentracks.ui.intervals.IntervalStatistics; -/** - * This class will announce the user's {@link TrackStatistics}. - * - * @author Sandor Dornbush - */ -public class VoiceAnnouncement { + +public class TTSManager { public final static int AUDIO_STREAM = TextToSpeech.Engine.DEFAULT_STREAM; - private static final String TAG = VoiceAnnouncement.class.getSimpleName(); + private static final String TAG = TTSManager.class.getSimpleName(); private final Context context; private final AudioManager audioManager; - private final ContentProviderUtils contentProviderUtils; - private TrackPoint.Id startTrackPointId = null; - - private IntervalStatistics intervalStatistics; - private Distance intervalDistance; - private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) { @@ -108,12 +90,9 @@ public void onError(String utteranceId) { private MediaPlayer ttsFallback; - VoiceAnnouncement(Context context) { + TTSManager(Context context) { this.context = context; audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - contentProviderUtils = new ContentProviderUtils(context); - intervalDistance = PreferencesUtils.getVoiceAnnouncementDistance(); - intervalStatistics = new IntervalStatistics(intervalDistance); } public void start() { @@ -139,7 +118,7 @@ public void start() { } } - public void announce(@NonNull Track track) { + public void announce(@NonNull Spannable announcement) { synchronized (this) { if (!ttsReady) { ttsReady = ttsInitStatus == TextToSpeech.SUCCESS; @@ -166,23 +145,6 @@ public void announce(@NonNull Track track) { return; } - Distance currentIntervalDistance = PreferencesUtils.getVoiceAnnouncementDistance(); - if (currentIntervalDistance != intervalDistance) { - intervalStatistics = new IntervalStatistics(currentIntervalDistance); - intervalDistance = currentIntervalDistance; - startTrackPointId = null; - } - - TrackPointIterator trackPointIterator = new TrackPointIterator(contentProviderUtils, track.getId(), startTrackPointId); - startTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator); - IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval(); - SensorStatistics sensorStatistics = null; - if (track.getId() != null) { - sensorStatistics = contentProviderUtils.getSensorStats(track.getId()); - } - - Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics); - if (announcement.length() > 0) { // We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called. tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used"); diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java index 0bfe185afc..7b5a4d5b06 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java @@ -17,6 +17,7 @@ import android.content.Context; import android.content.SharedPreferences; +import android.text.Spannable; import android.util.Log; import androidx.annotation.NonNull; @@ -27,11 +28,15 @@ import java.time.Duration; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.data.ContentProviderUtils; +import de.dennisguse.opentracks.data.TrackPointIterator; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Track; -import de.dennisguse.opentracks.services.TrackRecordingService; +import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.settings.PreferencesUtils; +import de.dennisguse.opentracks.stats.SensorStatistics; import de.dennisguse.opentracks.stats.TrackStatistics; +import de.dennisguse.opentracks.ui.intervals.IntervalStatistics; /** * Execute a periodic task on a time or distance schedule. @@ -42,9 +47,9 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe private static final String TAG = VoiceAnnouncementManager.class.getSimpleName(); - private final TrackRecordingService trackRecordingService; + private final Context context; - private VoiceAnnouncement voiceAnnouncement; + private TTSManager voiceAnnouncement; private TrackStatistics trackStatistics; @@ -58,12 +63,22 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe @NonNull private Duration nextTotalTime = TOTALTIME_OFF; - public VoiceAnnouncementManager(@NonNull TrackRecordingService trackRecordingService) { - this.trackRecordingService = trackRecordingService; + + private final ContentProviderUtils contentProviderUtils; + private TrackPoint.Id startTrackPointId = null; + + private IntervalStatistics intervalStatistics; + private Distance intervalDistance; + + public VoiceAnnouncementManager(@NonNull Context context) { + this.context = context; + contentProviderUtils = new ContentProviderUtils(context); + intervalDistance = PreferencesUtils.getVoiceAnnouncementDistance(); + intervalStatistics = new IntervalStatistics(intervalDistance); } public void start(@Nullable TrackStatistics trackStatistics) { - voiceAnnouncement = new VoiceAnnouncement(trackRecordingService); + voiceAnnouncement = new TTSManager(context); voiceAnnouncement.start(); update(trackStatistics); } @@ -74,10 +89,10 @@ void update(@Nullable TrackStatistics trackStatistics) { updateNextTaskDistance(); } - public void update(@NonNull Context context, @NonNull Track track) { + private boolean shouldNotAnnounce() { if (voiceAnnouncement == null) { Log.e(TAG, "Cannot update when in status shutdown."); - return; + return true; } if (!PreferencesUtils.shouldVoiceAnnouncementOnDeviceSpeaker() @@ -85,6 +100,26 @@ public void update(@NonNull Context context, @NonNull Track track) { .getSelectedRoute() .isDeviceSpeaker()) { Log.i(TAG, "No voice announcement on device speaker."); + return true; + } + + return false; + } + + public void announceIdle() { + if (shouldNotAnnounce()) { + return; + } + + if (!PreferencesUtils.shouldVoiceAnnouncementIdle()) { + return; + } + + voiceAnnouncement.announce(VoiceAnnouncementUtils.createIdle(context)); + } + + public void announceStatisticsIfNeeded(@NonNull Track track) { + if (shouldNotAnnounce()) { return; } @@ -100,10 +135,29 @@ public void update(@NonNull Context context, @NonNull Track track) { } if (announce) { - voiceAnnouncement.announce(track); + voiceAnnouncement.announce(createAnnouncement(track)); } } + private Spannable createAnnouncement(Track track) { + Distance currentIntervalDistance = PreferencesUtils.getVoiceAnnouncementDistance(); + if (currentIntervalDistance != intervalDistance) { + intervalStatistics = new IntervalStatistics(currentIntervalDistance); + intervalDistance = currentIntervalDistance; + startTrackPointId = null; + } + + TrackPointIterator trackPointIterator = new TrackPointIterator(contentProviderUtils, track.getId(), startTrackPointId); + startTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator); + IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval(); + SensorStatistics sensorStatistics = null; + if (track.getId() != null) { + sensorStatistics = contentProviderUtils.getSensorStats(track.getId()); + } + + return VoiceAnnouncementUtils.createStatistics(context, track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics); + } + public void stop() { if (voiceAnnouncement != null) { voiceAnnouncement.stop(); diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java index c06ec5db8c..801de5b2b0 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java @@ -33,7 +33,12 @@ class VoiceAnnouncementUtils { private VoiceAnnouncementUtils() { } - static Spannable getAnnouncement(Context context, TrackStatistics trackStatistics, UnitSystem unitSystem, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) { + static Spannable createIdle(Context context) { + return new SpannableStringBuilder() + .append(context.getString(R.string.voiceIdle)); + } + + static Spannable createStatistics(Context context, TrackStatistics trackStatistics, UnitSystem unitSystem, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) { SpannableStringBuilder builder = new SpannableStringBuilder(); Distance totalDistance = trackStatistics.getTotalDistance(); Speed averageMovingSpeed = trackStatistics.getAverageMovingSpeed(); diff --git a/src/main/java/de/dennisguse/opentracks/settings/AnnouncementsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/AnnouncementsSettingsFragment.java index a27e036a87..1bd67eb4ae 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/AnnouncementsSettingsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/settings/AnnouncementsSettingsFragment.java @@ -17,7 +17,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @Override public void onStart() { super.onStart(); - ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_announcements_title); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_announcements_statistics_title); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 5a1af8bc9d..9c40c4805a 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -312,6 +312,10 @@ public static boolean shouldVoiceAnnouncementOnDeviceSpeaker() { return getBoolean(R.string.voice_on_device_speaker_key, DEFAULT); } + public static boolean shouldVoiceAnnouncementIdle() { + return getBoolean(R.string.voice_announce_idle_key, true); + } + public static Duration getVoiceAnnouncementFrequency() { final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default)); int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT); diff --git a/src/main/res/values-b+es+419/strings.xml b/src/main/res/values-b+es+419/strings.xml index 9d16d1ac2e..93b03420c4 100644 --- a/src/main/res/values-b+es+419/strings.xml +++ b/src/main/res/values-b+es+419/strings.xml @@ -425,7 +425,7 @@ Filtrar Seleccionar diseño Importar/Exportar - Avisos de voz + Avisos de voz Tiempo, Distancia, Velocidad de la voz Importar, Exportar, Autoexportar Unidades diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 00f2a9a346..45afc9491d 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -488,7 +488,7 @@ limitations under the License. Import, export, automatický export Import/Export Čas, vzdálenost, rychlost hlasu - Hlasová oznámení + Hlasová oznámení Bluetooth senzory Časový interval, Interval vzdálenosti, Maximální vzdálenost, Přesnost Vzhled, Rozložení záznamu, Téma diff --git a/src/main/res/values-da/strings.xml b/src/main/res/values-da/strings.xml index aefa73f9d4..dcea99d811 100644 --- a/src/main/res/values-da/strings.xml +++ b/src/main/res/values-da/strings.xml @@ -458,7 +458,7 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj Foretrukne enheder, standardaktivitet, spor standardnavn Tidsinterval, Distanceinterval, Maksimal afstand, Nøjagtighed Cykelsensorer, Hjulomkreds, Løbesensorer - Stemmemeddelelser + Stemmemeddelelser Tid, afstand, stemmehastighed Import/eksport Import, eksport, automatisk eksport diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index f05d0d4f6c..aa6750f366 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -500,7 +500,7 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka Import/Export Bluetooth Sensoren Fahrradsensoren, Radumfang und Lauf-Sensoren - Sprachansagen + Sprachansagen Rückgängig Heute Gestern diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 5a717228fb..8f2cccc7b3 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -525,7 +525,7 @@ limitations under the License. Interfaz de usuario Apariencia, Diseño de grabación, Tema GPS - Avisos de voz + Avisos de voz Unidades Importar/Exportar Diseño predeterminado diff --git a/src/main/res/values-et/strings.xml b/src/main/res/values-et/strings.xml index b85dd0d9fe..b65720a53e 100644 --- a/src/main/res/values-et/strings.xml +++ b/src/main/res/values-et/strings.xml @@ -468,7 +468,7 @@ limitations under the License. Välimus, Salvestamise Paigutus, Teema Ajavahemik, vahemaa intervall, maksimaalne kaugus, täpsus Bluetooth Andurid - Häälteated + Häälteated Aeg, Kaugus, Hääle Kiirus Ühikud Import, Eksport, Automaatne Eksport diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index b41b5efde2..80da5df5fe 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -477,7 +477,7 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O %1$.1f mi/h (gomendatua) Bluetooth Denbora-tartea, Distantzia-tartea, Gehienezko Distantzia, Zehaztasuna - Ahots Iragarkiak + Ahots Iragarkiak Denbora, Distantzia, Ahotsaren Abiadura Inportatu/Esportatu Inportatu, Esportatu, Auto-Export diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml index f7df29ec08..f4d6c853f4 100644 --- a/src/main/res/values-fi/strings.xml +++ b/src/main/res/values-fi/strings.xml @@ -539,7 +539,7 @@ limitations under the License. Suositeltavat yksiköt, Oletusaktiviteetti, Seurannan oletusnimi Valitse\" Näytä kartalla\"käyttäytyminen Pyöräilyanturit, Pyörän ympärysmitta, Juoksuanturit - Ääni-ilmoitukset + Ääni-ilmoitukset Aika, etäisyys, äänennopeus Aikaväli, etäisyysväli, maksimietäisyys, tarkkuus \ No newline at end of file diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 54d27ff420..b32a67d6b7 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -526,7 +526,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio Importez, Exportez, Auto Export Importez/Exportez Temps, distance, vitesse de la voix - Annonces vocales + Annonces vocales Capteurs Bluetooth Intervalle de temps, Intervalle de distance, Distance maximale, Précision GPS diff --git a/src/main/res/values-ga/strings.xml b/src/main/res/values-ga/strings.xml index 4a9fdbd677..216ec7958c 100644 --- a/src/main/res/values-ga/strings.xml +++ b/src/main/res/values-ga/strings.xml @@ -352,7 +352,7 @@ Athshocraigh Socruithe go Luachanna Réamhshocraithe Eatramh ama Cuirfear gach socrú leagan amach ar ais chuig na luachanna réamhshocraithe. Ní scriosfaidh sé seo aon rianta ar an bhfeiste. - Fógraí Gutha + Fógraí Gutha Taispeáin staitisticí ar an scáileán glas Meánráta croí Athshocraigh do leagan amach\? diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 79d9da8af1..55b9ea2451 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -492,7 +492,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele Intervalo de tempo, Distancia intervalo, Max Distancia, Precisión Sensores Bluetooth Sensores de ciclismo, Circunferencia da roda, Sensores de carreira - Indicacións por Voz + Indicacións por Voz Importación/Exportación Importación, Exportación, Auto Exportación Unidades diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml index be72cd72cd..8305cc6a48 100644 --- a/src/main/res/values-hu/strings.xml +++ b/src/main/res/values-hu/strings.xml @@ -466,7 +466,7 @@ limitations under the License. Bluetooth Érzékelők Elbocsátás Kerékpáros Érzékelők, Kerék Kerülete, Futásérzékelők - Voice Közlemények + Voice Közlemények A fájlnév formátuma A rögzített adatokat nem osztják meg automatikusan más alkalmazásokkal. Eltávolítás diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 1096fb8d4e..13a2626857 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -489,7 +489,7 @@ Infatti, un\'applicazione deve supportare ACTION_VIEW con MIME applica GPS Intervallo di tempo, Intervallo di distanza, Massima Distanza, Precisione Sensori Bluetooth - Annunci Vocali + Annunci Vocali Tempo, Distanza, Velocità della Voce Importa/Esporta Importa, Esporta, Auto Esporta diff --git a/src/main/res/values-nb/strings.xml b/src/main/res/values-nb/strings.xml index cd2911194d..99414bc1e7 100644 --- a/src/main/res/values-nb/strings.xml +++ b/src/main/res/values-nb/strings.xml @@ -484,7 +484,7 @@ limitations under the License. GPS Tidsintervall, distanseintervall, maks, distanse, nøyaktighet Blåtannssensorer - Talemeldinger + Talemeldinger Tid, distanse, talehastighet Import/eksport Import, eksport, auto-eksport diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 63761ff6fc..61c508830e 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -532,7 +532,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid Tijdsinterval, afstandsinterval, maximale afstand, nauwkeurigheid Bluetooth sensoren Fiets sensoren, wielomtrek, loop sensoren - Gesproken berichten + Gesproken berichten Lay-outs Voer de naam van de lay-out in Lay-out naam bestaat al diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index c819c075c6..35f54a329f 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -474,7 +474,7 @@ limitations under the License. GPS Czujnik Bluetooth Czujniki rowerowe, Obwód koła, Czujniki biegowe - Wiadomości głosowe + Wiadomości głosowe Czas, Dystans, Głos prędkości Import/Eksport Jednostki diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 558c4a1f8b..e151accf10 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -428,7 +428,7 @@ %1$.1f km/h %1$.1f km/h (Recomendado) Relógio - Anúncios de voz + Anúncios de voz Voice announcements Sensores Bluetooth Intervalo de tempo, Intervalo de distância, Distância máxima, Precisão diff --git a/src/main/res/values-pt-rPT/strings.xml b/src/main/res/values-pt-rPT/strings.xml index 61f51c2793..07ae4326a8 100644 --- a/src/main/res/values-pt-rPT/strings.xml +++ b/src/main/res/values-pt-rPT/strings.xml @@ -428,7 +428,7 @@ Intervalo de tempo, Intervalo de distância, Distância máxima, Precisão Sensores Bluetooth Voice announcements - Anúncios de voz + Anúncios de voz Tempo, distância, velocidade da voz Importar/Exportar Importar, Exportar, Auto Export diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml index a0152c7305..ba5ae80cbe 100644 --- a/src/main/res/values-pt/strings.xml +++ b/src/main/res/values-pt/strings.xml @@ -460,7 +460,7 @@ limitations under the License. Voice announcements Aparência, Layout de gravação, Tema GPS - Anúncios de voz + Anúncios de voz Tempo, distância, velocidade da voz Importar/Exportar Importar, Exportar, Auto Export diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 5d255f783f..aedc4d000f 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -460,7 +460,7 @@ limitations under the License. Интервал времени и дистанции, Максимальная дистанция, Точность Bluetooth-датчики Датчики велосипеда, Оборот колеса, Датчики бега - Голосовые подсказки + Голосовые подсказки Импорт/Экспорт Импорт, Экспорт, Автоэкспорт Единицы diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index 8ca052929e..94bb957607 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -483,7 +483,7 @@ limitations under the License. Predvolené jednotky, predvolená aktivita, predvolený názov skladby Používateľské rozhranie Vzhľad, Rozloženie nahrávania, Téma - Hlasové oznámenia + Hlasové oznámenia GPS Senzory Bluetooth Cyklistické snímače, obvod kolies, snímače behu diff --git a/src/main/res/values-sl/strings.xml b/src/main/res/values-sl/strings.xml index 79d3571670..e0ff4f6e18 100644 --- a/src/main/res/values-sl/strings.xml +++ b/src/main/res/values-sl/strings.xml @@ -401,7 +401,7 @@ limitations under the License. s fotografijami Skladbe niso izvožene Ponastavitev postavitve po meri - Glasovna obvestila + Glasovna obvestila Čas, razdalja, hitrost glasu Uvoz/izvoz Privzete vrednosti dejavnosti diff --git a/src/main/res/values-tr/strings.xml b/src/main/res/values-tr/strings.xml index 330d82566f..27223c4d83 100644 --- a/src/main/res/values-tr/strings.xml +++ b/src/main/res/values-tr/strings.xml @@ -365,7 +365,7 @@ limitations under the License. GPS Tercih Edilen Birimler, Varsayılan Etkinlik, Varsayılan Parça Adı Zaman aralığı, Mesafe aralığı, Maks. Mesafe, Hassasiyet - Sesli Anonslar + Sesli Anonslar Zaman, Mesafe, Ses Hızı Sensör verileri, kaydedilen konum başına saklanır. Kalp atış hızı sensörü açısından bu, konum başına yalnızca bir kalp atış hızı ölçümünün depolandığı anlamına gelir. Hareketsiz duruyorsanız veya içerideyseniz, hiçbir sensör verisi kaydedilmeyecektir (ancak gösterilecektir). Yapabileceklerin: diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 8e3c1c8a46..0c98393d08 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -488,7 +488,7 @@ limitations under the License. Інтерфейс користувача Зовнішній вигляд, Макет запису, Тема Датчики Bluetooth - Голосові оголошення + Голосові оголошення Інтервал часу, інтервал відстані, Максимальна відстань, точність Датчики Їзди На Велосипеді, Окружність Колеса, Датчики Ходу Їзда на велосипеді diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index 0368129a2b..8af5e35d81 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -487,7 +487,7 @@ limitations under the License. GPS Cảm Biến Bluetooth Đi Xe Đạp Cảm Biến, Bánh Vòng, Chạy Bộ Cảm Biến - Giọng Nói Thông Báo + Giọng Nói Thông Báo Thời Gian, Khoảng Cách, Tốc Độ Giọng Nói Xuất Nhập Khẩu Hoàn tác diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 6d4f84db44..fbe67df628 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -332,7 +332,7 @@ limitations under the License. 時間間隔、距離間隔、最大距離、精度 藍芽感測器 自行車感測器、輪周、跑步感測器 - 語音播報 + 語音播報 時間、距離、語速 匯入/匯出 匯入、匯出、自動匯出 diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index e89b5cc8bc..42073fd0e8 100644 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -558,7 +558,7 @@ limitations under the License. 默认的单位和活动类型 蓝牙传感器 自行车传感器,车轮周长,跑步传感器 - 语音播报 + 语音播报 将设置重置为默认值 重置自定义布局 配速(分钟/海里) diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index f35f4499e2..9a71193661 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -237,12 +237,22 @@ voiceOnDeviceSpeaker true + voice_announce_idle_key + false + voiceAnnounceTotalDistance + true voiceAnnounceMovingTime + true voiceAnnounceAverageSpeedPace + true voiceAnnounceLapSpeedPace + true + voiceAnnounceAverageHeartRate + false voiceAnnounceLapHeartRate + false exportTrackFileFormat diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 7cfc916540..d45d88455f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -356,7 +356,11 @@ limitations under the License. Bluetooth Sensors Cycling Sensors, Wheel Circumference, Running Sensors - Voice Announcements + + Idle Announcements + Idle + + Statistics Announcements Time, Distance, Voice Speed Import/Export @@ -558,6 +562,7 @@ limitations under the License. Off Smallest (recommended) %1$d s (recommended) + Becoming idle. {n, plural, =1 {1 hour} diff --git a/src/main/res/xml/settings.xml b/src/main/res/xml/settings.xml index 69cb5e7e79..8cbe9b1e0b 100644 --- a/src/main/res/xml/settings.xml +++ b/src/main/res/xml/settings.xml @@ -31,7 +31,7 @@ android:icon="@drawable/ic_baseline_volume_up_24" android:key="@string/settings_announcements_key" android:summary="@string/settings_announcements_summary" - android:title="@string/settings_announcements_title" /> + android:title="@string/settings_announcements_statistics_title" /> + android:title="@string/settings_announcements_statistics_title"> - - - + - - - + + android:title="@string/settings_announcements_idle_title" /> + + + + + + + +