Files
OpenTracks/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java
T
2024-12-29 12:38:15 +01:00

227 lines
7.8 KiB
Java

/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
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;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
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.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;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
/**
* Execute a periodic task on a time or distance schedule.
*
* @author Sandor Dornbush
*/
public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = VoiceAnnouncementManager.class.getSimpleName();
private final Context context;
private TTSManager voiceAnnouncement;
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;
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 TTSManager(context);
voiceAnnouncement.start();
update(trackStatistics);
}
void update(@Nullable TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
updateNextDuration();
updateNextTaskDistance();
}
private boolean shouldNotAnnounce() {
if (voiceAnnouncement == null) {
Log.e(TAG, "Cannot update when in status shutdown.");
return true;
}
if (!PreferencesUtils.shouldVoiceAnnouncementOnDeviceSpeaker()
&& MediaRouter.getInstance(context)
.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.speak(VoiceAnnouncementUtils.createIdle(context));
}
public void announceStatisticsIfNeeded(@NonNull Track track, @NonNull SensorDataSet sensorDataSet) {
if (shouldNotAnnounce()) {
return;
}
boolean announce = false;
this.trackStatistics = track.getTrackStatistics();
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
updateNextTaskDistance();
announce = true;
}
if (!trackStatistics.getTotalTime().minus(nextTotalTime).isNegative()) {
updateNextDuration();
announce = true;
}
if (announce) {
voiceAnnouncement.speak(createAnnouncement(track, sensorDataSet));
}
}
private Spannable createAnnouncement(Track track, SensorDataSet sensorDataSet) {
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, sensorDataSet, PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics);
}
public void stop() {
if (voiceAnnouncement != null) {
voiceAnnouncement.stop();
voiceAnnouncement = null;
}
}
public void setFrequency(Duration frequency) {
this.totalTimeFrequency = frequency;
update(this.trackStatistics);
}
public void setFrequency(Distance frequency) {
this.distanceFrequency = frequency;
update(this.trackStatistics);
}
public void updateNextTaskDistance() {
if (trackStatistics == null || distanceFrequency.isZero()) {
nextTotalDistance = DISTANCE_OFF;
} else {
Distance distance = trackStatistics.getTotalDistance();
int index = (int) (distance.dividedBy(distanceFrequency));
nextTotalDistance = distanceFrequency.multipliedBy(index + 1);
}
}
private void updateNextDuration() {
if (trackStatistics == null || totalTimeFrequency.isZero()) {
nextTotalTime = TOTALTIME_OFF;
} else {
Duration totalTime = trackStatistics.getTotalTime();
Duration intervalMod = Duration.ofMillis(trackStatistics.getTotalTime().toMillis() % totalTimeFrequency.toMillis());
nextTotalTime = totalTime.plus(totalTimeFrequency.minus(intervalMod));
}
}
@VisibleForTesting
@NonNull
public Duration getNextTotalTime() {
return nextTotalTime;
}
@VisibleForTesting
@NonNull
public Distance getNextTotalDistance() {
return nextTotalDistance;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(R.string.voice_announcement_frequency_key, key)) {
setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency());
}
if (PreferencesUtils.isKey(new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
setFrequency(PreferencesUtils.getVoiceAnnouncementDistance());
}
}
}