forked from upstream-mirrors/OpenTracks
@@ -20,7 +20,7 @@ import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public final class SensorDataSet {
|
||||
public class SensorDataSet {
|
||||
|
||||
private static final String TAG = SensorDataSet.class.getSimpleName();
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
// Compute temporary track statistics using sensorData and update time.
|
||||
Pair<Track, Pair<TrackPoint, SensorDataSet>> data = trackRecordingManager.getDataForUI();
|
||||
|
||||
voiceAnnouncementManager.announceStatisticsIfNeeded(data.first);
|
||||
voiceAnnouncementManager.announceStatisticsIfNeeded(data.first, data.second.second);
|
||||
|
||||
recordingDataObservable.postValue(new RecordingData(data.first, data.second.first, data.second.second));
|
||||
}
|
||||
|
||||
+5
-4
@@ -33,6 +33,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.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -118,7 +119,7 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
voiceAnnouncement.speak(VoiceAnnouncementUtils.createIdle(context));
|
||||
}
|
||||
|
||||
public void announceStatisticsIfNeeded(@NonNull Track track) {
|
||||
public void announceStatisticsIfNeeded(@NonNull Track track, @NonNull SensorDataSet sensorDataSet) {
|
||||
if (shouldNotAnnounce()) {
|
||||
return;
|
||||
}
|
||||
@@ -135,11 +136,11 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
}
|
||||
|
||||
if (announce) {
|
||||
voiceAnnouncement.speak(createAnnouncement(track));
|
||||
voiceAnnouncement.speak(createAnnouncement(track, sensorDataSet));
|
||||
}
|
||||
}
|
||||
|
||||
private Spannable createAnnouncement(Track track) {
|
||||
private Spannable createAnnouncement(Track track, SensorDataSet sensorDataSet) {
|
||||
Distance currentIntervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
|
||||
if (currentIntervalDistance != intervalDistance) {
|
||||
intervalStatistics = new IntervalStatistics(currentIntervalDistance);
|
||||
@@ -155,7 +156,7 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(track.getId());
|
||||
}
|
||||
|
||||
return VoiceAnnouncementUtils.createStatistics(context, track, PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics);
|
||||
return VoiceAnnouncementUtils.createStatistics(context, track, sensorDataSet, PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
|
||||
+74
-60
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.services.announcement;
|
||||
import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageHeartRate;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceHeartRateCurrent;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapPower;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace;
|
||||
@@ -17,6 +18,7 @@ import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.TtsSpan;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -29,8 +31,10 @@ import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
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.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -47,7 +51,7 @@ class VoiceAnnouncementUtils {
|
||||
.append(context.getString(R.string.voiceIdle));
|
||||
}
|
||||
|
||||
static Spannable createStatistics(Context context, Track track, UnitSystem unitSystem, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) {
|
||||
static Spannable createStatistics(Context context, Track track, SensorDataSet sensorDataSet, UnitSystem unitSystem, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) {
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
@@ -104,92 +108,102 @@ class VoiceAnnouncementUtils {
|
||||
// Punctuation helps introduce natural pauses in TTS
|
||||
builder.append(".");
|
||||
}
|
||||
if (totalDistance.isZero()) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
boolean hasTravelledDistance = !totalDistance.isZero();
|
||||
|
||||
// Announce time
|
||||
Duration movingTime = trackStatistics.getMovingTime();
|
||||
if (shouldVoiceAnnounceMovingTime() && !movingTime.isZero()) {
|
||||
if (shouldVoiceAnnounceMovingTime() && hasTravelledDistance && !movingTime.isZero()) {
|
||||
appendDuration(context, builder, movingTime);
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
if (isReportSpeed) {
|
||||
if (shouldVoiceAnnounceAverageSpeedPace()) {
|
||||
double speedInUnit = averageMovingSpeed.to(unitSystem);
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.speed));
|
||||
String template = context.getResources().getString(speedId);
|
||||
appendDecimalUnit(builder, MessageFormat.format(template, Map.of("n", speedInUnit)), speedInUnit, 1, unitSpeedTTS);
|
||||
builder.append(".");
|
||||
}
|
||||
if (shouldVoiceAnnounceLapSpeedPace() && currentDistancePerTime != null) {
|
||||
double currentDistancePerTimeInUnit = currentDistancePerTime.to(unitSystem);
|
||||
if (currentDistancePerTimeInUnit > 0) {
|
||||
if (hasTravelledDistance) {
|
||||
if (isReportSpeed) {
|
||||
if (shouldVoiceAnnounceAverageSpeedPace()) {
|
||||
double speedInUnit = averageMovingSpeed.to(unitSystem);
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_speed));
|
||||
.append(context.getString(R.string.speed));
|
||||
String template = context.getResources().getString(speedId);
|
||||
appendDecimalUnit(builder, MessageFormat.format(template, Map.of("n", currentDistancePerTimeInUnit)), currentDistancePerTimeInUnit, 1, unitSpeedTTS);
|
||||
appendDecimalUnit(builder, MessageFormat.format(template, Map.of("n", speedInUnit)), speedInUnit, 1, unitSpeedTTS);
|
||||
builder.append(".");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (shouldVoiceAnnounceAverageSpeedPace()) {
|
||||
Duration time = averageMovingSpeed.toPace(unitSystem);
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.pace));
|
||||
appendDuration(context, builder, time);
|
||||
builder.append(" ")
|
||||
.append(context.getString(perUnitStringId))
|
||||
.append(".");
|
||||
}
|
||||
if (shouldVoiceAnnounceLapSpeedPace() && currentDistancePerTime != null) {
|
||||
double currentDistancePerTimeInUnit = currentDistancePerTime.to(unitSystem);
|
||||
if (currentDistancePerTimeInUnit > 0) {
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_speed));
|
||||
String template = context.getResources().getString(speedId);
|
||||
appendDecimalUnit(builder, MessageFormat.format(template, Map.of("n", currentDistancePerTimeInUnit)), currentDistancePerTimeInUnit, 1, unitSpeedTTS);
|
||||
builder.append(".");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (shouldVoiceAnnounceAverageSpeedPace()) {
|
||||
Duration time = averageMovingSpeed.toPace(unitSystem);
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.pace));
|
||||
appendDuration(context, builder, time);
|
||||
builder.append(" ")
|
||||
.append(context.getString(perUnitStringId))
|
||||
.append(".");
|
||||
}
|
||||
|
||||
if (shouldVoiceAnnounceLapSpeedPace() && currentDistancePerTime != null) {
|
||||
Duration currentTime = currentDistancePerTime.toPace(unitSystem);
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_time));
|
||||
appendDuration(context, builder, currentTime);
|
||||
builder.append(" ")
|
||||
.append(context.getString(perUnitStringId))
|
||||
.append(".");
|
||||
if (shouldVoiceAnnounceLapSpeedPace() && currentDistancePerTime != null) {
|
||||
Duration currentTime = currentDistancePerTime.toPace(unitSystem);
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_time));
|
||||
appendDuration(context, builder, currentTime);
|
||||
builder.append(" ")
|
||||
.append(context.getString(perUnitStringId))
|
||||
.append(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldVoiceAnnounceAverageHeartRate() && sensorStatistics != null && sensorStatistics.hasHeartRate()) {
|
||||
int averageHeartRate = Math.round(sensorStatistics.avgHeartRate().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(".");
|
||||
}
|
||||
if (shouldVoiceAnnounceLapHeartRate() && currentInterval != null && currentInterval.hasAverageHeartRate()) {
|
||||
int currentHeartRate = Math.round(currentInterval.getAverageHeartRate().getBPM());
|
||||
Pair<HeartRate, String> heartrate = sensorDataSet.getHeartRate();
|
||||
if (shouldVoiceAnnounceHeartRateCurrent() && heartrate != null && heartrate.first != null) { //TODO Check has an announcable value?
|
||||
int averageHeartRate = Math.round(heartrate.first.getBPM());
|
||||
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.current_heart_rate));
|
||||
appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, currentHeartRate), currentHeartRate);
|
||||
appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, averageHeartRate), averageHeartRate);
|
||||
builder.append(".");
|
||||
}
|
||||
if (hasTravelledDistance) {
|
||||
if (shouldVoiceAnnounceAverageHeartRate() && sensorStatistics != null && sensorStatistics.hasHeartRate()) {
|
||||
int averageHeartRate = Math.round(sensorStatistics.avgHeartRate().getBPM());
|
||||
|
||||
if (shouldVoiceAnnounceLapPower() && currentInterval != null && currentInterval.hasAveragePower()) {
|
||||
int currentPower = Math.round(currentInterval.getAveragePower().getW());
|
||||
if(shouldVoiceAnnounceUnit()) {
|
||||
String template = context.getResources().getString(R.string.power_x_watt);
|
||||
builder.append(" ")
|
||||
.append(MessageFormat.format(template, Map.of("x", currentPower)));
|
||||
} else {
|
||||
builder.append(" ").append(context.getString(R.string.power)).append(" ");
|
||||
builder.append(String.valueOf(currentPower));
|
||||
.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(".");
|
||||
}
|
||||
if (shouldVoiceAnnounceLapHeartRate() && currentInterval != null && currentInterval.hasAverageHeartRate()) {
|
||||
int currentHeartRate = Math.round(currentInterval.getAverageHeartRate().getBPM());
|
||||
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_heart_rate));
|
||||
appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, currentHeartRate), currentHeartRate);
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
if (shouldVoiceAnnounceLapPower() && currentInterval != null && currentInterval.hasAveragePower()) {
|
||||
int currentPower = Math.round(currentInterval.getAveragePower().getW());
|
||||
if (shouldVoiceAnnounceUnit()) {
|
||||
String template = context.getResources().getString(R.string.power_x_watt);
|
||||
builder.append(" ")
|
||||
.append(MessageFormat.format(template, Map.of("x", currentPower)));
|
||||
} else {
|
||||
builder.append(" ").append(context.getString(R.string.power)).append(" ");
|
||||
builder.append(String.valueOf(currentPower));
|
||||
}
|
||||
builder.append(".");
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
//TODO TtsSpan.TimeBuilder?
|
||||
private static void appendDuration(@NonNull Context context, @NonNull SpannableStringBuilder builder, @NonNull Duration duration) {
|
||||
int hours = (int) (duration.toHours());
|
||||
int minutes = (int) (duration.toMinutes() % 60);
|
||||
|
||||
@@ -433,14 +433,23 @@ public class PreferencesUtils {
|
||||
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);
|
||||
}
|
||||
|
||||
public static boolean shouldVoiceAnnounceLapPower() {
|
||||
return getBoolean(R.string.voice_announce_lap_power_key, false);
|
||||
}
|
||||
|
||||
public static boolean shouldVoiceAnnounceHeartRateCurrent() {
|
||||
return getBoolean(R.string.voice_announce_heart_rate_current_key, false);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setVoiceAnnounceHeartRateCurrent(boolean value) {
|
||||
setBoolean(R.string.voice_announce_heart_rate_current_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);
|
||||
|
||||
Reference in New Issue
Block a user