mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 17:43:06 +02:00
@@ -21,7 +21,7 @@ import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import de.dennisguse.opentracks.services.announcement.VoiceAnnouncement;
|
||||
import de.dennisguse.opentracks.services.announcement.TTSManager;
|
||||
|
||||
/**
|
||||
* @author Jimmy Shih
|
||||
@@ -33,7 +33,7 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set volume control stream for text to speech
|
||||
setVolumeControlStream(VoiceAnnouncement.AUDIO_STREAM);
|
||||
setVolumeControlStream(TTSManager.AUDIO_STREAM);
|
||||
|
||||
setContentView(getRootView());
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final Context context;
|
||||
private final IdleObserver idleObserver;
|
||||
|
||||
private final Handler handler;
|
||||
|
||||
@@ -59,8 +60,9 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
private TrackPoint lastStoredTrackPoint;
|
||||
private TrackPoint lastStoredTrackPointWithLocation;
|
||||
|
||||
TrackRecordingManager(Context context, TrackPointCreator trackPointCreator, Handler handler) {
|
||||
TrackRecordingManager(Context context, TrackPointCreator trackPointCreator, IdleObserver idleObserver, Handler handler) {
|
||||
this.context = context;
|
||||
this.idleObserver = idleObserver;
|
||||
this.trackPointCreator = trackPointCreator;
|
||||
this.handler = handler;
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
@@ -165,6 +167,8 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
public void onIdle() {
|
||||
Log.d(TAG, "Becoming idle");
|
||||
onNewTrackPoint(trackPointCreator.createIdle());
|
||||
|
||||
idleObserver.onIdle();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,6 +274,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
lastStoredTrackPointWithLocation = lastStoredTrackPoint;
|
||||
}
|
||||
} catch (SQLiteException e) {
|
||||
// TODO Remove; if this is a problem; use a synchronized method.
|
||||
/*
|
||||
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
||||
* This is expected to happen extremely rarely (if our listener gets invoked twice at about the same time).
|
||||
@@ -299,4 +304,8 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
idleDuration = PreferencesUtils.getIdleDurationTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IdleObserver {
|
||||
void onIdle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
public class TrackRecordingService extends Service implements TrackPointCreator.Callback, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public class TrackRecordingService extends Service implements TrackPointCreator.Callback, SharedPreferences.OnSharedPreferenceChangeListener, TrackRecordingManager.IdleObserver {
|
||||
|
||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||
|
||||
@@ -108,7 +108,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
|
||||
|
||||
trackPointCreator = new TrackPointCreator(this);
|
||||
trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, handler);
|
||||
trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this , handler);
|
||||
|
||||
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
|
||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||
@@ -296,11 +296,15 @@ 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.update(this, data.first);
|
||||
voiceAnnouncementManager.announceStatisticsIfNeeded(data.first);
|
||||
|
||||
recordingDataObservable.postValue(new RecordingData(data.first, data.second.first, data.second.second));
|
||||
}
|
||||
|
||||
public void onIdle() {
|
||||
voiceAnnouncementManager.announceIdle();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void stopUpdateRecordingData() {
|
||||
handler.removeCallbacks(updateRecordingData);
|
||||
|
||||
+5
-43
@@ -31,37 +31,19 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
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.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This class will announce the user's {@link TrackStatistics}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class VoiceAnnouncement {
|
||||
|
||||
public class TTSManager {
|
||||
|
||||
public final static int AUDIO_STREAM = TextToSpeech.Engine.DEFAULT_STREAM;
|
||||
|
||||
private static final String TAG = VoiceAnnouncement.class.getSimpleName();
|
||||
private static final String TAG = TTSManager.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final AudioManager audioManager;
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private TrackPoint.Id startTrackPointId = null;
|
||||
|
||||
private IntervalStatistics intervalStatistics;
|
||||
private Distance intervalDistance;
|
||||
|
||||
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
@@ -108,12 +90,9 @@ public class VoiceAnnouncement {
|
||||
|
||||
private MediaPlayer ttsFallback;
|
||||
|
||||
VoiceAnnouncement(Context context) {
|
||||
TTSManager(Context context) {
|
||||
this.context = context;
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
intervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
|
||||
intervalStatistics = new IntervalStatistics(intervalDistance);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -139,7 +118,7 @@ public class VoiceAnnouncement {
|
||||
}
|
||||
}
|
||||
|
||||
public void announce(@NonNull Track track) {
|
||||
public void announce(@NonNull Spannable announcement) {
|
||||
synchronized (this) {
|
||||
if (!ttsReady) {
|
||||
ttsReady = ttsInitStatus == TextToSpeech.SUCCESS;
|
||||
@@ -166,23 +145,6 @@ public class VoiceAnnouncement {
|
||||
return;
|
||||
}
|
||||
|
||||
Distance currentIntervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
|
||||
if (currentIntervalDistance != intervalDistance) {
|
||||
intervalStatistics = new IntervalStatistics(currentIntervalDistance);
|
||||
intervalDistance = currentIntervalDistance;
|
||||
startTrackPointId = null;
|
||||
}
|
||||
|
||||
TrackPointIterator trackPointIterator = new TrackPointIterator(contentProviderUtils, track.getId(), startTrackPointId);
|
||||
startTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator);
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
|
||||
SensorStatistics sensorStatistics = null;
|
||||
if (track.getId() != null) {
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(track.getId());
|
||||
}
|
||||
|
||||
Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics);
|
||||
|
||||
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");
|
||||
+63
-9
@@ -17,6 +17,7 @@ package de.dennisguse.opentracks.services.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.Spannable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -27,11 +28,15 @@ import androidx.mediarouter.media.MediaRouter;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
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.Track;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Execute a periodic task on a time or distance schedule.
|
||||
@@ -42,9 +47,9 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
|
||||
private static final String TAG = VoiceAnnouncementManager.class.getSimpleName();
|
||||
|
||||
private final TrackRecordingService trackRecordingService;
|
||||
private final Context context;
|
||||
|
||||
private VoiceAnnouncement voiceAnnouncement;
|
||||
private TTSManager voiceAnnouncement;
|
||||
|
||||
private TrackStatistics trackStatistics;
|
||||
|
||||
@@ -58,12 +63,22 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
@NonNull
|
||||
private Duration nextTotalTime = TOTALTIME_OFF;
|
||||
|
||||
public VoiceAnnouncementManager(@NonNull TrackRecordingService trackRecordingService) {
|
||||
this.trackRecordingService = trackRecordingService;
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private TrackPoint.Id startTrackPointId = null;
|
||||
|
||||
private IntervalStatistics intervalStatistics;
|
||||
private Distance intervalDistance;
|
||||
|
||||
public VoiceAnnouncementManager(@NonNull Context context) {
|
||||
this.context = context;
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
intervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
|
||||
intervalStatistics = new IntervalStatistics(intervalDistance);
|
||||
}
|
||||
|
||||
public void start(@Nullable TrackStatistics trackStatistics) {
|
||||
voiceAnnouncement = new VoiceAnnouncement(trackRecordingService);
|
||||
voiceAnnouncement = new TTSManager(context);
|
||||
voiceAnnouncement.start();
|
||||
update(trackStatistics);
|
||||
}
|
||||
@@ -74,10 +89,10 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
updateNextTaskDistance();
|
||||
}
|
||||
|
||||
public void update(@NonNull Context context, @NonNull Track track) {
|
||||
private boolean shouldNotAnnounce() {
|
||||
if (voiceAnnouncement == null) {
|
||||
Log.e(TAG, "Cannot update when in status shutdown.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!PreferencesUtils.shouldVoiceAnnouncementOnDeviceSpeaker()
|
||||
@@ -85,6 +100,26 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
.getSelectedRoute()
|
||||
.isDeviceSpeaker()) {
|
||||
Log.i(TAG, "No voice announcement on device speaker.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void announceIdle() {
|
||||
if (shouldNotAnnounce()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PreferencesUtils.shouldVoiceAnnouncementIdle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
voiceAnnouncement.announce(VoiceAnnouncementUtils.createIdle(context));
|
||||
}
|
||||
|
||||
public void announceStatisticsIfNeeded(@NonNull Track track) {
|
||||
if (shouldNotAnnounce()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,10 +135,29 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
}
|
||||
|
||||
if (announce) {
|
||||
voiceAnnouncement.announce(track);
|
||||
voiceAnnouncement.announce(createAnnouncement(track));
|
||||
}
|
||||
}
|
||||
|
||||
private Spannable createAnnouncement(Track track) {
|
||||
Distance currentIntervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
|
||||
if (currentIntervalDistance != intervalDistance) {
|
||||
intervalStatistics = new IntervalStatistics(currentIntervalDistance);
|
||||
intervalDistance = currentIntervalDistance;
|
||||
startTrackPointId = null;
|
||||
}
|
||||
|
||||
TrackPointIterator trackPointIterator = new TrackPointIterator(contentProviderUtils, track.getId(), startTrackPointId);
|
||||
startTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator);
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
|
||||
SensorStatistics sensorStatistics = null;
|
||||
if (track.getId() != null) {
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(track.getId());
|
||||
}
|
||||
|
||||
return VoiceAnnouncementUtils.createStatistics(context, track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (voiceAnnouncement != null) {
|
||||
voiceAnnouncement.stop();
|
||||
|
||||
+6
-1
@@ -33,7 +33,12 @@ class VoiceAnnouncementUtils {
|
||||
private VoiceAnnouncementUtils() {
|
||||
}
|
||||
|
||||
static Spannable getAnnouncement(Context context, TrackStatistics trackStatistics, UnitSystem unitSystem, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) {
|
||||
static Spannable createIdle(Context context) {
|
||||
return new SpannableStringBuilder()
|
||||
.append(context.getString(R.string.voiceIdle));
|
||||
}
|
||||
|
||||
static Spannable createStatistics(Context context, TrackStatistics trackStatistics, UnitSystem unitSystem, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) {
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
Distance totalDistance = trackStatistics.getTotalDistance();
|
||||
Speed averageMovingSpeed = trackStatistics.getAverageMovingSpeed();
|
||||
|
||||
@@ -17,7 +17,7 @@ public class AnnouncementsSettingsFragment extends PreferenceFragmentCompat {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_announcements_title);
|
||||
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_announcements_statistics_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -312,6 +312,10 @@ public class PreferencesUtils {
|
||||
return getBoolean(R.string.voice_on_device_speaker_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean shouldVoiceAnnouncementIdle() {
|
||||
return getBoolean(R.string.voice_announce_idle_key, true);
|
||||
}
|
||||
|
||||
public static Duration getVoiceAnnouncementFrequency() {
|
||||
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
|
||||
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
|
||||
|
||||
Reference in New Issue
Block a user