diff --git a/src/main/java/de/dennisguse/opentracks/ControllerFragment.java b/src/main/java/de/dennisguse/opentracks/ControllerFragment.java index e1af60ad6..3cfb98cfb 100644 --- a/src/main/java/de/dennisguse/opentracks/ControllerFragment.java +++ b/src/main/java/de/dennisguse/opentracks/ControllerFragment.java @@ -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())); } } diff --git a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java index db9e04df1..8e89b67d8 100644 --- a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java +++ b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java @@ -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; diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Track.java b/src/main/java/de/dennisguse/opentracks/content/data/Track.java index bd7eb2926..adf7ada91 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/Track.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/Track.java @@ -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. */ diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java index bce046f16..337e8a346 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java @@ -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 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 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 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 parts = StringUtils.formatAltitude(getContext(), recordingData.getTrackStatistics().getTotalAltitudeGain(), preferenceMetricUnits); + viewBinding.statsAltitudeGainValue.setText(parts.first); + viewBinding.statsAltitudeGainUnit.setText(parts.second); } - - Pair 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 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 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 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 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 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(); + } } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 728f21f43..c751ce47b 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -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 recordingStatusObservable; private MutableLiveData gpsStatusObservable; + private MutableLiveData 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 getRecordingDataObservable() { + return recordingDataObservable; + } + public LiveData 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; + } + } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java index 867c5c3c9..ba0d26192 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java @@ -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; diff --git a/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java b/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java index 4b1fe4aa7..2d5503629 100644 --- a/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java +++ b/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java @@ -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 trackPoints = new ArrayList<>(); while (trackPointIterator.hasNext()) {