Voice announcements: added settings for what to announce

This commit is contained in:
Jonas Kalderstam
2022-02-20 14:42:46 +01:00
committed by Dennis Guse
parent 14b705b43e
commit 00351136f2
7 changed files with 187 additions and 59 deletions
@@ -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<Track.Id, TrackStatistics> 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<Track.Id, TrackStatistics> 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);
}
}
@@ -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() {
@@ -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;
@@ -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() {
+6 -1
View File
@@ -291,7 +291,12 @@
<item>1.2</item>
<item>1.3</item>
</string-array>
<string name="voice_announce_heart_rate_key" translatable="false">voiceAnnounceHeartRate</string>
<string name="voice_announce_total_distance_key" translatable="false">voiceAnnounceTotalDistance</string>
<string name="voice_announce_total_time_key" translatable="false">voiceAnnounceTotalTime</string>
<string name="voice_announce_average_speed_pace_key" translatable="false">voiceAnnounceAverageSpeedPace</string>
<string name="voice_announce_lap_speed_pace_key" translatable="false">voiceAnnounceLapSpeedPace</string>
<string name="voice_announce_average_heart_rate_key" translatable="false">voiceAnnounceAverageHeartRate</string>
<string name="voice_announce_lap_heart_rate_key" translatable="false">voiceAnnounceLapHeartRate</string>
<string name="export_trackfileformat_key" translatable="false">exportTrackFileFormat</string>
<!-- See TrackFileFormat -->
+4 -1
View File
@@ -395,7 +395,10 @@ limitations under the License.
<string name="settings_announcements_totaltime_frequency">Time interval</string>
<string name="settings_announcements_distance_frequency">Distance interval</string>
<string name="settings_announcements_report_heart_rate">Report heart rate</string>
<string name="settings_announcements_average_heart_rate">Average heart rate</string>
<string name="settings_announcements_lap_heart_rate">Lap heart rate</string>
<string name="settings_announcements_average_speed_pace">Average speed/pace</string>
<string name="settings_announcements_lap_speed_pace">Lap speed/pace</string>
<!-- Custom Layout -->
<string name="custom_layout_list_title">Layouts</string>
+32 -4
View File
@@ -26,9 +26,37 @@
android:title="@string/menu_voice_rate"
app:useSimpleSummaryProvider="true" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/voice_announce_heart_rate_key"
android:title="@string/settings_announcements_report_heart_rate" />
<PreferenceCategory android:title="@string/settings_announcements">
<SwitchPreference
android:defaultValue="true"
android:key="@string/voice_announce_total_distance_key"
android:title="@string/total_distance" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/voice_announce_total_time_key"
android:title="@string/stats_total_time" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/voice_announce_average_speed_pace_key"
android:title="@string/settings_announcements_average_speed_pace" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/voice_announce_lap_speed_pace_key"
android:title="@string/settings_announcements_lap_speed_pace" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/voice_announce_average_heart_rate_key"
android:title="@string/settings_announcements_average_heart_rate" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/voice_announce_lap_heart_rate_key"
android:title="@string/settings_announcements_lap_heart_rate" />
</PreferenceCategory>
</PreferenceScreen>