Voice announcement: added heart rate info to voice announcements

Also added setting to toggle announcement of heart rate

Defaults to off to preserve current behavior
This commit is contained in:
Jonas Kalderstam
2022-02-18 23:55:36 +01:00
parent d7fbae9321
commit 1075021e21
13 changed files with 177 additions and 50 deletions
@@ -125,7 +125,7 @@ public class TestDataUtil {
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
trackPoint.setHeartRate(100f + i);
trackPoint.setHeartRate(100f + i % 80);
trackPoint.setCadence(300f + i);
trackPoint.setPower(400f + i);
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
@@ -19,8 +19,11 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
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.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
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;
import de.dennisguse.opentracks.util.StringUtils;
@@ -34,6 +37,7 @@ public class VoiceAnnouncementUtilsTest {
@Before
public void setUp() {
contentProviderUtils = new ContentProviderUtils(context);
PreferencesUtils.setVoiceAnnounceHeartRate(false);
}
@Test
@@ -46,7 +50,7 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, null, null);
// then
assertEquals("total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
@@ -67,18 +71,10 @@ public class VoiceAnnouncementUtilsTest {
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval, null);
// then
Assert.assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
StringUtils.getSpeedParts(context, stats.getAverageMovingSpeed(), true, true).first +
" kilometers per hour Lap speed of " +
StringUtils.getSpeedParts(context, lastInterval.getSpeed(), true, true).first +
" kilometers per hour",
announcement);
assertEquals("total distance 14.21 kilometers in 16 minutes 39 seconds at 51.2 kilometers per hour Lap speed of 0.0 kilometers per hour", announcement);
}
@Test
@@ -91,7 +87,7 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, null, null);
// then
assertEquals("total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer", announcement);
@@ -112,18 +108,10 @@ public class VoiceAnnouncementUtilsTest {
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval, null);
// then
assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
buildAndGetTimeText(stats.getAverageMovingSpeed().toPace(true), true) +
" per kilometer Lap time of " +
buildAndGetTimeText(lastInterval.getSpeed().toPace(true), true) +
" per kilometer",
announcement);
assertEquals("total distance 14.21 kilometers in 16 minutes 39 seconds at 1 minute 10 seconds per kilometer Lap time of 0 minutes 0 seconds per kilometer", announcement);
}
@Test
@@ -136,7 +124,7 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, null, null);
// then
assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour", announcement);
@@ -157,18 +145,10 @@ public class VoiceAnnouncementUtilsTest {
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval, null);
// then
assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), false).first +
" miles in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
StringUtils.getSpeedParts(context, stats.getAverageMovingSpeed(), false, true).first +
" miles per hour Lap speed of " +
StringUtils.getSpeedParts(context, lastInterval.getSpeed(), false, true).first +
" miles per hour",
announcement);
assertEquals("total distance 8.83 miles in 16 minutes 39 seconds at 31.8 miles per hour Lap speed of 0.0 miles per hour", announcement);
}
@Test
@@ -181,7 +161,7 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, null, null);
// then
assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile", announcement);
@@ -202,19 +182,35 @@ public class VoiceAnnouncementUtilsTest {
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval, null);
// then
//assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), false).first +
" miles in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
buildAndGetTimeText(stats.getAverageMovingSpeed().toPace(false), true) +
" per mile Lap time of " +
buildAndGetTimeText(lastInterval.getSpeed().toPace(false), true) +
" per mile",
announcement);
assertEquals("total distance 8.83 miles in 16 minutes 39 seconds at 1 minute 53 seconds per mile Lap time of 0 minutes 0 seconds per mile", announcement);
}
@Test
public void getAnnouncement_heart_rate_and_sensor_statistics() {
PreferencesUtils.setVoiceAnnounceHeartRate(true);
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);
// then
assertEquals("total distance 14.21 kilometers in 16 minutes 39 seconds at 51.2 kilometers per hour Lap speed of 51.2 kilometers per hour Average heart rate 180 BPM Current heart rate 133 BPM", announcement);
}
/**
@@ -245,4 +241,4 @@ public class VoiceAnnouncementUtilsTest {
}
return res;
}
}
}
@@ -28,6 +28,7 @@ import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
/**
@@ -67,6 +68,7 @@ public class TrackStatisticsTest {
assertEquals(0.0, statistics.getMaxSpeed().toMPS(), 0.0);
assertEquals(0.0, statistics.getAverageSpeed().toMPS(), 0.0);
assertEquals(0.0, statistics.getAverageMovingSpeed().toMPS(), 0.0);
assertNull(statistics.getAverageHeartRate());
}
@Test
@@ -91,6 +93,8 @@ public class TrackStatisticsTest {
statistics.setMinAltitude(1200.0); // Resulting min altitude
statistics2.setMaxAltitude(3575.0); // Resulting max altitude
statistics2.setMinAltitude(2800.0);
statistics.setAverageHeartRate(HeartRate.of(100f));
statistics2.setAverageHeartRate(HeartRate.of(200f));
// when
statistics.merge(statistics2);
@@ -105,6 +109,7 @@ public class TrackStatisticsTest {
assertEquals(Speed.of(statistics.getTotalDistance(), statistics.getMovingTime()).toMPS(), statistics.getMaxSpeed().toMPS(), 0.001);
assertEquals(1200.0, statistics.getMinAltitude(), 0.001);
assertEquals(3575.0, statistics.getMaxAltitude(), 0.001);
assertEquals(150.0, statistics.getAverageHeartRate().getBPM(), 0.001);
}
@Test
@@ -40,6 +40,7 @@ public class TrackStatisticsUpdaterTest {
assertNull(statistics.getTotalAltitudeGain());
assertNull(statistics.getTotalAltitudeLoss());
assertNull(statistics.getAverageHeartRate());
}
@Test
@@ -66,6 +67,7 @@ public class TrackStatisticsUpdaterTest {
assertNull(statistics.getTotalAltitudeGain());
assertNull(statistics.getTotalAltitudeLoss());
assertNull(statistics.getAverageHeartRate());
}
@Test
@@ -91,6 +93,7 @@ public class TrackStatisticsUpdaterTest {
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(9.84, statistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(106.85f, statistics.getAverageHeartRate().getBPM(), 0.01);
}
@Test
@@ -34,6 +34,7 @@ import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track;
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;
@@ -171,8 +172,12 @@ public class VoiceAnnouncement {
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.one(isMetricUnits));
intervalStatistics.addTrackPoints(trackPointIterator);
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
SensorStatistics sensorStatistics = null;
if (track.getId() != null) {
sensorStatistics = contentProviderUtils.getSensorStats(track.getId());
}
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), isMetricUnits, isReportSpeed, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), isMetricUnits, 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");
@@ -1,5 +1,7 @@
package de.dennisguse.opentracks.services.announcement;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceHeartRate;
import android.content.Context;
import androidx.annotation.Nullable;
@@ -9,6 +11,7 @@ import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
@@ -17,7 +20,14 @@ class VoiceAnnouncementUtils {
private VoiceAnnouncementUtils() {
}
static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
static String getAnnouncement(
Context context,
TrackStatistics trackStatistics,
boolean isMetricUnits,
boolean isReportSpeed,
@Nullable IntervalStatistics.Interval currentInterval,
@Nullable SensorStatistics sensorStatistics
) {
Distance distance = trackStatistics.getTotalDistance();
Speed distancePerTime = trackStatistics.getAverageMovingSpeed();
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
@@ -32,6 +42,7 @@ class VoiceAnnouncementUtils {
String rate;
String currentRate;
String currentRateMsg;
String heartRateMsg = "";
if (isReportSpeed) {
int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
double speedInUnit = distancePerTime.to(isMetricUnits);
@@ -53,7 +64,21 @@ class VoiceAnnouncementUtils {
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, trackStatistics.getMovingTime()), rate) + currentRateMsg;
if (shouldVoiceAnnounceHeartRate()) {
if (sensorStatistics != null && sensorStatistics.hasHeartRate()) {
heartRateMsg = context.getString(R.string.average_heart_rate, Math.round(sensorStatistics.getAvgHeartRate().getBPM()));
}
if (currentInterval != null && currentInterval.hasAverageHeartRate()) {
if (!heartRateMsg.isEmpty()) {
heartRateMsg += " ";
}
heartRateMsg += context.getString(R.string.current_heart_rate, Math.round(currentInterval.getAverageHeartRate().getBPM()));
}
}
heartRateMsg = heartRateMsg.isEmpty() ? "" : " " + heartRateMsg;
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, trackStatistics.getMovingTime()), rate) + currentRateMsg + heartRateMsg;
}
//TODO We might need to localize this using strings.xml if order is relevant.
@@ -172,6 +172,13 @@ public class PreferencesUtils {
setString(keyId, resources.getString(valueId));
}
@VisibleForTesting
public static void setBoolean(int keyId, Boolean value) {
Editor editor = sharedPreferences.edit();
editor.putBoolean(getKey(keyId), value);
editor.apply();
}
static void setInt(int keyId, int value) {
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(keyId), value);
@@ -316,6 +323,15 @@ 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);
}
@VisibleForTesting
public static void setVoiceAnnounceHeartRate(boolean value) {
setBoolean(R.string.voice_announce_heart_rate_key, value);
}
public static Distance getRecordingDistanceInterval() {
return Distance.of(getInt(R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefaultInternal()));
}
@@ -25,6 +25,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -56,6 +57,8 @@ public class TrackStatistics {
private Speed maxSpeed;
private Float totalAltitudeGain_m = null;
private Float totalAltitudeLoss_m = null;
// The average heart rate seen on this track
private HeartRate avgHeartRate = null;
public TrackStatistics() {
reset();
@@ -76,6 +79,7 @@ public class TrackStatistics {
altitudeExtremities.set(other.altitudeExtremities.getMin(), other.altitudeExtremities.getMax());
totalAltitudeGain_m = other.totalAltitudeGain_m;
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
avgHeartRate = other.avgHeartRate;
}
@VisibleForTesting
@@ -108,6 +112,19 @@ public class TrackStatistics {
stopTime = stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime;
}
if (avgHeartRate == null) {
avgHeartRate = other.avgHeartRate;
} else {
if (other.avgHeartRate != null) {
// Using total time as weights for the averaging.
// Important to do this before total time is updated
avgHeartRate = HeartRate.of(
(totalTime.getSeconds() * avgHeartRate.getBPM() + other.totalTime.getSeconds() * other.avgHeartRate.getBPM())
/ (totalTime.getSeconds() + other.totalTime.getSeconds())
);
}
}
totalDistance = totalDistance.plus(other.totalDistance);
totalTime = totalTime.plus(other.totalTime);
movingTime = movingTime.plus(other.movingTime);
@@ -230,6 +247,15 @@ public class TrackStatistics {
return totalTime.minus(movingTime);
}
public boolean hasAverageHeartRate() {
return avgHeartRate != null;
}
@Nullable
public HeartRate getAverageHeartRate() {
return avgHeartRate;
}
/**
* Gets the average speed.
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
@@ -287,6 +313,12 @@ public class TrackStatistics {
}
}
public void setAverageHeartRate(HeartRate heartRate) {
if (heartRate != null) {
avgHeartRate = heartRate;
}
}
public boolean hasTotalAltitudeGain() {
return totalAltitudeGain_m != null;
}
@@ -26,6 +26,7 @@ import java.util.List;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -64,6 +65,8 @@ public class TrackStatisticsUpdater {
private final AltitudeRingBuffer altitudeBuffer;
private final SpeedRingBuffer speedBuffer;
private float averageHeartRateBPM;
private Duration totalHeartRateDuration = Duration.ZERO;
// The current segment's statistics
private final TrackStatistics currentSegment;
@@ -85,6 +88,7 @@ public class TrackStatisticsUpdater {
altitudeBuffer = new AltitudeRingBuffer(ALTITUDE_SMOOTHING_FACTOR);
speedBuffer = new SpeedRingBuffer(SPEED_SMOOTHING_FACTOR);
resetAverageHeartRate();
}
public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) {
@@ -95,6 +99,7 @@ public class TrackStatisticsUpdater {
this.speedBuffer = new SpeedRingBuffer(toCopy.speedBuffer);
this.lastTrackPoint = toCopy.lastTrackPoint;
resetAverageHeartRate();
}
public TrackStatistics getTrackStatistics() {
@@ -142,6 +147,17 @@ public class TrackStatisticsUpdater {
currentSegment.updateAltitudeExtremities(newAverage);
}
// Update heart rate
if (trackPoint.hasHeartRate() && lastTrackPoint != null) {
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Duration newTotalDuration = totalHeartRateDuration.plus(trackPointDuration);
averageHeartRateBPM = (totalHeartRateDuration.toMillis() * averageHeartRateBPM + trackPointDuration.toMillis() * trackPoint.getHeartRate().getBPM()) / newTotalDuration.toMillis();
totalHeartRateDuration = newTotalDuration;
currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM));
}
// Update total distance
if (trackPoint.hasSensorDistance()) {
// Sensor-based distance/speed
@@ -184,6 +200,12 @@ public class TrackStatisticsUpdater {
lastTrackPoint = null;
altitudeBuffer.reset();
speedBuffer.reset();
resetAverageHeartRate();
}
private void resetAverageHeartRate() {
averageHeartRateBPM = 0.0f;
totalHeartRateDuration = Duration.ZERO;
}
/**
@@ -8,6 +8,7 @@ import java.util.List;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -102,6 +103,7 @@ public class IntervalStatistics {
private Duration time = Duration.ofSeconds(0);
private Float gain_m;
private Float loss_m;
private HeartRate avgHeartRate;
public Interval() {
}
@@ -117,6 +119,7 @@ public class IntervalStatistics {
time = i.time;
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
}
public Interval(Interval i) {
@@ -124,6 +127,7 @@ public class IntervalStatistics {
time = i.time;
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
}
private void adjust(double adjustFactor) {
@@ -155,11 +159,20 @@ public class IntervalStatistics {
return loss_m;
}
public boolean hasAverageHeartRate() {
return avgHeartRate != null;
}
public HeartRate getAverageHeartRate() {
return avgHeartRate;
}
private void add(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
distance = distance.plus(trackStatistics.getTotalDistance());
time = time.plus(trackStatistics.getTotalTime());
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
if (lastTrackPoint == null) {
return;
}
@@ -176,6 +189,7 @@ public class IntervalStatistics {
time = trackStatistics.getTotalTime();
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
}
}
}
+1
View File
@@ -283,6 +283,7 @@
<item>1.2</item>
<item>1.3</item>
</string-array>
<string name="voice_announce_heart_rate_key" translatable="false">voiceAnnounceHeartRate</string>
<string name="export_trackfileformat_key" translatable="false">exportTrackFileFormat</string>
<!-- See TrackFileFormat -->
+3
View File
@@ -405,6 +405,7 @@ limitations under the License.
<string name="settings_announcements">Announcements</string>
<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_reset">Reset</string>
<!-- Custom Layout -->
@@ -582,6 +583,8 @@ limitations under the License.
<item quantity="one">total distance 1 mile</item>
<item quantity="other">total distance %1$.2f miles</item>
</plurals>
<string name="average_heart_rate">Average heart rate %1$d BPM</string>
<string name="current_heart_rate">Current heart rate %1$d BPM</string>
<!-- Waypoint Type -->
<string name="waypoint_type_atm">ATM</string>
<string name="waypoint_type_bank">bank</string>
@@ -26,4 +26,9 @@
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" />
</PreferenceScreen>