Added settings for what to announce
This commit is contained in:
Dennis Guse
2022-06-06 17:04:15 +02:00
committed by GitHub
19 changed files with 187 additions and 71 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.setVoiceAnnounceMovingTime(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.setVoiceAnnounceMovingTime(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.shouldVoiceAnnounceMovingTime;
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 (shouldVoiceAnnounceMovingTime() && !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 shouldVoiceAnnounceMovingTime() {
return getBoolean(R.string.voice_announce_moving_time_key, true);
}
@VisibleForTesting
public static void setVoiceAnnounceMovingTime(boolean value) {
setBoolean(R.string.voice_announce_moving_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() {
-1
View File
@@ -520,6 +520,5 @@
</plurals>
<string name="average_heart_rate">Ritmo cardíaco promedio</string>
<string name="current_heart_rate">Ritmo cardíaco actual</string>
<string name="settings_announcements_report_heart_rate">Anunciar ritmo cardíaco</string>
<string name="settings_export_filename_title">Formato del nombre del archivo</string>
</resources>
-1
View File
@@ -546,7 +546,6 @@ limitations under the License.
<string name="export_dir_not_writable">" (nelze zapisovat!)"</string>
<string name="generic_today">Dnes</string>
<string name="no_compatible_file_manager_installed">Není nainstalován kompatibilní správce souborů</string>
<string name="settings_announcements_report_heart_rate">Hlásit tepovou frekvenci</string>
<string name="voice_per_kilometer">za kilometr</string>
<string name="lap_time">Čas kola</string>
<string name="lap_speed">Rychlost kola</string>
-1
View File
@@ -554,7 +554,6 @@ limitations under the License.
<string name="settings_public_api_dashboard_enabled_title">Transferencia automática de datos</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Una aplicación que ha iniciado una grabación puede también acceder a los datos grabados.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Los datos grabados no se compartirán automáticamente con otras aplicaciones.</string>
<string name="settings_announcements_report_heart_rate">Anunciar ritmo cardíaco</string>
<string name="voice_per_kilometer">por kilometro</string>
<string name="voice_per_mile">por milla</string>
<string name="lap_time">Tiempo de vuelta</string>
-1
View File
@@ -542,7 +542,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="export_cannot_write_to_dir">Impossible d\'écrire dans le répertoire d\'exportation.</string>
<string name="export_dir_not_writable">" (non inscriptible !)"</string>
<string name="no_compatible_file_manager_installed">Aucun gestionnaire de fichiers compatible n\'est installé</string>
<string name="settings_announcements_report_heart_rate">Rapport de la fréquence cardiaque</string>
<string name="settings_public_api_enabled_title">API publique</string>
<string name="average_heart_rate">Fréquence cardiaque moyenne</string>
<string name="current_heart_rate">Fréquence cardiaque actuelle</string>
-1
View File
@@ -549,7 +549,6 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="export_dir_not_writable">" (non escribible!)"</string>
<string name="no_compatible_file_manager_installed">Non compatible xestor de ficheiros instalados</string>
<string name="settings_api_title">API pública</string>
<string name="settings_announcements_report_heart_rate">Informar do pulso</string>
<string name="settings_public_api_enabled_title">API pública</string>
<string name="settings_public_api_disabled_toast">API pública de OpenTracks desactivada: pode activarse nos axustes.</string>
<string name="lap_time">Tempo da volta</string>
-1
View File
@@ -550,7 +550,6 @@ limitations under the License.
<string name="settings_public_api_dashboard_enabled_title">Automatisk dataoverføring</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Et program som startet et opptak kan også få tilgang til de registrerte dataene.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Registrerte data blir ikke delt automatisk med andre programmer.</string>
<string name="settings_announcements_report_heart_rate">Rapporter puls</string>
<string name="voice_per_kilometer">per kilometer</string>
<string name="voice_per_mile">per engelsk mil</string>
<string name="lap_time">Rundetid</string>
-1
View File
@@ -499,7 +499,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<string name="settings_public_api_dashboard_enabled_summary_on">Een app die een opname start, heeft ook toegang tot de opgenomen gegevens.</string>
<string name="stats_sensors_cadence">Cadans</string>
<string name="stats_sensors_heart_rate">Hartslag</string>
<string name="settings_announcements_report_heart_rate">Hartslag melden</string>
<string name="settings_layout_reset">Aangepaste lay-out terugzetten</string>
<string name="settings_layout_reset_confirm_message">Alle lay-out instellingen worden teruggezet naar de standaardwaarden. Tracks worden niet gewist.</string>
<string name="settings_public_api_enabled_summary_on">Andere geïnstalleerde apps kunnen opnames starten of stoppen.</string>
-1
View File
@@ -508,7 +508,6 @@
<item quantity="other">%1$.2f quilômetros</item>
</plurals>
<string name="generic_dismiss">Dispensar</string>
<string name="settings_announcements_report_heart_rate">Informar frequência cardíaca</string>
<string name="settings_api_title">API pública</string>
<string name="settings_public_api_dashboard_enabled_title">Transferência automática de dados</string>
<string name="voice_per_kilometer">por quilômetro</string>
-1
View File
@@ -532,7 +532,6 @@ limitations under the License.
<string name="settings_reset_summary">Сбросить настройки</string>
<string name="settings_recording_idle_speed_title">Порог скорости холостого хода</string>
<string name="settings_generic_sensor">Обычные</string>
<string name="settings_announcements_report_heart_rate">Сообщать пульс</string>
<string name="custom_layout_select_layout">Выбрать раскладку</string>
<string name="stats_custom_layout_default_layout">Раскладка по умолчанию</string>
<string name="settings_layout_reset_confirm_title">Сбросить свои раскладки\?</string>
-1
View File
@@ -386,7 +386,6 @@ limitations under the License.
<string name="settings_cycling_sensor">Kolesarjenje</string>
<string name="settings_running_sensor">Teče</string>
<string name="track_recording_notification_accuracy">Natančnost lokacije: %1$s</string>
<string name="settings_announcements_report_heart_rate">Poročanje o srčnem utripu</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="location_coordinate">%1$s°</string>
<string name="track_distance_notification">Razdalja: %1$s</string>
-1
View File
@@ -512,7 +512,6 @@ limitations under the License.
<string name="location_coordinate">%1$s°</string>
<string name="settings_all_reset">Sıfırla</string>
<string name="settings_generic_sensor">Genel</string>
<string name="settings_announcements_report_heart_rate">Kalp atış hızını bildir</string>
<string name="custom_layout_list_edit_name">Eklemek için dizilimin adını yazın</string>
<string name="settings_layout_reset_confirm_message">Tüm dizilim ayarları varsayılan değerlere döndürülecektir. Bu cihazdaki hiçbir parçayı silmez.</string>
<string name="settings_running_sensor">Koşu</string>
-1
View File
@@ -535,7 +535,6 @@ limitations under the License.
<string name="settings_public_api_enabled_title">API công khai</string>
<string name="settings_public_api_enabled_summary_off">Chỉ OpenTracks có thể bắt đầu và dừng ghi.</string>
<string name="settings_public_api_enabled_summary_on">Các ứng dụng khác có thể bắt đầu hoặc dừng ghi.</string>
<string name="settings_announcements_report_heart_rate">Báo cáo nhịp tim</string>
<string name="settings_api_title">API công khai</string>
<string name="generic_dismiss">Bỏ</string>
<string name="gps_recording_status">%1$s: %2$s</string>
+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_moving_time_key" translatable="false">voiceAnnounceMovingTime</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_title">
<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_moving_time_key"
android:title="@string/stats_moving_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>