forked from upstream-mirrors/OpenTracks
Voice announcements: added settings for what to announce
This commit is contained in:
committed by
Dennis Guse
parent
14b705b43e
commit
00351136f2
+39
-2
@@ -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() {
|
||||
|
||||
+53
-45
@@ -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() {
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user