mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-03 09:53:06 +02:00
Merge pull request #1132 from spacecowboy/voiceHeartRate
Voice announcements: configurable, TtsSpan, and heart rate.
This commit is contained in:
@@ -21,6 +21,7 @@ import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.text.Spannable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -34,6 +35,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 +173,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);
|
||||
Spannable 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");
|
||||
|
||||
+125
-36
@@ -1,7 +1,14 @@
|
||||
package de.dennisguse.opentracks.services.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceHeartRate;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.TtsSpan;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -9,6 +16,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,68 +25,149 @@ class VoiceAnnouncementUtils {
|
||||
private VoiceAnnouncementUtils() {
|
||||
}
|
||||
|
||||
static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
|
||||
static Spannable getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) {
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
Distance distance = trackStatistics.getTotalDistance();
|
||||
Speed distancePerTime = trackStatistics.getAverageMovingSpeed();
|
||||
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
|
||||
int perUnitStringId = isMetricUnits ? R.string.voice_per_kilometer : R.string.voice_per_mile;
|
||||
|
||||
int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||
double distanceInUnit = distance.toKM_Miles(isMetricUnits);
|
||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distanceInUnit), distanceInUnit);
|
||||
int distanceId = isMetricUnits ? R.plurals.voiceDistanceKilometers : R.plurals.voiceDistanceMiles;
|
||||
|
||||
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, isMetricUnits ? "kilometer" : "mile");
|
||||
// Punctuation helps introduce natural pauses in TTS
|
||||
builder.append(".");
|
||||
if (distance.isZero()) {
|
||||
return totalDistance;
|
||||
return builder;
|
||||
}
|
||||
|
||||
// Announce time
|
||||
Duration movingTime = trackStatistics.getMovingTime();
|
||||
if (!movingTime.isZero()) {
|
||||
appendDuration(context, builder, movingTime);
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
String rate;
|
||||
String currentRate;
|
||||
String currentRateMsg;
|
||||
if (isReportSpeed) {
|
||||
int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
||||
double speedInUnit = distancePerTime.to(isMetricUnits);
|
||||
rate = context.getResources().getQuantityString(speedId, getQuantityCount(speedInUnit), speedInUnit);
|
||||
|
||||
double currentDistancePerTimeInUnit = currentDistancePerTime != null ? currentDistancePerTime.to(isMetricUnits) : 0;
|
||||
currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTimeInUnit), currentDistancePerTimeInUnit);
|
||||
currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate);
|
||||
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, isMetricUnits ? "kilometer per hour" : "mile per hour");
|
||||
builder.append(".");
|
||||
|
||||
if (currentDistancePerTime != null) {
|
||||
double currentDistancePerTimeInUnit = currentDistancePerTime.to(isMetricUnits);
|
||||
|
||||
if (currentDistancePerTimeInUnit > 0) {
|
||||
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_speed));
|
||||
long currentDistanceIntegerPart = (long) currentDistancePerTimeInUnit;
|
||||
// Extract the decimal part
|
||||
String currentDistanceFractionalPart = String.format("%.1f", (currentDistancePerTimeInUnit - currentDistanceIntegerPart)).substring(2);
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTimeInUnit), currentDistancePerTimeInUnit), currentDistanceIntegerPart, currentDistanceFractionalPart, isMetricUnits ? "kilometer per hour" : "mile per hour");
|
||||
builder.append(".");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Duration time = distancePerTime.toPace(isMetricUnits);
|
||||
|
||||
int paceId = isMetricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||
rate = context.getString(paceId, getAnnounceTime(context, time));
|
||||
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(isMetricUnits) : Duration.ofMillis(0);
|
||||
currentRate = context.getString(paceId, getAnnounceTime(context, currentTime));
|
||||
currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate);
|
||||
if (!currentTime.isZero()) {
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_time));
|
||||
appendDuration(context, builder, currentTime);
|
||||
builder.append(" ")
|
||||
.append(context.getString(perUnitStringId))
|
||||
.append(".");
|
||||
}
|
||||
}
|
||||
|
||||
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
|
||||
if (shouldVoiceAnnounceHeartRate()) {
|
||||
if (sensorStatistics != null && sensorStatistics.hasHeartRate()) {
|
||||
int averageHeartRate = Math.round(sensorStatistics.getAvgHeartRate().getBPM());
|
||||
|
||||
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, trackStatistics.getMovingTime()), rate) + currentRateMsg;
|
||||
}
|
||||
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(".");
|
||||
}
|
||||
|
||||
//TODO We might need to localize this using strings.xml if order is relevant.
|
||||
private static String getAnnounceTime(Context context, Duration duration) {
|
||||
String result = "";
|
||||
if (currentInterval != null && currentInterval.hasAverageHeartRate()) {
|
||||
int currentHeartRate = Math.round(currentInterval.getAverageHeartRate().getBPM());
|
||||
|
||||
int hours = (int) (duration.toHours());
|
||||
int minutes = (int) (duration.toMinutes() % 60);
|
||||
int seconds = (int) (duration.getSeconds() % 60);
|
||||
|
||||
if (hours != 0) {
|
||||
String hoursText = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceHours, hours, hours);
|
||||
result += hoursText + " ";
|
||||
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(".");
|
||||
}
|
||||
}
|
||||
String minutesText = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceMinutes, minutes, minutes);
|
||||
String secondsText = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceSeconds, seconds, seconds);
|
||||
|
||||
return result + minutesText + " " + secondsText;
|
||||
return builder;
|
||||
}
|
||||
|
||||
static int getQuantityCount(double d) {
|
||||
return (int) d;
|
||||
}
|
||||
|
||||
private static void appendDuration(@NonNull Context context, @NonNull SpannableStringBuilder builder, @NonNull Duration duration) {
|
||||
int hours = (int) (duration.toHours());
|
||||
int minutes = (int) (duration.toMinutes() % 60);
|
||||
int seconds = (int) (duration.getSeconds() % 60);
|
||||
|
||||
if (hours > 0) {
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceHours, hours, hours), hours, null, "hour");
|
||||
}
|
||||
if (minutes > 0) {
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceMinutes, minutes, minutes), minutes, null, "minute");
|
||||
}
|
||||
if (seconds > 0 || duration.isZero()) {
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceSeconds, seconds, seconds), seconds, null, "second");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speaks as: 98.14 [UNIT] - ninety eight point one four [UNIT with correct plural form]
|
||||
*/
|
||||
private static void appendDecimalUnit(@NonNull SpannableStringBuilder builder, @NonNull String localizedText, long integerPart, @Nullable String fractionalPart, @NonNull String unit) {
|
||||
TtsSpan.MeasureBuilder measureBuilder = new TtsSpan.MeasureBuilder()
|
||||
.setUnit(unit);
|
||||
|
||||
if (fractionalPart == null) {
|
||||
measureBuilder.setNumber(integerPart);
|
||||
} else {
|
||||
measureBuilder.setIntegerPart(integerPart)
|
||||
.setFractionalPart(fractionalPart);
|
||||
}
|
||||
|
||||
builder.append(" ")
|
||||
.append(localizedText, measureBuilder.build(), SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speaks as: 98 - ninety eight
|
||||
*/
|
||||
private static void appendCardinal(@NonNull SpannableStringBuilder builder, @NonNull String localizedText, long number) {
|
||||
builder.append(" ")
|
||||
.append(localizedText, new TtsSpan.CardinalBuilder().setNumber(number).build(), SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user