Announcements: can be triggered by time and distance (instead of either).

Fixes #921.
This commit is contained in:
Dennis Guse
2021-08-29 20:22:31 +02:00
parent 9cb3d8c1bb
commit 8f2d1722ad
65 changed files with 169 additions and 205 deletions
@@ -77,7 +77,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
private static final String TAG = TrackListActivity.class.getSimpleName();
// The following are set in onCreate
// The following are setFrequency in onCreate
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private ResourceCursorAdapter resourceCursorAdapter;
@@ -66,7 +66,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
// The following are set in onCreate.
// The following are setFrequency in onCreate.
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
@@ -51,7 +51,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
// The following are set in onCreate
// The following are setFrequency in onCreate
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -108,7 +108,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
};
/**
* A runnable that will set the orange pointer as appropriate and redraw.
* A runnable that will setFrequency the orange pointer as appropriate and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
@@ -73,7 +73,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null);
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
// The following variables are set in onCreate:
// The following variables are setFrequency in onCreate:
private VoiceAnnouncementManager voiceAnnouncementManager;
private TrackRecordingServiceNotificationManager notificationManager;
@@ -104,20 +104,21 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
voiceAnnouncementManager.setMetricUnits(metricUnits);
notificationManager.setMetricUnits(metricUnits);
}
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
voiceAnnouncementManager.setTaskFrequency(PreferencesUtils.getVoiceFrequency(sharedPreferences, context));
if (PreferencesUtils.isKey(context, R.string.voice_announcement_frequency_key, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency(sharedPreferences, context));
}
if (PreferencesUtils.isKey(context, new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementDistance(sharedPreferences, context));
}
trackPointCreator.onSharedPreferenceChanged(sharedPreferences, key);
trackRecordingManager.onSharedPreferenceChanged(sharedPreferences, key);
}
};
// The following variables are set when recording:
// The following variables are setFrequency when recording:
private WakeLock wakeLock;
private final Binder binder = new Binder();
@@ -40,48 +40,35 @@ public class VoiceAnnouncementManager {
private final TrackRecordingService trackRecordingService;
private final int TASK_FREQUENCY_OFF;
/**
* The task frequency.
* A positive value is a time frequency (minutes).
* A negative value is a distance frequency (km or mi).
* A zero value is to turn off periodic task.
*/
private int taskFrequency;
private VoiceAnnouncement voiceAnnouncement;
private boolean metricUnits;
private TrackStatistics trackStatistics;
private static final Distance DISTANCE_OFF = Distance.of(Double.MAX_VALUE);
private Distance distanceFrequency = DISTANCE_OFF;
@NonNull
private Distance nextTotalDistance = DISTANCE_OFF;
private static final Duration TOTALTIME_OFF = Duration.ofMillis(Long.MAX_VALUE);
private Duration totalTimeFrequency = TOTALTIME_OFF;
@NonNull
private Duration nextTotalTime = TOTALTIME_OFF;
public VoiceAnnouncementManager(@NonNull TrackRecordingService trackRecordingService) {
this.trackRecordingService = trackRecordingService;
TASK_FREQUENCY_OFF = Integer.parseInt(trackRecordingService.getString(R.string.frequency_off));
taskFrequency = TASK_FREQUENCY_OFF;
}
public void restore(@Nullable TrackStatistics trackStatistics) {
if (taskFrequency == TASK_FREQUENCY_OFF) {
Log.d(TAG, "Task frequency is off.");
return;
}
voiceAnnouncement = new VoiceAnnouncement(trackRecordingService);
voiceAnnouncement.start();
if (isTimeFrequency()) {
nextTotalTime = trackStatistics != null ? calculateNextDuration(trackStatistics) : TOTALTIME_OFF;
} else {
// For distance periodic task
updateNextTaskDistance(trackStatistics);
this.trackStatistics = trackStatistics;
if (!totalTimeFrequency.isZero()) {
nextTotalTime = calculateNextDuration();
}
if (!distanceFrequency.isZero()) {
nextTotalDistance = calculateNextTaskDistance();
}
}
@@ -90,14 +77,15 @@ public class VoiceAnnouncementManager {
Log.e(TAG, "Cannot update when in status shutdown.");
return;
}
boolean announce = false;
this.trackStatistics = track.getTrackStatistics();
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
updateNextTaskDistance(trackStatistics);
nextTotalDistance = calculateNextTaskDistance();
announce = true;
}
if (!trackStatistics.getTotalTime().minus(nextTotalTime).isNegative()) {
nextTotalTime = calculateNextDuration(trackStatistics);
nextTotalTime = calculateNextDuration();
announce = true;
}
@@ -113,59 +101,48 @@ public class VoiceAnnouncementManager {
}
}
public void setTaskFrequency(int taskFrequency) {
this.taskFrequency = taskFrequency;
public void setFrequency(Duration frequency) {
this.totalTimeFrequency = frequency;
restore(this.trackStatistics);
}
public void setMetricUnits(boolean metricUnits) {
this.metricUnits = metricUnits;
updateNextTaskDistance(this.trackStatistics);
}
private void updateNextTaskDistance(TrackStatistics trackStatistics) {
if (!isDistanceFrequency()) {
nextTotalDistance = DISTANCE_OFF;
Log.d(TAG, "SplitManager: Distance splits disabled.");
return;
}
if (trackStatistics == null) {
return;
}
nextTotalDistance = calculateNextTaskDistance(trackStatistics);
public void setFrequency(Distance frequency) {
this.distanceFrequency = frequency;
restore(this.trackStatistics);
}
@VisibleForTesting
public Distance calculateNextTaskDistance(@NonNull TrackStatistics trackStatistics) {
public Distance calculateNextTaskDistance() {
if (trackStatistics == null) {
return DISTANCE_OFF;
}
Distance distance = trackStatistics.getTotalDistance();
Distance announcementInterval = Distance.one(metricUnits).multipliedBy(Math.abs(taskFrequency));
int index = (int) (distance.dividedBy(announcementInterval));
return announcementInterval.multipliedBy(index + 1);
int index = (int) (distance.dividedBy(distanceFrequency));
return distanceFrequency.multipliedBy(index + 1);
}
private Duration calculateNextDuration(@Nullable TrackStatistics trackStatistics) {
private Duration calculateNextDuration() {
if (trackStatistics == null) {
return null;
return TOTALTIME_OFF;
}
if (!isTimeFrequency()) {
throw new RuntimeException("Using distance frequency as time frequency is impossible.");
}
Duration totalTime = trackStatistics.getTotalTime();
Duration interval = Duration.ofMinutes(taskFrequency);
Duration intervalMod = Duration.ofMillis(trackStatistics.getTotalTime().toMillis() % interval.toMillis());
Duration intervalMod = Duration.ofMillis(trackStatistics.getTotalTime().toMillis() % totalTimeFrequency.toMillis());
return totalTime.plus(interval.minus(intervalMod));
return totalTime.plus(totalTimeFrequency.minus(intervalMod));
}
private boolean isTimeFrequency() {
return taskFrequency > 0;
@VisibleForTesting
@NonNull
public Duration getNextTotalTime() {
return nextTotalTime;
}
private boolean isDistanceFrequency() {
return taskFrequency < 0;
@VisibleForTesting
@NonNull
public Distance getNextTotalDistance() {
return nextTotalDistance;
}
}
@@ -3,12 +3,10 @@ package de.dennisguse.opentracks.settings;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.documentfile.provider.DocumentFile;
@@ -286,8 +284,11 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
ListPreference voiceFrequency = findPreference(getString(R.string.voice_announcement_frequency_key));
voiceFrequency.setEntries(StringUtils.getAnnouncementFrequency(getActivity()));
ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key));
voiceDistance.setEntries(StringUtils.getAnnouncementDistance(getActivity(), metricUnits));
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
@@ -90,6 +90,15 @@ public class PreferencesUtils {
return key == null || key.equals(getKey(context, keyId));
}
public static boolean isKey(Context context, int[] keyIds, String key) {
for(int keyId : keyIds) {
if (isKey(context, keyId, key)) {
return true;
}
}
return false;
}
private static boolean getBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean defaultValue) {
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
}
@@ -216,13 +225,23 @@ public class PreferencesUtils {
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static int getVoiceFrequency(SharedPreferences sharedPreferences, Context context) {
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
public static Duration getVoiceAnnouncementFrequency(SharedPreferences sharedPreferences, Context context) {
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_announcement_frequency_default));
int value = getInt(sharedPreferences, context, R.string.voice_announcement_frequency_key, DEFAULT);
return Duration.ofSeconds(value);
}
/**
* @return Result depends on isMetricUnits
*/
public static Distance getVoiceAnnouncementDistance(SharedPreferences sharedPreferences, Context context) {
final float DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_announcement_distance_default));
float value = getFloat(sharedPreferences, context, R.string.voice_announcement_distance_key, DEFAULT);
return Distance.one(isMetricUnits(sharedPreferences, context)).multipliedBy(value);
}
public static float getVoiceSpeedRate(SharedPreferences sharedPreferences, Context context) {
final float DEFAULT = Float.parseFloat(context.getResources().getString(R.string.voice_frequency_default));
final float DEFAULT = Float.parseFloat(context.getResources().getString(R.string.voice_speed_rate_default));
return getFloat(sharedPreferences, context, R.string.voice_speed_rate_key, DEFAULT);
}
@@ -292,28 +292,34 @@ public class StringUtils {
}
}
/**
* Gets the frequency display options.
*
* @param context the context
* @param metricUnits true to display in metric units
*/
public static String[] getFrequencyOptions(Context context, boolean metricUnits) {
String[] values = context.getResources().getStringArray(R.array.frequency_values);
public static String[] getAnnouncementFrequency(Context context) {
String[] values = context.getResources().getStringArray(R.array.voice_announcement_frequency_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
if (context.getString(R.string.frequency_off).equals(values[i])) {
if (context.getString(R.string.announcement_off).equals(values[i])) {
options[i] = context.getString(R.string.value_off);
} else if (value < 0) {
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, Math.abs(value));
} else {
int value = Integer.parseInt(values[i]);
options[i] = context.getString(R.string.value_integer_minute, value);
}
}
return options;
}
public static String[] getAnnouncementDistance(Context context, boolean metricUnits) {
String[] values = context.getResources().getStringArray(R.array.voice_announcement_distance_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (context.getString(R.string.announcement_off).equals(values[i])) {
options[i] = context.getString(R.string.value_off);
} else {
int value = Integer.parseInt(values[i]);
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, value);
}
}
return options;
}
/**
* @return the formatted altitude_m (or null) and it's unit as {@link Pair}
*/