TrackRecordingService uses LiveData to transfer data.

Replaces pull from UI layer.

Fixes #173.
This commit is contained in:
Dennis Guse
2021-05-13 12:05:44 +02:00
parent 2cdf122e8c
commit 56ff7827df
7 changed files with 208 additions and 307 deletions
@@ -31,8 +31,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import java.time.Duration;
import de.dennisguse.opentracks.databinding.ControllerFragmentBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -49,10 +47,6 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
private static final String TAG = ControllerFragment.class.getSimpleName();
@Deprecated
//TODO TrackRecordingService should provide LifeData for time (replace internal timer)
private static final Duration UI_UPDATE_INTERVAL = Duration.ofSeconds(1);
@Deprecated
private Handler handlerUpdateTotalTime;
private Runnable buttonDelay;
@@ -63,7 +57,7 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackRecordingService.RecordingStatus recordingStatus;
private Duration totalTime;
private TrackRecordingService.RecordingData recordingData;
private final Runnable bindChangedCallback = new Runnable() {
@Override
@@ -76,18 +70,9 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
service.getRecordingStatusObservable()
.observe(ControllerFragment.this, status -> onRecordingStatusChanged(status));
}
};
// A runnable to update the total time.
@Deprecated
private final Runnable updateTotalTimeRunnable = new Runnable() {
public void run() {
if (isResumed() && recordingStatus.isRecordingAndNotPaused()) {
updateTotalTime();
setTotalTime();
handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
}
service.getRecordingDataObservable()
.observe(ControllerFragment.this, recordingData -> onTotalTimeChanged(recordingData));
}
};
@@ -129,12 +114,6 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
trackRecordingServiceConnection.startConnection(getContext());
}
@Override
public void onPause() {
super.onPause();
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
}
@Override
public void onStop() {
super.onStop();
@@ -239,19 +218,6 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
viewBinding.controllerContainer.setVisibility(View.VISIBLE);
}
private void updateTotalTime() {
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) {
totalTime = trackRecordingService.getTotalTime();
}
}
private void setTotalTime() {
if (totalTime != null) {
viewBinding.controllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
}
}
private void showStatusSetDefaultText() {
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
}
@@ -271,13 +237,6 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
recordingStatus = status;
if (!recordingStatus.isRecording()) {
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
}
if (recordingStatus.isRecordingAndNotPaused()) {
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UI_UPDATE_INTERVAL.toMillis());
}
viewBinding.controllerRecord.setImageResource(recordingStatus.isRecordingAndNotPaused() ? R.drawable.ic_button_pause : R.drawable.button_record);
viewBinding.controllerRecord.setContentDescription(getContext().getString(recordingStatus.isRecordingAndNotPaused() ? R.string.image_pause : R.string.image_record));
@@ -290,10 +249,11 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
}
viewBinding.controllerTotalTime.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
}
if (recordingStatus.isRecording()) {
updateTotalTime();
setTotalTime();
}
private void onTotalTimeChanged(TrackRecordingService.RecordingData recordingData) {
this.recordingData = recordingData;
viewBinding.controllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(this.recordingData.getTrackStatistics().getTotalTime()));
}
}
@@ -78,6 +78,7 @@ public class TrackDataHub {
private EGM2008Utils.EGM2008Correction egm2008Correction;
private boolean started;
//TODO Check if this is needed.
private HandlerThread handlerThread;
private Handler handler;
@@ -49,6 +49,16 @@ public class Track {
public Track() {
}
public Track(Track track) {
this.id = track.id;
this.uuid = track.uuid;
this.name = name;
this.description = description;
this.category = category;
this.icon = icon;
this.trackStatistics = trackStatistics;
}
/**
* May be null if the track was not loaded from the database.
*/
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.fragments;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -22,10 +21,6 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordingActivity;
import de.dennisguse.opentracks.adapters.SensorsAdapter;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
@@ -34,7 +29,6 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -46,25 +40,17 @@ import de.dennisguse.opentracks.viewmodels.SensorDataModel;
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StatisticsRecordingFragment extends Fragment implements TrackDataListener {
public class StatisticsRecordingFragment extends Fragment {
private static final String TAG = StatisticsRecordingFragment.class.getSimpleName();
private static final Duration UI_UPDATE_INTERVAL = Duration.ofSeconds(1);
public static Fragment newInstance() {
return new StatisticsRecordingFragment();
}
private TrackDataHub trackDataHub;
private Handler handlerUpdateUI;
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private TrackPoint lastTrackPoint;
@Nullable // Lazily loaded.
private Track track;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackRecordingService.RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
private TrackPoint latestTrackPoint;
private StatisticsRecordingBinding viewBinding;
private SensorsAdapter sensorsAdapter;
@@ -83,9 +69,9 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && track != null) {
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && recordingData != null) {
updateUInecessary = true;
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), recordingData.getTrackCategory());
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_altitude_key, key)) {
@@ -99,39 +85,29 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
if (key != null && updateUInecessary && isResumed()) {
getActivity().runOnUiThread(() -> {
if (isResumed()) {
updateUI();
}
});
getActivity().runOnUiThread(this::updateUI);
}
};
private final Runnable updateUIeachSecond = new Runnable() {
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
if (isResumed() && isSelectedTrackRecording()) {
if (!isSelectedTrackPaused() && track != null) {
synchronized (this) {
if (lastTrackPoint != null && lastTrackPoint.hasLocation() && !lastTrackPoint.isRecent()) {
lastTrackPoint = null;
setLocationValues();
}
}
updateTotalTime();
updateSensorDataUI();
}
handlerUpdateUI.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.w(TAG, "could not get TrackRecordingService");
return;
}
service.getRecordingDataObservable()
.observe(StatisticsRecordingFragment.this, recordingData -> onRecordingDataChanged(recordingData));
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handlerUpdateUI = new Handler();
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
sensorsAdapter = new SensorsAdapter(getContext());
}
@@ -139,7 +115,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(track != null ? track.getCategory() : "")); //TODO "" would be useless as Track was not yet loaded.
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(recordingData.getTrackCategory()));
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
@@ -151,25 +127,18 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
trackRecordingServiceConnection.startConnection(getContext());
updateUIeachSecond.run();
handlerUpdateUI.post(updateUIeachSecond);
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
handlerUpdateUI.removeCallbacks(updateUIeachSecond);
}
@Override
@@ -191,101 +160,12 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
sharedPreferences = null;
}
@Override
public void onTrackUpdated(final Track track) {
if (isResumed()) {
getActivity().runOnUiThread(() -> {
if (isResumed()) {
if ((this.track == null && track != null) || (this.track != null && track != null && !this.track.getCategory().equals(track.getCategory()))) {
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
}
this.track = track;
updateUI();
}
});
}
}
@Override
public void clearTrackPoints() {
lastTrackPoint = null;
}
@Override
public void onNewTrackPointsDone(@NonNull TrackPoint newLastTrackPoint, @NonNull TrackStatistics trackStatistics) {
if (isResumed()) {
getActivity().runOnUiThread(() -> {
if (isResumed()) {
synchronized (this) {
this.lastTrackPoint = newLastTrackPoint;
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
this.lastTrackPoint = null;
}
if (this.lastTrackPoint != null && this.lastTrackPoint.hasLocation() && !this.lastTrackPoint.isRecent()) {
this.lastTrackPoint = null;
}
setLocationValues();
}
}
});
}
}
@Override
public void clearMarkers() {
// We don't care.
}
@Override
public void onNewMarker(Marker marker) {
// We don't care.
}
@Override
public void onNewMarkersDone() {
// We don't care.
}
/**
* Resumes the trackDataHub.
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackRecordingActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, true, false, true, true);
}
/**
* Pauses the trackDataHub.
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Returns true if the selected track is recording.
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackRecording() {
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
}
/**
* Returns true if the selected track is paused.
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackPaused() {
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
}
private void updateUI() {
updateStats();
setLocationValues();
updateSensorDataUI();
if (isResumed()) {
updateStats();
setLocationValues();
updateSensorDataUI();
}
}
/**
@@ -293,100 +173,45 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
* Also sets altitude gain and loss.
*/
private void updateSensorDataUI() {
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService == null) {
Log.d(TAG, "Cannot get the track recording service.");
} else {
SensorDataSet sensorDataSet = trackRecordingService.getSensorDataSet();
if (sensorDataSet != null) {
List<SensorDataModel> sensorDataList = new ArrayList<>();
if (sensorDataSet.getHeartRate() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getHeartRate()));
}
if (sensorDataSet.getCyclingCadence() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingCadence()));
}
if (sensorDataSet.getCyclingPower() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingPower()));
}
sensorsAdapter.swapData(sensorDataList);
setSpeedSensorData(sensorDataSet);
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
if (sensorDataSet != null) {
List<SensorDataModel> sensorDataList = new ArrayList<>();
if (sensorDataSet.getHeartRate() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getHeartRate()));
}
//TODO Check if we can get the total altitude gain and loss via trackStatistics instead of doing some computation in the UI layer.
setTotalAltitudeGain(trackRecordingService.getAltitudeGain_m());
setTotalAltitudeLoss(trackRecordingService.getAltitudeLoss_m());
}
}
// Set altitude gain
private void setTotalAltitudeGain(Float altitudeGain_m) {
Float totalAltitudeGain = altitudeGain_m;
if (track != null && track.getTrackStatistics().hasTotalAltitudeGain()) {
if (altitudeGain_m == null) {
totalAltitudeGain = 0f;
if (sensorDataSet.getCyclingCadence() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingCadence()));
}
totalAltitudeGain += track.getTrackStatistics().getTotalAltitudeGain();
if (sensorDataSet.getCyclingPower() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingPower()));
}
sensorsAdapter.swapData(sensorDataList);
}
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), totalAltitudeGain, preferenceMetricUnits);
viewBinding.statsAltitudeGainValue.setText(parts.first);
viewBinding.statsAltitudeGainUnit.setText(parts.second);
}
// Set altitude loss
private void setTotalAltitudeLoss(Float altitudeLoss_m) {
Float totalAltitudeLoss = altitudeLoss_m;
if (track != null && track.getTrackStatistics().hasTotalAltitudeLoss()) {
if (altitudeLoss_m == null) {
totalAltitudeLoss = track.getTrackStatistics().getTotalAltitudeLoss();
} else {
totalAltitudeLoss += track.getTrackStatistics().getTotalAltitudeLoss();
}
{
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), recordingData.getTrackStatistics().getTotalAltitudeGain(), preferenceMetricUnits);
viewBinding.statsAltitudeGainValue.setText(parts.first);
viewBinding.statsAltitudeGainUnit.setText(parts.second);
}
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), totalAltitudeLoss, preferenceMetricUnits);
viewBinding.statsAltitudeLossValue.setText(parts.first);
viewBinding.statsAltitudeLossUnit.setText(parts.second);
}
private void setSpeedSensorData(SensorDataSet sensorDataSet) {
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
if (data.hasValue() && data.isRecent()) {
setTotalDistance(data.getValue().distanceOverall);
setSpeed(data.getValue().speed);
}
if (data.hasValue() && data.isRecent()) {
setSpeed(data.getValue().speed);
}
{
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), recordingData.getTrackStatistics().getTotalAltitudeLoss(), preferenceMetricUnits);
viewBinding.statsAltitudeLossValue.setText(parts.first);
viewBinding.statsAltitudeLossUnit.setText(parts.second);
}
}
private void updateStats() {
setTotalDistance(Distance.of(0)); //TODO Why?
// Set activity type
if (track != null) {
String trackIconValue = TrackIconUtils.getIconValue(getContext(), track.getCategory());
viewBinding.statsActivityTypeIcon.setEnabled(isSelectedTrackRecording());
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
}
String trackIconValue = TrackIconUtils.getIconValue(getContext(), recordingData.getTrackCategory());
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
// Set time
if (track != null) {
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(track.getTrackStatistics().getMovingTime()));
updateTotalTime();
}
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getMovingTime()));
updateTotalTime();
// Set average speed/pace
{
Speed speed = track != null ? track.getTrackStatistics().getAverageSpeed() : null;
Speed speed = recordingData.getTrackStatistics().getAverageSpeed();
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
@@ -396,7 +221,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
// Set max speed/pace
{
Speed speed = track != null ? track.getTrackStatistics().getMaxSpeed() : null;
Speed speed = recordingData.getTrackStatistics().getMaxSpeed();
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
@@ -407,7 +232,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
// Set moving speed/pace
{
Speed speed = track != null ? track.getTrackStatistics().getAverageMovingSpeed() : null;
Speed speed = recordingData.getTrackStatistics().getAverageMovingSpeed();
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
@@ -417,34 +242,41 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
// Set altitude gain and loss
{
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
}
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
}
private void updateTotalTime() {
Duration totalTime = track.getTrackStatistics().getTotalTime();
if (isSelectedTrackRecording()) {
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) {
totalTime = trackRecordingService.getTotalTime();
}
}
Duration totalTime = recordingData.getTrackStatistics().getTotalTime();
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(totalTime));
}
private void setLocationValues() {
// Set speed/pace
Speed speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : null;
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
if (data.hasValue() && data.isRecent()) {
speed = data.getValue().speed;
}
}
setSpeed(speed);
// Set distance
{
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), recordingData.getTrackStatistics().getTotalDistance(), preferenceMetricUnits);
viewBinding.statsDistanceValue.setText(parts.first);
viewBinding.statsDistanceUnit.setText(parts.second);
}
// Set altitude
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
if (preferenceShowAltitude) {
// Current altitude
Float altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? (float) lastTrackPoint.getAltitude().toM() : null;
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), altitude, preferenceMetricUnits);
viewBinding.statsAltitudeCurrentValue.setText(parts.first);
viewBinding.statsAltitudeCurrentUnit.setText(parts.second);
@@ -455,23 +287,15 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
if (preferenceShowCoordinate) {
String latitudeText = getContext().getString(R.string.value_unknown);
String longitudeText = getContext().getString(R.string.value_unknown);
if (lastTrackPoint != null && lastTrackPoint.hasLocation()) {
latitudeText = StringUtils.formatCoordinate(lastTrackPoint.getLatitude());
longitudeText = StringUtils.formatCoordinate(lastTrackPoint.getLongitude());
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
latitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLatitude());
longitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLongitude());
}
viewBinding.statsLatitudeValue.setText(latitudeText);
viewBinding.statsLongitudeValue.setText(longitudeText);
}
}
private void setTotalDistance(Distance sensorDistanceSinceLastTrackpoint) {
Distance totalDistance = track != null ? (track.getTrackStatistics().getTotalDistance().plus(sensorDistanceSinceLastTrackpoint)) : Distance.invalid();
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
viewBinding.statsDistanceValue.setText(parts.first);
viewBinding.statsDistanceUnit.setText(parts.second);
}
private void setSpeed(Speed speed) {
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
@@ -479,4 +303,21 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
viewBinding.statsSpeedValue.setText(parts.first);
viewBinding.statsSpeedUnit.setText(parts.second);
}
private void onRecordingDataChanged(TrackRecordingService.RecordingData recordingData) {
String oldCategory = this.recordingData.getTrackCategory();
String newCategory = recordingData.getTrackCategory();
if (!oldCategory.equals(newCategory)) {
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
}
this.recordingData = recordingData;
this.latestTrackPoint = recordingData.getLatestTrackPoint();
if (latestTrackPoint != null && latestTrackPoint.hasLocation() && !latestTrackPoint.isRecent()) {
latestTrackPoint = null;
}
updateUI();
}
}
@@ -76,7 +76,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private static final String TAG = TrackRecordingService.class.getSimpleName();
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
public static final RecordingStatus STATUS_DEFAULT = new RecordingStatus(null, false);
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:
@@ -84,6 +87,19 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private PeriodicTaskExecutor voiceExecutor;
private TrackRecordingServiceNotificationManager notificationManager;
private Handler handler;
private final Runnable updateRecordingData = new Runnable() {
@Override
public void run() {
Track track = contentProviderUtils.getTrack(recordingStatus.getTrackId());
track.getTrackStatistics().setTotalTime(TrackRecordingService.this.getTotalTime());
SensorDataSet sensorDataSet = fillWithSensorDataSet(lastTrackPoint);
recordingDataObservable.postValue(new RecordingData(track, lastTrackPoint, sensorDataSet));
handler.postDelayed(this, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
}
};
private SharedPreferences sharedPreferences;
private Distance recordingDistanceInterval;
@@ -127,14 +143,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private RecordingStatus recordingStatus;
private MutableLiveData<RecordingStatus> recordingStatusObservable;
private MutableLiveData<GpsStatusValue> gpsStatusObservable;
private MutableLiveData<RecordingData> recordingDataObservable;
@Override
public void onCreate() {
super.onCreate();
handler = new Handler();
recordingStatusObservable = new MutableLiveData<>();
updateRecordingStatus(STATUS_DEFAULT);
gpsStatusObservable = new MutableLiveData<>(STATUS_GPS_DEFAULT);
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
handlerServer = new HandlerServer(this);
@@ -160,6 +180,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override
public void onDestroy() {
handler = null;
handlerServer.stop(this);
handlerServer = null;
@@ -192,6 +214,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
recordingStatusObservable = null;
gpsStatusObservable = null;
recordingDataObservable = null;
super.onDestroy();
}
@@ -208,6 +231,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return recordingStatus.getTrackId();
}
//TODO
public TrackStatistics getTrackStatistics() {
if (trackStatisticsUpdater == null) {
return null;
@@ -352,6 +376,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
remoteSensorManager = new BluetoothRemoteSensorManager(this);
remoteSensorManager.start();
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
altitudeSumManager = new AltitudeSumManager();
altitudeSumManager.start(this);
@@ -438,6 +464,13 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* @param trackStopped true if track is stopped, false if track is paused
*/
private void endRecording(boolean trackStopped) {
if (trackStopped) {
recordingDataObservable.postValue(NOT_RECORDING);
} else {
updateRecordingData.run();
}
handler.removeCallbacks(updateRecordingData);
// Shutdown periodic tasks
voiceExecutor.shutdown();
@@ -613,14 +646,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
trackPoint.setAltitudeLoss(getAltitudeLoss_m());
altitudeSumManager.reset();
}
//TODO We need a copy of the SensorDataSet as data will be reset!
SensorDataSet sensorDataSet = fillWithSensorDataSet(trackPoint);
if (remoteSensorManager != null) {
fillWithSensorDataSet(trackPoint);
remoteSensorManager.reset();
}
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
recordingDataObservable.postValue(new RecordingData(track, trackPoint, sensorDataSet));
contentProviderUtils.updateTrack(track);
} catch (SQLiteException e) {
/*
@@ -632,25 +669,26 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
voiceExecutor.update();
}
public SensorDataSet getSensorDataSet() {
private SensorDataSet fillWithSensorDataSet(TrackPoint trackPoint) {
if (remoteSensorManager == null) {
return null;
}
return remoteSensorManager.getSensorData();
}
SensorDataSet sensorData = remoteSensorManager.getSensorData();
if (sensorData == null) {
return null;
}
private void fillWithSensorDataSet(TrackPoint trackPoint) {
SensorDataSet sensorData = getSensorDataSet();
if (sensorData != null) {
if (trackPoint != null) {
sensorData.fillTrackPoint(trackPoint);
}
return sensorData;
}
/**
* Returns the relative altitude gain (since last trackpoint).
*/
public Float getAltitudeGain_m() {
private Float getAltitudeGain_m() {
if (altitudeSumManager == null || !altitudeSumManager.isConnected()) {
return null;
}
@@ -661,7 +699,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
/**
* Returns the relative altitude loss (since last trackpoint).
*/
public Float getAltitudeLoss_m() {
private Float getAltitudeLoss_m() {
if (altitudeSumManager == null || !altitudeSumManager.isConnected()) {
return null;
}
@@ -712,6 +750,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return gpsStatusObservable;
}
public MutableLiveData<RecordingData> getRecordingDataObservable() {
return recordingDataObservable;
}
public LiveData<RecordingStatus> getRecordingStatusObservable() {
return recordingStatusObservable;
}
@@ -792,4 +834,50 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
'}';
}
}
public static class RecordingData {
private final Track track;
private final TrackPoint latestTrackPoint;
private final SensorDataSet sensorDataSet;
/**
* {@link Track} and {@link TrackPoint} must be immutable (i.e., their content does not change).
*/
public RecordingData(Track track, TrackPoint lastTrackPoint, SensorDataSet sensorDataSet) {
this.track = track;
this.latestTrackPoint = lastTrackPoint;
this.sensorDataSet = sensorDataSet;
}
public Track getTrack() {
return track;
}
public String getTrackCategory() {
if (track == null) {
return "";
}
return track.getCategory();
}
@NonNull
public TrackStatistics getTrackStatistics() {
if (track == null) {
return new TrackStatistics();
}
return track.getTrackStatistics();
}
public TrackPoint getLatestTrackPoint() {
return latestTrackPoint;
}
public SensorDataSet getSensorDataSet() {
return sensorDataSet;
}
}
}
@@ -44,6 +44,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
@Deprecated //TODO Implement proper interface that passes the current TrackRecordingService
private final Runnable callback;
private TrackRecordingService trackRecordingService;
@@ -178,7 +178,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
}
String category = track.getCategory();
//TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else.
//TODO Querying all TrackPoints all the time is inefficient; get infos from TrackRecordingService
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
while (trackPointIterator.hasNext()) {