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 7e74d523f..38c34bda9 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java @@ -42,7 +42,13 @@ public class VoiceAnnouncementUtilsTest { @Before public void setUp() { contentProviderUtils = new ContentProviderUtils(context); - PreferencesUtils.setVoiceAnnounceHeartRate(false); + + PreferencesUtils.setVoiceAnnounceLapHeartRate(false); + PreferencesUtils.setVoiceAnnounceAverageHeartRate(false); + PreferencesUtils.setVoiceAnnounceTotalDistance(true); + PreferencesUtils.setVoiceAnnounceTotalTime(true); + PreferencesUtils.setVoiceAnnounceAverageSpeedPace(true); + PreferencesUtils.setVoiceAnnounceLapSpeedPace(true); } @Test @@ -199,7 +205,8 @@ public class VoiceAnnouncementUtilsTest { @Test public void getAnnouncement_heart_rate_and_sensor_statistics() { - PreferencesUtils.setVoiceAnnounceHeartRate(true); + PreferencesUtils.setVoiceAnnounceAverageHeartRate(true); + PreferencesUtils.setVoiceAnnounceLapHeartRate(true); int numberOfPoints = 1000; Pair trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints); @@ -221,4 +228,34 @@ public class VoiceAnnouncementUtilsTest { // then assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement); } + + @Test + public void getAnnouncement_only_lap_heart_rate() { + PreferencesUtils.setVoiceAnnounceLapHeartRate(true); + PreferencesUtils.setVoiceAnnounceAverageHeartRate(false); + PreferencesUtils.setVoiceAnnounceTotalDistance(false); + PreferencesUtils.setVoiceAnnounceTotalTime(false); + PreferencesUtils.setVoiceAnnounceAverageSpeedPace(false); + PreferencesUtils.setVoiceAnnounceLapSpeedPace(false); + + int numberOfPoints = 1000; + Pair trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints); + Track.Id trackId = trackWithStats.first; + TrackStatistics stats = trackWithStats.second; + IntervalStatistics.Interval lastInterval; + try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) { + assertEquals(trackPointIterator.getCount(), numberOfPoints); + IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000)); + intervalStatistics.addTrackPoints(trackPointIterator); + lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1); + } + + SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null); + + // when + String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval, sensorStatistics).toString(); + + // then + assertEquals(" Current heart rate 132 bpm,", announcement); + } } diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java index a17338385..caa1ec80c 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java @@ -181,8 +181,10 @@ public class VoiceAnnouncement { Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), unitSystem, isReportSpeed, lastInterval, sensorStatistics); - // 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"); + 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"); + } } public void 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 a665920c4..64aa88583 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java @@ -1,7 +1,12 @@ package de.dennisguse.opentracks.services.announcement; import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE; -import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceHeartRate; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageHeartRate; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalTime; import android.content.Context; import android.text.Spannable; @@ -58,38 +63,42 @@ class VoiceAnnouncementUtils { double distanceInUnit = distance.toKM_Miles(unitSystem); - builder.append(context.getString(R.string.total_distance)); - long distanceIntegerPart = (long) distanceInUnit; - // Extract the decimal part - String distanceFractionalPart = String.format("%.2f", (distanceInUnit - distanceIntegerPart)).substring(2); - // Units should always be english singular for TTS. - // See https://developer.android.com/reference/android/text/style/TtsSpan?hl=en#TYPE_MEASURE - appendDecimalUnit(builder, context.getResources().getQuantityString(distanceId, getQuantityCount(distanceInUnit), distanceInUnit), distanceIntegerPart, distanceFractionalPart, unitDistanceTTS); - // Punctuation helps introduce natural pauses in TTS - builder.append("."); + if (shouldVoiceAnnounceTotalDistance()) { + builder.append(context.getString(R.string.total_distance)); + long distanceIntegerPart = (long) distanceInUnit; + // Extract the decimal part + String distanceFractionalPart = String.format("%.2f", (distanceInUnit - distanceIntegerPart)).substring(2); + // Units should always be english singular for TTS. + // See https://developer.android.com/reference/android/text/style/TtsSpan?hl=en#TYPE_MEASURE + appendDecimalUnit(builder, context.getResources().getQuantityString(distanceId, getQuantityCount(distanceInUnit), distanceInUnit), distanceIntegerPart, distanceFractionalPart, unitDistanceTTS); + // Punctuation helps introduce natural pauses in TTS + builder.append("."); + } if (distance.isZero()) { return builder; } // Announce time Duration movingTime = trackStatistics.getMovingTime(); - if (!movingTime.isZero()) { + if (shouldVoiceAnnounceTotalTime() && !movingTime.isZero()) { appendDuration(context, builder, movingTime); builder.append("."); } if (isReportSpeed) { - double speedInUnit = distancePerTime.to(unitSystem); + if (shouldVoiceAnnounceAverageSpeedPace()) { + double speedInUnit = distancePerTime.to(unitSystem); - builder.append(" ") - .append(context.getString(R.string.speed)); - long speedIntegerPart = (long) speedInUnit; - // Extract the decimal part - String speedFractionalPart = String.format("%.1f", (speedInUnit - speedIntegerPart)).substring(2); - appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(speedInUnit), speedInUnit), speedIntegerPart, speedFractionalPart, unitSpeedTTS); - builder.append("."); + builder.append(" ") + .append(context.getString(R.string.speed)); + long speedIntegerPart = (long) speedInUnit; + // Extract the decimal part + String speedFractionalPart = String.format("%.1f", (speedInUnit - speedIntegerPart)).substring(2); + appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(speedInUnit), speedInUnit), speedIntegerPart, speedFractionalPart, unitSpeedTTS); + builder.append("."); + } - if (currentDistancePerTime != null) { + if (shouldVoiceAnnounceLapSpeedPace() && currentDistancePerTime != null) { double currentDistancePerTimeInUnit = currentDistancePerTime.to(unitSystem); if (currentDistancePerTimeInUnit > 0) { @@ -104,16 +113,18 @@ class VoiceAnnouncementUtils { } } } else { - Duration time = distancePerTime.toPace(unitSystem); - builder.append(" ") - .append(context.getString(R.string.pace)); - appendDuration(context, builder, time); - builder.append(" ") - .append(context.getString(perUnitStringId)) - .append("."); + if (shouldVoiceAnnounceAverageSpeedPace()) { + Duration time = distancePerTime.toPace(unitSystem); + builder.append(" ") + .append(context.getString(R.string.pace)); + appendDuration(context, builder, time); + builder.append(" ") + .append(context.getString(perUnitStringId)) + .append("."); + } - Duration currentTime = currentDistancePerTime != null ? currentDistancePerTime.toPace(unitSystem) : Duration.ofMillis(0); - if (!currentTime.isZero()) { + if (shouldVoiceAnnounceLapSpeedPace() && currentDistancePerTime != null) { + Duration currentTime = currentDistancePerTime.toPace(unitSystem); builder.append(" ") .append(context.getString(R.string.lap_time)); appendDuration(context, builder, currentTime); @@ -123,24 +134,21 @@ class VoiceAnnouncementUtils { } } - if (shouldVoiceAnnounceHeartRate()) { - if (sensorStatistics != null && sensorStatistics.hasHeartRate()) { - int averageHeartRate = Math.round(sensorStatistics.getAvgHeartRate().getBPM()); + if (shouldVoiceAnnounceAverageHeartRate() && sensorStatistics != null && sensorStatistics.hasHeartRate()) { + int averageHeartRate = Math.round(sensorStatistics.getAvgHeartRate().getBPM()); - builder.append(" ") - .append(context.getString(R.string.average_heart_rate)); - appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, averageHeartRate), averageHeartRate); - builder.append("."); - } + builder.append(" ") + .append(context.getString(R.string.average_heart_rate)); + appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, averageHeartRate), averageHeartRate); + builder.append("."); + } + if (shouldVoiceAnnounceLapHeartRate() && currentInterval != null && currentInterval.hasAverageHeartRate()) { + int currentHeartRate = Math.round(currentInterval.getAverageHeartRate().getBPM()); - if (currentInterval != null && currentInterval.hasAverageHeartRate()) { - int currentHeartRate = Math.round(currentInterval.getAverageHeartRate().getBPM()); - - builder.append(" ") - .append(context.getString(R.string.current_heart_rate)); - appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, currentHeartRate), currentHeartRate); - builder.append("."); - } + builder.append(" ") + .append(context.getString(R.string.current_heart_rate)); + appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, currentHeartRate), currentHeartRate); + builder.append("."); } return builder; diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index e866f2da2..5e9bd270e 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -343,13 +343,58 @@ public class PreferencesUtils { return getFloat(R.string.voice_speed_rate_key, DEFAULT); } - public static boolean shouldVoiceAnnounceHeartRate() { - return getBoolean(R.string.voice_announce_heart_rate_key, false); + public static boolean shouldVoiceAnnounceTotalDistance() { + return getBoolean(R.string.voice_announce_total_distance_key, true); } @VisibleForTesting - public static void setVoiceAnnounceHeartRate(boolean value) { - setBoolean(R.string.voice_announce_heart_rate_key, value); + public static void setVoiceAnnounceTotalDistance(boolean value) { + setBoolean(R.string.voice_announce_total_distance_key, value); + } + + public static boolean shouldVoiceAnnounceTotalTime() { + return getBoolean(R.string.voice_announce_total_time_key, true); + } + + @VisibleForTesting + public static void setVoiceAnnounceTotalTime(boolean value) { + setBoolean(R.string.voice_announce_total_time_key, value); + } + + public static boolean shouldVoiceAnnounceAverageSpeedPace() { + return getBoolean(R.string.voice_announce_average_speed_pace_key, true); + } + + @VisibleForTesting + public static void setVoiceAnnounceAverageSpeedPace(boolean value) { + setBoolean(R.string.voice_announce_average_speed_pace_key, value); + } + + public static boolean shouldVoiceAnnounceLapSpeedPace() { + return getBoolean(R.string.voice_announce_lap_speed_pace_key, true); + } + + @VisibleForTesting + public static void setVoiceAnnounceLapSpeedPace(boolean value) { + setBoolean(R.string.voice_announce_lap_speed_pace_key, value); + } + + public static boolean shouldVoiceAnnounceLapHeartRate() { + return getBoolean(R.string.voice_announce_lap_heart_rate_key, false); + } + + @VisibleForTesting + public static void setVoiceAnnounceLapHeartRate(boolean value) { + setBoolean(R.string.voice_announce_lap_heart_rate_key, value); + } + + public static boolean shouldVoiceAnnounceAverageHeartRate() { + return getBoolean(R.string.voice_announce_average_heart_rate_key, false); + } + + @VisibleForTesting + public static void setVoiceAnnounceAverageHeartRate(boolean value) { + setBoolean(R.string.voice_announce_average_heart_rate_key, value); } public static Distance getRecordingDistanceInterval() { diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 273c25088..937ac30cc 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -291,7 +291,12 @@ 1.2 1.3 - voiceAnnounceHeartRate + voiceAnnounceTotalDistance + voiceAnnounceTotalTime + voiceAnnounceAverageSpeedPace + voiceAnnounceLapSpeedPace + voiceAnnounceAverageHeartRate + voiceAnnounceLapHeartRate exportTrackFileFormat diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ccae52a32..b72119a5f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -395,7 +395,10 @@ limitations under the License. Time interval Distance interval - Report heart rate + Average heart rate + Lap heart rate + Average speed/pace + Lap speed/pace Layouts diff --git a/src/main/res/xml/settings_announcements.xml b/src/main/res/xml/settings_announcements.xml index b8789dbdb..a3c1f17cf 100644 --- a/src/main/res/xml/settings_announcements.xml +++ b/src/main/res/xml/settings_announcements.xml @@ -26,9 +26,37 @@ android:title="@string/menu_voice_rate" app:useSimpleSummaryProvider="true" /> - + + + + + + + + + + + + + + \ No newline at end of file