mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-04 10:23:06 +02:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -25,7 +25,6 @@ import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@@ -60,8 +59,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
|
||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||
|
||||
public static final String VIEW_TRACK_ICON = "track_icon";
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
|
||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||
@@ -98,8 +95,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBar.replaceMenu(R.menu.track_detail);
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
|
||||
postponeEnterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -284,11 +279,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
}
|
||||
}
|
||||
|
||||
public void startPostponedEnterTransitionWith(View viewIcon) {
|
||||
ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON);
|
||||
startPostponedEnterTransition();
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
}
|
||||
|
||||
@@ -68,14 +68,14 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
|
||||
viewBinding.trackEditDescription.setText(track.getDescription());
|
||||
|
||||
viewBinding.time.setText(StringUtils.formatElapsedTime(track.getTrackStatistics().getMovingTime()));
|
||||
viewBinding.time.setText(StringUtils.formatElapsedTime(track.getStatistics().movingTime()));
|
||||
|
||||
{
|
||||
Pair<String, String> parts = SpeedFormatter.Builder()
|
||||
.setUnit(PreferencesUtils.getUnitSystem())
|
||||
.setReportSpeedOrPace(PreferencesUtils.isReportSpeed(track))
|
||||
.build(this)
|
||||
.getSpeedParts(track.getTrackStatistics().getAverageMovingSpeed());
|
||||
.getSpeedParts(track.getStatistics().getAverageMovingSpeed());
|
||||
viewBinding.speed.setText(parts.first);
|
||||
viewBinding.speedUnit.setText(parts.second);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
Pair<String, String> parts = DistanceFormatter.Builder()
|
||||
.setUnit(PreferencesUtils.getUnitSystem())
|
||||
.build(this)
|
||||
.getDistanceParts(track.getTrackStatistics().getTotalDistance());
|
||||
.getDistanceParts(track.getStatistics().totalDistance());
|
||||
viewBinding.distance.setText(parts.first);
|
||||
viewBinding.distanceUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
@@ -33,17 +33,18 @@ import java.util.List;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.TrackDataHub;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.ChartBinding;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
|
||||
/**
|
||||
* A fragment to display track chart to the user.
|
||||
* ChartFragment uses a {@link TrackStatisticsUpdater} internally and recomputes the {@link TrackStatistics} from the beginning.
|
||||
* ChartFragment uses a {@link TrackStatisticsUpdater} internally and recomputes the {@link SegmentStatisticUpdater} from the beginning.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
@@ -202,7 +203,7 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Statistics trackStatistics) {
|
||||
if (isResumed()) {
|
||||
ChartPoint point = ChartPoint.create(trackStatistics, trackPoint, trackPoint.getSpeed(), chartByDistance, viewBinding.chartView.getUnitSystem());
|
||||
pendingPoints.add(point);
|
||||
|
||||
@@ -4,9 +4,9 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
public record ChartPoint(
|
||||
//X-axis
|
||||
@@ -22,11 +22,11 @@ public record ChartPoint(
|
||||
) {
|
||||
|
||||
|
||||
public static ChartPoint create(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, boolean chartByDistance, UnitSystem unitSystem) {
|
||||
public static ChartPoint create(@NonNull Statistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, boolean chartByDistance, UnitSystem unitSystem) {
|
||||
return new ChartPoint(
|
||||
chartByDistance
|
||||
? trackStatistics.getTotalDistance().toKM_Miles(unitSystem)
|
||||
: trackStatistics.getTotalTime().toMillis(),
|
||||
? trackStatistics.totalDistance().toKM_Miles(unitSystem)
|
||||
: trackStatistics.totalTime().toMillis(),
|
||||
trackPoint.hasAltitude()
|
||||
? Distance.of(trackPoint.getAltitude().toM()).toM_FT(unitSystem)
|
||||
: null,
|
||||
|
||||
@@ -41,6 +41,8 @@ import java.util.UUID;
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
@@ -48,14 +50,15 @@ import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
/**
|
||||
@@ -116,7 +119,6 @@ public class ContentProviderUtils {
|
||||
int altitudeLossIndex = cursor.getColumnIndexOrThrow(TracksColumns.ALTITUDE_LOSS);
|
||||
|
||||
Track track = new Track(ZoneOffset.ofTotalSeconds(cursor.getInt(startTimeOffsetIndex)));
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
if (!cursor.isNull(idIndex)) {
|
||||
track.setId(new Track.Id(cursor.getLong(idIndex)));
|
||||
}
|
||||
@@ -136,37 +138,23 @@ public class ContentProviderUtils {
|
||||
track.setActivityTypeLocalized(cursor.getString(activityTypeLocalizedIndex));
|
||||
}
|
||||
|
||||
if (!cursor.isNull(startTimeIndex)) {
|
||||
trackStatistics.setStartTime(Instant.ofEpochMilli(cursor.getLong(startTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(stopTimeIndex)) {
|
||||
trackStatistics.setStopTime(Instant.ofEpochMilli(cursor.getLong(stopTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(totalDistanceIndex)) {
|
||||
trackStatistics.setTotalDistance(Distance.of(cursor.getFloat(totalDistanceIndex)));
|
||||
}
|
||||
if (!cursor.isNull(totalTimeIndex)) {
|
||||
trackStatistics.setTotalTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(movingTimeIndex)) {
|
||||
trackStatistics.setMovingTime(Duration.ofMillis(cursor.getLong(movingTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(maxSpeedIndex)) {
|
||||
trackStatistics.setMaxSpeed(Speed.of(cursor.getFloat(maxSpeedIndex)));
|
||||
}
|
||||
if (!cursor.isNull(minAltitudeIndex)) {
|
||||
trackStatistics.setMinAltitude(cursor.getFloat(minAltitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(maxAltitudeIndex)) {
|
||||
trackStatistics.setMaxAltitude(cursor.getFloat(maxAltitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(altitudeGainIndex)) {
|
||||
trackStatistics.setTotalAltitudeGain(cursor.getFloat(altitudeGainIndex));
|
||||
}
|
||||
if (!cursor.isNull(altitudeLossIndex)) {
|
||||
trackStatistics.setTotalAltitudeLoss(cursor.getFloat(altitudeLossIndex));
|
||||
}
|
||||
Statistics trackStatistics = new Statistics(
|
||||
cursor.isNull(startTimeIndex) ? null : Instant.ofEpochMilli(cursor.getLong(startTimeIndex)),
|
||||
cursor.isNull(stopTimeIndex) ? null : Instant.ofEpochMilli(cursor.getLong(stopTimeIndex)),
|
||||
cursor.isNull(totalTimeIndex) ? null : Duration.ofMillis(cursor.getLong(totalTimeIndex)),
|
||||
cursor.isNull(movingTimeIndex) ? null : Duration.ofMillis(cursor.getLong(movingTimeIndex)),
|
||||
|
||||
cursor.isNull(totalDistanceIndex) ? null : Distance.of(cursor.getFloat(totalDistanceIndex)),
|
||||
|
||||
cursor.isNull(maxSpeedIndex) ? null : Speed.of(cursor.getFloat(maxSpeedIndex)),
|
||||
cursor.isNull(minAltitudeIndex) || cursor.isNull(maxAltitudeIndex) ? null : new AltitudeExtremities(cursor.getFloat(minAltitudeIndex), cursor.getFloat(maxAltitudeIndex)),
|
||||
cursor.isNull(altitudeGainIndex) || cursor.isNull(altitudeLossIndex) ? null : new AltitudeGainLoss(cursor.getFloat(altitudeGainIndex), cursor.getFloat(altitudeLossIndex)),
|
||||
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
track.setStatistics(trackStatistics);
|
||||
return track;
|
||||
}
|
||||
|
||||
@@ -314,7 +302,7 @@ public class ContentProviderUtils {
|
||||
|
||||
private ContentValues createContentValues(Track track) {
|
||||
ContentValues values = new ContentValues();
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
Statistics trackStatistics = track.getStatistics();
|
||||
|
||||
if (track.getId() != null) {
|
||||
values.put(TracksColumns._ID, track.getId().id());
|
||||
@@ -325,48 +313,56 @@ public class ContentProviderUtils {
|
||||
values.put(TracksColumns.ACTIVITY_TYPE, track.getActivityType() != null ? track.getActivityType().getId() : null);
|
||||
values.put(TracksColumns.ACTIVITY_TYPE_LOCALIZED, track.getActivityTypeLocalized());
|
||||
values.put(TracksColumns.STARTTIME_OFFSET, track.getZoneOffset().getTotalSeconds());
|
||||
if (trackStatistics.getStartTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime().toEpochMilli());
|
||||
if (trackStatistics.startTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
|
||||
}
|
||||
if (trackStatistics.getStopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli());
|
||||
if (trackStatistics.stopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.stopTime().toEpochMilli());
|
||||
}
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis());
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.totalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.totalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.movingTime().toMillis());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS());
|
||||
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
|
||||
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain());
|
||||
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.getTotalAltitudeLoss());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.maxSpeed().toMPS());
|
||||
|
||||
if (trackStatistics.altitudeExtremities() != null) {
|
||||
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
|
||||
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
|
||||
}
|
||||
|
||||
if (trackStatistics.altitudeGainLoss() != null) {
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.altitudeGainLoss().gain_m());
|
||||
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.altitudeGainLoss().loss_m());
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) {
|
||||
public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull Statistics trackStatistics) {
|
||||
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())});
|
||||
}
|
||||
|
||||
private ContentValues createContentValues(TrackStatistics trackStatistics) {
|
||||
private ContentValues createContentValues(Statistics trackStatistics) {
|
||||
ContentValues values = new ContentValues();
|
||||
if (trackStatistics.getStartTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime().toEpochMilli());
|
||||
if (trackStatistics.startTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
|
||||
}
|
||||
if (trackStatistics.getStopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli());
|
||||
if (trackStatistics.stopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.stopTime().toEpochMilli());
|
||||
}
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis());
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.totalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.totalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.movingTime().toMillis());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.maxSpeed().toMPS());
|
||||
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
|
||||
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain());
|
||||
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.getTotalAltitudeLoss());
|
||||
if (trackStatistics.altitudeGainLoss() != null) {
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.altitudeGainLoss().gain_m());
|
||||
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.altitudeGainLoss().loss_m());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -656,11 +652,8 @@ public class ContentProviderUtils {
|
||||
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
|
||||
}
|
||||
|
||||
if (!cursor.isNull(indexes.altitudeGainIndex)) {
|
||||
trackPoint.setAltitudeGain(cursor.getFloat(indexes.altitudeGainIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.altitudeLossIndex)) {
|
||||
trackPoint.setAltitudeLoss(cursor.getFloat(indexes.altitudeLossIndex));
|
||||
if (!cursor.isNull(indexes.altitudeGainIndex) && !cursor.isNull(indexes.altitudeLossIndex)) {
|
||||
trackPoint.setAltitudeGainLoss(cursor.getFloat(indexes.altitudeGainIndex), cursor.getFloat(indexes.altitudeLossIndex));
|
||||
}
|
||||
|
||||
return trackPoint;
|
||||
@@ -814,13 +807,10 @@ public class ContentProviderUtils {
|
||||
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower().getW());
|
||||
}
|
||||
|
||||
if (trackPoint.hasAltitudeGain()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE_GAIN, trackPoint.getAltitudeGain());
|
||||
if (trackPoint.hasAltitudeGainLoss()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE_GAIN, trackPoint.getAltitudeGainLoss().gain_m());
|
||||
values.put(TrackPointsColumns.ALTITUDE_LOSS, trackPoint.getAltitudeGainLoss().loss_m());
|
||||
}
|
||||
if (trackPoint.hasAltitudeLoss()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE_LOSS, trackPoint.getAltitudeLoss());
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -834,7 +824,7 @@ public class ContentProviderUtils {
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting trackPoint id. `null` to ignore
|
||||
*/
|
||||
public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final TrackPoint.Id startTrackPointId) {
|
||||
public AutoCloseableIterator<TrackPoint> getTrackPointLocationIterator(final Track.Id trackId, final TrackPoint.Id startTrackPointId) {
|
||||
return new TrackPointIterator(this, trackId, startTrackPointId);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
@@ -40,8 +41,8 @@ import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.handlers.AltitudeCorrectionManager;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
|
||||
/**
|
||||
* Track data hub.
|
||||
@@ -330,7 +331,7 @@ public class TrackDataHub {
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = null;
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
||||
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
||||
|
||||
while (trackPointIterator.hasNext()) {
|
||||
//Prevents a NPE if stop() is happening while notifyTrackPointsTableUpdate()
|
||||
@@ -428,7 +429,7 @@ public class TrackDataHub {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Statistics trackStatistics) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -436,7 +437,7 @@ public class TrackDataHub {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
default void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
default void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Statistics trackStatistics) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,16 +5,16 @@ import android.database.Cursor;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
|
||||
/**
|
||||
* A lightweight wrapper around the original {@link Cursor}.
|
||||
*/
|
||||
public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
class TrackPointIterator extends AutoCloseableIterator<TrackPoint> {
|
||||
|
||||
private static final String TAG = TrackPointIterator.class.getSimpleName();
|
||||
|
||||
@@ -23,7 +23,7 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
private final CachedTrackPointsIndexes indexes;
|
||||
private Cursor cursor;
|
||||
|
||||
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
|
||||
TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.trackId = trackId;
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
public record AltitudeExtremities(double min_m, double max_m) {
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public record Speed(double speed_mps) {
|
||||
return of(Distance.ofKilometer(speed_kmh), Duration.ofHours(1));
|
||||
}
|
||||
|
||||
//TODO Like Duration.ZERO
|
||||
public static Speed zero() {
|
||||
return of(0.0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public record Statistics(
|
||||
Instant startTime,
|
||||
Instant stopTime,
|
||||
//TODO Rename to totalDuration
|
||||
Duration totalTime,
|
||||
//TODO Rename to movingDuration
|
||||
Duration movingTime, // Based on when we believe the user is traveling
|
||||
|
||||
Distance totalDistance,
|
||||
//TODO Check if this is persisted; if not: remove
|
||||
Speed maxSpeed,
|
||||
|
||||
@Nullable
|
||||
AltitudeExtremities altitudeExtremities,
|
||||
|
||||
@Nullable
|
||||
AltitudeGainLoss altitudeGainLoss,
|
||||
|
||||
//NOTE: The following values are not persisted.
|
||||
@Nullable
|
||||
HeartRate avgHeartRate,
|
||||
@Nullable
|
||||
Power avgPower
|
||||
) {
|
||||
|
||||
public Duration getStoppedTime() {
|
||||
return totalTime.minus(movingTime);
|
||||
}
|
||||
|
||||
public Speed getAverageSpeed() {
|
||||
if (totalTime.isZero()) {
|
||||
return Speed.of(0);
|
||||
}
|
||||
return Speed.of(totalDistance, totalTime);
|
||||
}
|
||||
|
||||
public Speed getAverageMovingSpeed() {
|
||||
return Speed.of(totalDistance, movingTime);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO null should be return (need adjusting ContentPtoviderUtils.createContentValues()
|
||||
public double getMinAltitude() {
|
||||
if (altitudeExtremities == null) return 0;
|
||||
return altitudeExtremities.min_m();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO null should be return (need adjusting ContentPtoviderUtils.createContentValues()
|
||||
public double getMaxAltitude() {
|
||||
if (altitudeExtremities == null) return 0;
|
||||
return altitudeExtremities.max_m();
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,9 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
|
||||
|
||||
/**
|
||||
* A track.
|
||||
@@ -49,17 +48,31 @@ public class Track {
|
||||
|
||||
private ActivityType activityType;
|
||||
|
||||
//TODO Move to Statistics?
|
||||
private ZoneOffset zoneOffset;
|
||||
|
||||
private TrackStatistics trackStatistics = new TrackStatistics();
|
||||
@NonNull
|
||||
private Statistics trackStatistics;
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public Track() {
|
||||
this(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Track(Statistics statistics) {
|
||||
this(ZoneOffset.UTC, statistics);
|
||||
}
|
||||
|
||||
@Deprecated //TODO Remove
|
||||
public Track(@NonNull ZoneOffset zoneOffset) {
|
||||
setZoneOffset(zoneOffset);
|
||||
this(zoneOffset, new SegmentStatisticUpdater().getStatistics());
|
||||
}
|
||||
|
||||
public Track(@NonNull ZoneOffset zoneOffset, @NonNull Statistics trackStatistics) {
|
||||
this.zoneOffset = zoneOffset;
|
||||
this.trackStatistics = trackStatistics;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,23 +142,23 @@ public class Track {
|
||||
|
||||
public OffsetDateTime getStartTime() {
|
||||
return trackStatistics
|
||||
.getStartTime().atOffset(zoneOffset);
|
||||
.startTime().atOffset(zoneOffset);
|
||||
}
|
||||
|
||||
public OffsetDateTime getStopTime() {
|
||||
return trackStatistics
|
||||
.getStopTime().atOffset(zoneOffset);
|
||||
.stopTime().atOffset(zoneOffset);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
public Statistics getStatistics() {
|
||||
return trackStatistics;
|
||||
}
|
||||
|
||||
public void setTrackStatistics(@NonNull TrackStatistics trackStatistics) {
|
||||
public void setStatistics(Statistics trackStatistics) {
|
||||
this.trackStatistics = trackStatistics;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Track{" +
|
||||
@@ -156,20 +169,6 @@ public class Track {
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Track track = (Track) o;
|
||||
return id.equals(track.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
|
||||
public record Id(long id) implements Parcelable {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -83,8 +83,7 @@ public class TrackPoint {
|
||||
private HeartRate heartRate = null;
|
||||
private Cadence cadence = null;
|
||||
private Power power = null;
|
||||
private Float altitudeGain_m = null;
|
||||
private Float altitudeLoss_m = null;
|
||||
private AltitudeGainLoss altitudeGainLoss = null;
|
||||
|
||||
public TrackPoint(@Nullable TrackPoint.Id id, @NonNull Type type, @NonNull Position position) {
|
||||
this.id = id;
|
||||
@@ -163,31 +162,21 @@ public class TrackPoint {
|
||||
return getPosition().toLocation();
|
||||
}
|
||||
|
||||
public boolean hasAltitudeGain() {
|
||||
return altitudeGain_m != null;
|
||||
public boolean hasAltitudeGainLoss() {
|
||||
return altitudeGainLoss != null;
|
||||
}
|
||||
|
||||
public float getAltitudeGain() {
|
||||
return altitudeGain_m;
|
||||
public AltitudeGainLoss getAltitudeGainLoss() {
|
||||
return altitudeGainLoss;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public TrackPoint setAltitudeGain(Float altitudeGain_m) {
|
||||
this.altitudeGain_m = altitudeGain_m;
|
||||
public TrackPoint setAltitudeGainLoss(AltitudeGainLoss altitudeGainLoss) {
|
||||
this.altitudeGainLoss = altitudeGainLoss;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasAltitudeLoss() {
|
||||
return altitudeLoss_m != null;
|
||||
}
|
||||
|
||||
public float getAltitudeLoss() {
|
||||
return altitudeLoss_m;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public TrackPoint setAltitudeLoss(Float altitudeLoss_m) {
|
||||
this.altitudeLoss_m = altitudeLoss_m;
|
||||
public TrackPoint setAltitudeGainLoss(float altitudeGain_m, float altitudeLoss_m) {
|
||||
setAltitudeGainLoss(new AltitudeGainLoss(altitudeGain_m, altitudeLoss_m));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -326,11 +315,10 @@ public class TrackPoint {
|
||||
if (hasSensorDistance() && lastTrackPoint.hasSensorDistance()) {
|
||||
sensorDistance = sensorDistance.minus(lastTrackPoint.getSensorDistance());
|
||||
}
|
||||
if (hasAltitudeGain() && lastTrackPoint.hasAltitudeGain()) {
|
||||
altitudeGain_m -= lastTrackPoint.altitudeGain_m;
|
||||
}
|
||||
if (hasAltitudeLoss() && lastTrackPoint.hasAltitudeLoss()) {
|
||||
altitudeLoss_m -= lastTrackPoint.altitudeLoss_m;
|
||||
if (hasAltitudeGainLoss() && lastTrackPoint.hasAltitudeGainLoss()) {
|
||||
altitudeGainLoss = new AltitudeGainLoss(
|
||||
altitudeGainLoss.gain_m() - lastTrackPoint.altitudeGainLoss.gain_m(),
|
||||
altitudeGainLoss.loss_m() - lastTrackPoint.altitudeGainLoss.loss_m());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -391,14 +379,13 @@ public class TrackPoint {
|
||||
public String toString() {
|
||||
return "TrackPoint{" +
|
||||
"id=" + id +
|
||||
", type=" + type +
|
||||
", position=" + position +
|
||||
", sensorDistance=" + sensorDistance +
|
||||
", type=" + type +
|
||||
", heartRate=" + heartRate +
|
||||
", cadence=" + cadence +
|
||||
", power=" + power +
|
||||
", altitudeGain_m=" + altitudeGain_m +
|
||||
", altitudeLoss_m=" + altitudeLoss_m +
|
||||
", altitudeGainLoss=" + altitudeGainLoss +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@@ -30,16 +30,15 @@ import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -157,8 +156,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
loadTrackDescription(track);
|
||||
updateUI();
|
||||
updateSensorUI();
|
||||
|
||||
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -171,12 +168,12 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
Statistics trackStatistics = track.getStatistics();
|
||||
// Set total distance
|
||||
{
|
||||
Pair<String, String> parts = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(getContext()).getDistanceParts(trackStatistics.getTotalDistance());
|
||||
.build(getContext()).getDistanceParts(trackStatistics.totalDistance());
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
@@ -187,8 +184,8 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set time and start datetime
|
||||
{
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()));
|
||||
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.movingTime()));
|
||||
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.totalTime()));
|
||||
}
|
||||
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(preferenceReportSpeed).build(getContext());
|
||||
@@ -205,7 +202,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
{
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = formatter.getSpeedParts(trackStatistics.getMaxSpeed());
|
||||
Pair<String, String> parts = formatter.getSpeedParts(trackStatistics.maxSpeed());
|
||||
viewBinding.statsMaxSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMaxSpeedUnit.setText(parts.second);
|
||||
}
|
||||
@@ -221,8 +218,8 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set altitude gain and loss
|
||||
{
|
||||
Float altitudeGain_m = trackStatistics.getTotalAltitudeGain();
|
||||
Float altitudeLoss_m = trackStatistics.getTotalAltitudeLoss();
|
||||
Float altitudeGain_m = trackStatistics.altitudeGainLoss() != null ? trackStatistics.altitudeGainLoss().gain_m() : null;
|
||||
Float altitudeLoss_m = trackStatistics.altitudeGainLoss() != null ? trackStatistics.altitudeGainLoss().loss_m() : null;
|
||||
|
||||
Pair<String, String> parts;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
@@ -91,7 +92,7 @@ public class TrackFilenameGenerator {
|
||||
Track track = new Track();
|
||||
track.setName("Berlin");
|
||||
track.setUuid(UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"));
|
||||
track.getTrackStatistics().setStartTime(Instant.ofEpochMilli(0));
|
||||
track.setStatistics(new Statistics(Instant.MIN, null, null, null, null, null, null, null, null, null));
|
||||
|
||||
return format(track, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -98,8 +98,8 @@ public class CSVTrackExporter implements TrackExporter {
|
||||
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
|
||||
|
||||
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""),
|
||||
new Column("altitude_gain", t -> t.hasAltitudeGain() ? ALTITUDE_FORMAT.format(t.getAltitudeGain()) : ""),
|
||||
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? ALTITUDE_FORMAT.format(t.getAltitudeLoss()) : ""),
|
||||
new Column("altitude_gain", t -> t.hasAltitudeGainLoss() ? ALTITUDE_FORMAT.format(t.getAltitudeGainLoss().gain_m()) : ""),
|
||||
new Column("altitude_loss", t -> t.hasAltitudeGainLoss() ? ALTITUDE_FORMAT.format(t.getAltitudeGainLoss().loss_m()) : ""),
|
||||
new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""),
|
||||
new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""),
|
||||
new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""),
|
||||
@@ -131,7 +131,7 @@ public class CSVTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
private void writeTrackPoints(List<Column> columns, Track track) throws InterruptedException {
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||
while (trackPointIterator.hasNext()) {
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -128,7 +128,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
LinkedList<TrackPoint> sensorPoints = new LinkedList<>();
|
||||
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||
while (trackPointIterator.hasNext()) {
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
@@ -279,18 +279,16 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<opentracks:typeTranslated>" + StringUtils.formatCData(track.getActivityTypeLocalized()) + "</opentracks:typeTranslated>");
|
||||
}
|
||||
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
Statistics trackStatistics = track.getStatistics();
|
||||
printWriter.println("<gpxtrkx:TrackStatsExtension>");
|
||||
printWriter.println("<gpxtrkx:Distance>" + trackStatistics.getTotalDistance().toM() + "</gpxtrkx:Distance>");
|
||||
printWriter.println("<gpxtrkx:TimerTime>" + trackStatistics.getTotalTime().getSeconds() + "</gpxtrkx:TimerTime>");
|
||||
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.getMovingTime().getSeconds() + "</gpxtrkx:MovingTime>");
|
||||
printWriter.println("<gpxtrkx:Distance>" + trackStatistics.totalDistance().toM() + "</gpxtrkx:Distance>");
|
||||
printWriter.println("<gpxtrkx:TimerTime>" + trackStatistics.totalTime().getSeconds() + "</gpxtrkx:TimerTime>");
|
||||
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.movingTime().getSeconds() + "</gpxtrkx:MovingTime>");
|
||||
printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>");
|
||||
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.getMaxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
|
||||
if (trackStatistics.hasTotalAltitudeGain()) {
|
||||
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.getTotalAltitudeGain() + "</gpxtrkx:Ascent>");
|
||||
}
|
||||
if (trackStatistics.hasTotalAltitudeLoss()) {
|
||||
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.getTotalAltitudeLoss() + "</gpxtrkx:Descent>");
|
||||
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.maxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
|
||||
if (trackStatistics.altitudeGainLoss() != null) {
|
||||
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.altitudeGainLoss().gain_m() + "</gpxtrkx:Ascent>");
|
||||
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.altitudeGainLoss().loss_m() + "</gpxtrkx:Descent>");
|
||||
}
|
||||
printWriter.println("</gpxtrkx:TrackStatsExtension>");
|
||||
|
||||
@@ -340,12 +338,12 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
extensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower().getW()) + "</pwr:PowerInWatts>\n";
|
||||
}
|
||||
|
||||
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null);
|
||||
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGainLoss() ? (double) tp.getAltitudeGainLoss().gain_m() : null);
|
||||
if (cumulativeGain != null) {
|
||||
extensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
|
||||
}
|
||||
|
||||
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null);
|
||||
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGainLoss() ? (double) tp.getAltitudeGainLoss().loss_m() : null);
|
||||
if (cumulativeLoss != null) {
|
||||
extensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
|
||||
}
|
||||
|
||||
@@ -36,13 +36,13 @@ import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -162,7 +162,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
boolean wroteTrack = false;
|
||||
boolean wroteSegment = false;
|
||||
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||
while (trackPointIterator.hasNext()) {
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
@@ -384,9 +384,13 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate().getBPM() : null);
|
||||
cadenceList.add(trackPoint.hasCadence() ? trackPoint.getCadence().getRPM() : null);
|
||||
powerList.add(trackPoint.hasPower() ? trackPoint.getPower().getW() : null);
|
||||
|
||||
altitudeGainList.add(trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : null);
|
||||
altitudeLossList.add(trackPoint.hasAltitudeLoss() ? trackPoint.getAltitudeLoss() : null);
|
||||
if (trackPoint.hasAltitudeGainLoss()) {
|
||||
altitudeGainList.add(trackPoint.getAltitudeGainLoss().loss_m());
|
||||
altitudeLossList.add(trackPoint.getAltitudeGainLoss().loss_m());
|
||||
} else {
|
||||
altitudeGainList.add(null);
|
||||
altitudeLossList.add(null);
|
||||
}
|
||||
accuracyHorizontal.add(trackPoint.hasHorizontalAccuracy() ? (float) trackPoint.getHorizontalAccuracy().toM() : null);
|
||||
accuracyVertical.add(trackPoint.hasVerticalAccuracy() ? (float) trackPoint.getVerticalAccuracy().toM() : null);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -48,7 +49,7 @@ import de.dennisguse.opentracks.util.StringUtils;
|
||||
* <p>
|
||||
* {@link de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter} does not export information if a segment was started automatic or manually.
|
||||
* Therefore, all segments starts are marked as SEGMENT_START_AUTOMATIC.
|
||||
* Thus, the {@link de.dennisguse.opentracks.stats.TrackStatistics} cannot be restored correctly.
|
||||
* Thus, the {@link SegmentStatisticUpdater} cannot be restored correctly.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@@ -368,18 +369,11 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
}
|
||||
|
||||
if (gain != null) {
|
||||
if (gain != null && loss != null) {
|
||||
try {
|
||||
trackPoint.setAltitudeGain(Float.parseFloat(gain));
|
||||
trackPoint.setAltitudeGainLoss(Float.parseFloat(gain), Float.parseFloat(loss));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s", gain)), e);
|
||||
}
|
||||
}
|
||||
if (loss != null) {
|
||||
try {
|
||||
trackPoint.setAltitudeLoss(Float.parseFloat(loss));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude loss: %s", loss)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s or los: %s", gain, loss)), e);
|
||||
}
|
||||
}
|
||||
if (sensorDistance != null) {
|
||||
|
||||
@@ -342,11 +342,10 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
if (i < sensorPowerList.size() && sensorPowerList.get(i) != null) {
|
||||
trackPoint.setPower(sensorPowerList.get(i));
|
||||
}
|
||||
if (i < altitudeGainList.size()) {
|
||||
trackPoint.setAltitudeGain(altitudeGainList.get(i));
|
||||
}
|
||||
if (i < altitudeLossList.size()) {
|
||||
trackPoint.setAltitudeLoss(altitudeLossList.get(i));
|
||||
if (i < altitudeGainList.size() && i < altitudeLossList.size()) {
|
||||
if (altitudeGainList.get(i) != null && altitudeLossList.get(i) != null) {
|
||||
trackPoint.setAltitudeGainLoss(altitudeGainList.get(i), altitudeLossList.get(i));
|
||||
}
|
||||
}
|
||||
if (i < accuracyHorizontal.size() && accuracyHorizontal.get(i) != null) {
|
||||
trackPoint.setHorizontalAccuracy(Distance.of(accuracyHorizontal.get(i)));
|
||||
|
||||
@@ -147,7 +147,7 @@ public class TrackImporter {
|
||||
|
||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||
updater.addTrackPoints(trackPoints);
|
||||
track.setTrackStatistics(updater.getTrackStatistics());
|
||||
track.setStatistics(updater.getTrackStatistics());
|
||||
|
||||
Track.Id trackId = contentProviderUtils.insertTrack(track);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.sensors;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -15,6 +16,7 @@ import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
public class SensorManager implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
@@ -24,6 +26,8 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
||||
@VisibleForTesting
|
||||
public SensorDataSet sensorDataSet;
|
||||
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
|
||||
private final TrackPointCreator observer;
|
||||
|
||||
private final SensorDataChangedObserver listener = new SensorDataChangedObserver() {
|
||||
@@ -71,6 +75,8 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
||||
throw new RuntimeException("SensorManager cannot be started twice; stop first.");
|
||||
}
|
||||
|
||||
wakeLock = SystemUtils.acquireWakeLock(context, wakeLock);
|
||||
|
||||
gpsManager = new GpsManager(observer, listener);
|
||||
altitudeSumManager = new GainManager(listener);
|
||||
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener);
|
||||
@@ -83,6 +89,8 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
|
||||
bluetoothSensorManager.stop(context);
|
||||
bluetoothSensorManager = null;
|
||||
|
||||
|
||||
@@ -185,8 +185,7 @@ public class SensorDataSet {
|
||||
}
|
||||
|
||||
if (barometer != null && barometer.hasReceivedData()) {
|
||||
trackPoint.setAltitudeGain(barometer.getAggregatedValue(trackPointCreator.createNow()).gain_m());
|
||||
trackPoint.setAltitudeLoss(barometer.getAggregatedValue(trackPointCreator.createNow()).loss_m());
|
||||
trackPoint.setAltitudeGainLoss(barometer.getAggregatedValue(trackPointCreator.createNow()).gain_m(), barometer.getAggregatedValue(trackPointCreator.createNow()).loss_m());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,16 @@ package de.dennisguse.opentracks.services;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
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.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
|
||||
|
||||
/**
|
||||
* {@link Track} and {@link TrackPoint} must be immutable (i.e., their content does not change).
|
||||
*/
|
||||
public record RecordingData(Track track, TrackPoint latestTrackPoint, SensorDataSet sensorDataSet, TrackStatistics currentSegment) {
|
||||
public record RecordingData(Track track, TrackPoint latestTrackPoint, SensorDataSet sensorDataSet, Statistics currentSegment) {
|
||||
public String getTrackCategory() {
|
||||
if (track == null) {
|
||||
return "";
|
||||
@@ -18,22 +19,13 @@ public record RecordingData(Track track, TrackPoint latestTrackPoint, SensorData
|
||||
return track.getActivityTypeLocalized();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public TrackStatistics getStatisticsTrack() {
|
||||
public Statistics getStatisticsTrack() {
|
||||
if (track == null) {
|
||||
return new TrackStatistics();
|
||||
return new SegmentStatisticUpdater().getStatistics();
|
||||
}
|
||||
|
||||
return track.getTrackStatistics();
|
||||
return track.getStatistics();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public TrackStatistics getStatisticsCurrentSegment() {
|
||||
if (currentSegment == null) {
|
||||
return new TrackStatistics();
|
||||
}
|
||||
|
||||
return currentSegment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
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.services.handlers.AltitudeCorrectionManager;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
|
||||
@@ -80,7 +80,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
String activityTypeLocalized = PreferencesUtils.getDefaultActivityTypeLocalized();
|
||||
track.setActivityTypeLocalized(activityTypeLocalized);
|
||||
track.setActivityType(ActivityType.findByLocalizedString(context, activityTypeLocalized));
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
track.setStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
track.setName(TrackNameUtils.getTrackName(context, trackId, track.getStartTime()));
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
@@ -98,7 +98,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
return false;
|
||||
}
|
||||
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getStatistics());
|
||||
onNewTrackPoint(trackPointCreator.createSegmentStartManual());
|
||||
|
||||
reset();
|
||||
@@ -124,16 +124,15 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
|
||||
ALTITUDE_CORRECTION_MANAGER.correctAltitude(context, current.first);
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId); //Get copy
|
||||
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
|
||||
return null;
|
||||
}
|
||||
|
||||
track.setTrackStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
||||
TrackStatistics currentSegment = tmpTrackStatisticsUpdater.getCurrentSegment();
|
||||
track.setStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
||||
|
||||
return new RecordingData(track, current.first, current.second, currentSegment);
|
||||
return new RecordingData(track, current.first, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
@@ -225,7 +224,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
handler.postDelayed(ON_IDLE, idleDuration.toMillis());
|
||||
}
|
||||
|
||||
TrackStatistics getTrackStatistics() {
|
||||
Statistics getTrackStatistics() {
|
||||
return trackStatisticsUpdater == null ? null : trackStatisticsUpdater.getTrackStatistics();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.content.SharedPreferences;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -44,7 +43,6 @@ import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.services.announcement.VoiceAnnouncementManager;
|
||||
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, TrackRecordingManager.IdleObserver {
|
||||
|
||||
@@ -89,7 +87,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
private MutableLiveData<RecordingData> recordingDataObservable;
|
||||
|
||||
// The following variables are set when recording:
|
||||
private WakeLock wakeLock; //TODO Move to SensorManager
|
||||
private Handler handler;
|
||||
|
||||
private TrackPointCreator trackPointCreator;
|
||||
@@ -125,7 +122,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
if (isRecording()) {
|
||||
endCurrentTrack();
|
||||
}
|
||||
if (isSensorStarted()) {
|
||||
if (trackPointCreator != null && trackPointCreator.isStarted()) {
|
||||
stopSensors();
|
||||
}
|
||||
|
||||
@@ -200,7 +197,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
}
|
||||
|
||||
public void tryStartSensors() {
|
||||
if (isSensorStarted()) return;
|
||||
if (trackPointCreator.isStarted()) return;
|
||||
|
||||
Log.i(TAG, "tryStartSensors");
|
||||
|
||||
@@ -208,12 +205,11 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
}
|
||||
|
||||
private synchronized void startSensors() {
|
||||
if (isSensorStarted()) {
|
||||
if (trackPointCreator.isStarted()) {
|
||||
Log.i(TAG, "sensors already started; skipping");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "startSensors");
|
||||
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
||||
trackPointCreator.start(this, handler);
|
||||
|
||||
ServiceCompat.startForeground(this, TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.onSensorsStarted(this), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
@@ -241,7 +237,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
trackPointCreator.stop();
|
||||
stopForeground(true);
|
||||
notificationManager.stop();
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
gpsStatusObservable.postValue(STATUS_GPS_DEFAULT);
|
||||
}
|
||||
|
||||
@@ -318,7 +313,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
// Compute temporary track statistics using sensorData and update time.
|
||||
RecordingData data = trackRecordingManager.getDataForUI();
|
||||
|
||||
voiceAnnouncementManager.announceStatisticsIfNeeded(data.track(), data.sensorDataSet());
|
||||
voiceAnnouncementManager.announceStatisticsIfNeeded(data);
|
||||
|
||||
recordingDataObservable.postValue(data);
|
||||
}
|
||||
@@ -347,9 +342,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
return recordingStatus.isRecording();
|
||||
}
|
||||
|
||||
private boolean isSensorStarted() {
|
||||
return wakeLock != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
|
||||
previousLocationWasAccurate = currentLocationWasAccurate;
|
||||
}
|
||||
|
||||
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(recordingData.getStatisticsTrack().getTotalDistance())));
|
||||
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(recordingData.getStatisticsTrack().totalDistance())));
|
||||
String formattedSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context).formatSpeed(recordingData.latestTrackPoint().getSpeed());
|
||||
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, formattedSpeed));
|
||||
notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
|
||||
|
||||
+29
-29
@@ -30,15 +30,14 @@ import java.util.List;
|
||||
|
||||
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.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
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;
|
||||
import de.dennisguse.opentracks.ui.intervals.IntervalStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
|
||||
/**
|
||||
* Execute a periodic task on a time or distance schedule.
|
||||
@@ -53,7 +52,7 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
|
||||
private TTSManager voiceAnnouncement;
|
||||
|
||||
private TrackStatistics trackStatistics;
|
||||
private Statistics trackStatistics;
|
||||
|
||||
private static final Distance DISTANCE_OFF = Distance.of(Double.MAX_VALUE);
|
||||
private Distance distanceFrequency = DISTANCE_OFF;
|
||||
@@ -69,23 +68,23 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private TrackPoint.Id startTrackPointId = null;
|
||||
|
||||
private IntervalStatistics intervalStatistics;
|
||||
private IntervalStatisticsUpdater intervalStatistics;
|
||||
private Distance intervalDistance;
|
||||
|
||||
public VoiceAnnouncementManager(@NonNull Context context) {
|
||||
this.context = context;
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
intervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
|
||||
intervalStatistics = new IntervalStatistics(intervalDistance);
|
||||
intervalStatistics = new IntervalStatisticsUpdater(intervalDistance);
|
||||
}
|
||||
|
||||
public void start(@Nullable TrackStatistics trackStatistics) {
|
||||
public void start(@Nullable Statistics trackStatistics) {
|
||||
voiceAnnouncement = new TTSManager(context);
|
||||
voiceAnnouncement.start();
|
||||
update(trackStatistics);
|
||||
}
|
||||
|
||||
void update(@Nullable TrackStatistics trackStatistics) {
|
||||
void update(@Nullable Statistics trackStatistics) {
|
||||
this.trackStatistics = trackStatistics;
|
||||
updateNextDuration();
|
||||
updateNextTaskDistance();
|
||||
@@ -120,44 +119,46 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
voiceAnnouncement.speak(VoiceAnnouncementUtils.createIdle(context));
|
||||
}
|
||||
|
||||
public void announceStatisticsIfNeeded(@NonNull Track track, @NonNull SensorDataSet sensorDataSet) {
|
||||
public void announceStatisticsIfNeeded(@NonNull RecordingData data) {
|
||||
if (shouldNotAnnounce()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean announce = false;
|
||||
this.trackStatistics = track.getTrackStatistics();
|
||||
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
|
||||
this.trackStatistics = data.track().getStatistics();
|
||||
if (trackStatistics.totalDistance().greaterThan(nextTotalDistance)) {
|
||||
updateNextTaskDistance();
|
||||
announce = true;
|
||||
}
|
||||
if (!trackStatistics.getTotalTime().minus(nextTotalTime).isNegative()) {
|
||||
if (!trackStatistics.totalTime().minus(nextTotalTime).isNegative()) {
|
||||
updateNextDuration();
|
||||
announce = true;
|
||||
}
|
||||
|
||||
if (announce) {
|
||||
voiceAnnouncement.speak(createAnnouncement(track, sensorDataSet));
|
||||
voiceAnnouncement.speak(createAnnouncement(data));
|
||||
}
|
||||
}
|
||||
|
||||
private Spannable createAnnouncement(Track track, SensorDataSet sensorDataSet) {
|
||||
private Spannable createAnnouncement(RecordingData data) {
|
||||
Distance currentIntervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
|
||||
if (currentIntervalDistance != intervalDistance) {
|
||||
intervalStatistics = new IntervalStatistics(currentIntervalDistance);
|
||||
intervalStatistics = new IntervalStatisticsUpdater(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());
|
||||
}
|
||||
//TODO Avoid reloading (some) TrackPoints just to announce the last interval.
|
||||
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(data.track().getId(), startTrackPointId)) {
|
||||
startTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator);
|
||||
Statistics lastInterval = intervalStatistics.getLastInterval();
|
||||
SensorStatistics sensorStatistics = null;
|
||||
if (data.track().getId() != null) {
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(data.track().getId());
|
||||
}
|
||||
|
||||
return VoiceAnnouncementUtils.createStatistics(context, track, sensorDataSet, PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics);
|
||||
return VoiceAnnouncementUtils.createStatistics(context, data.track(), data.sensorDataSet(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(data.track()), lastInterval, sensorStatistics);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@@ -182,7 +183,7 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
nextTotalDistance = DISTANCE_OFF;
|
||||
} else {
|
||||
|
||||
Distance distance = trackStatistics.getTotalDistance();
|
||||
Distance distance = trackStatistics.totalDistance();
|
||||
|
||||
int index = (int) (distance.dividedBy(distanceFrequency));
|
||||
nextTotalDistance = distanceFrequency.multipliedBy(index + 1);
|
||||
@@ -195,10 +196,9 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
nextTotalTime = TOTALTIME_OFF;
|
||||
} else {
|
||||
|
||||
Duration totalTime = trackStatistics.getTotalTime();
|
||||
Duration intervalMod = Duration.ofMillis(trackStatistics.getTotalTime().toMillis() % totalTimeFrequency.toMillis());
|
||||
Duration intervalMod = Duration.ofMillis(trackStatistics.totalTime().toMillis() % totalTimeFrequency.toMillis());
|
||||
|
||||
nextTotalTime = totalTime.plus(totalTimeFrequency.minus(intervalMod));
|
||||
nextTotalTime = trackStatistics.totalTime().plus(totalTimeFrequency.minus(intervalMod));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-11
@@ -33,12 +33,11 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
class VoiceAnnouncementUtils {
|
||||
@@ -51,8 +50,8 @@ class VoiceAnnouncementUtils {
|
||||
.append(context.getString(R.string.voiceIdle));
|
||||
}
|
||||
|
||||
static Spannable createStatistics(Context context, Track track, SensorDataSet sensorDataSet, UnitSystem unitSystem, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) {
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
static Spannable createStatistics(Context context, Track track, SensorDataSet sensorDataSet, UnitSystem unitSystem, boolean isReportSpeed, @Nullable Statistics currentInterval, @Nullable SensorStatistics sensorStatistics) {
|
||||
Statistics trackStatistics = track.getStatistics();
|
||||
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
|
||||
@@ -60,9 +59,9 @@ class VoiceAnnouncementUtils {
|
||||
appendTime(builder, track.getStopTime());
|
||||
}
|
||||
|
||||
Distance totalDistance = trackStatistics.getTotalDistance();
|
||||
Distance totalDistance = trackStatistics.totalDistance();
|
||||
Speed averageMovingSpeed = trackStatistics.getAverageMovingSpeed();
|
||||
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
|
||||
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getAverageSpeed() : null;
|
||||
|
||||
int perUnitStringId = R.string.empty;
|
||||
int distanceId = R.string.voiceDistance;
|
||||
@@ -112,7 +111,7 @@ class VoiceAnnouncementUtils {
|
||||
boolean hasTravelledDistance = !totalDistance.isZero();
|
||||
|
||||
// Announce time
|
||||
Duration movingTime = trackStatistics.getMovingTime();
|
||||
Duration movingTime = trackStatistics.movingTime();
|
||||
if (shouldVoiceAnnounceMovingTime() && hasTravelledDistance && !movingTime.isZero()) {
|
||||
appendDuration(context, builder, movingTime);
|
||||
builder.append(".");
|
||||
@@ -179,8 +178,8 @@ class VoiceAnnouncementUtils {
|
||||
appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, averageHeartRate), averageHeartRate);
|
||||
builder.append(".");
|
||||
}
|
||||
if (shouldVoiceAnnounceLapHeartRate() && currentInterval != null && currentInterval.hasAverageHeartRate()) {
|
||||
int currentHeartRate = Math.round(currentInterval.getAverageHeartRate().getBPM());
|
||||
if (shouldVoiceAnnounceLapHeartRate() && currentInterval != null && currentInterval.avgHeartRate() != null) {
|
||||
int currentHeartRate = Math.round(currentInterval.avgHeartRate().getBPM());
|
||||
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_heart_rate));
|
||||
@@ -188,8 +187,8 @@ class VoiceAnnouncementUtils {
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
if (shouldVoiceAnnounceLapPower() && currentInterval != null && currentInterval.hasAveragePower()) {
|
||||
int currentPower = Math.round(currentInterval.getAveragePower().getW());
|
||||
if (shouldVoiceAnnounceLapPower() && currentInterval != null && currentInterval.avgPower() != null) {
|
||||
int currentPower = Math.round(currentInterval.avgPower().getW());
|
||||
if (shouldVoiceAnnounceUnit()) {
|
||||
String template = context.getResources().getString(R.string.power_x_watt);
|
||||
builder.append(" ")
|
||||
|
||||
@@ -47,7 +47,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
||||
sensorManager.start(context, handler);
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
public boolean isStarted() {
|
||||
return context != null;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,7 @@ public class TrackShareSummaryGenerator {
|
||||
writeString(track.getName(), builder, R.string.generic_name_line);
|
||||
writeString(track.getActivityTypeLocalized(), builder, R.string.description_activity_type);
|
||||
writeString(track.getDescription(), builder, R.string.generic_description_line);
|
||||
builder.append(generateTrackStatisticsDescription(track.getTrackStatistics()));
|
||||
builder.append(generateTrackStatisticsDescription(track.getStatistics()));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
@@ -78,17 +78,17 @@ public class TrackShareSummaryGenerator {
|
||||
builder.append(TrackShareSummaryGenerator.TEXT_LINE_BREAK);
|
||||
}
|
||||
|
||||
private String generateTrackStatisticsDescription(TrackStatistics stats) {
|
||||
private String generateTrackStatisticsDescription(Statistics stats) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// Total distance
|
||||
writeDistance(stats.getTotalDistance(), builder, R.string.description_total_distance);
|
||||
writeDistance(stats.totalDistance(), builder, R.string.description_total_distance);
|
||||
|
||||
// Total time
|
||||
writeTime(stats.getTotalTime(), builder, R.string.description_total_time);
|
||||
writeTime(stats.totalTime(), builder, R.string.description_total_time);
|
||||
|
||||
// Moving time
|
||||
writeTime(stats.getMovingTime(), builder, R.string.description_moving_time);
|
||||
writeTime(stats.movingTime(), builder, R.string.description_moving_time);
|
||||
|
||||
// Average speed
|
||||
writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed);
|
||||
@@ -97,7 +97,7 @@ public class TrackShareSummaryGenerator {
|
||||
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed);
|
||||
|
||||
// Max speed
|
||||
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed);
|
||||
writeSpeed(stats.maxSpeed(), builder, R.string.description_max_speed);
|
||||
|
||||
// Average pace
|
||||
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute);
|
||||
@@ -106,26 +106,22 @@ public class TrackShareSummaryGenerator {
|
||||
writePace(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_pace_in_minute);
|
||||
|
||||
// Fastest pace
|
||||
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute);
|
||||
writePace(stats.maxSpeed(), builder, R.string.description_fastest_pace_in_minute);
|
||||
|
||||
// Min and Max altitude
|
||||
if (stats.hasAltitudeExtremities()) {
|
||||
writeAltitude(stats.getMaxAltitude(), builder, R.string.description_max_altitude);
|
||||
writeAltitude(stats.getMinAltitude(), builder, R.string.description_min_altitude);
|
||||
if (stats.altitudeExtremities() != null) {
|
||||
writeAltitude(stats.altitudeExtremities().max_m(), builder, R.string.description_max_altitude);
|
||||
writeAltitude(stats.altitudeExtremities().min_m(), builder, R.string.description_min_altitude);
|
||||
}
|
||||
|
||||
// Altitude gain
|
||||
if (stats.hasTotalAltitudeGain()) {
|
||||
writeAltitude(stats.getTotalAltitudeGain(), builder, R.string.description_altitude_gain);
|
||||
}
|
||||
|
||||
// Altitude loss
|
||||
if (stats.hasTotalAltitudeLoss()) {
|
||||
writeAltitude(stats.getTotalAltitudeLoss(), builder, R.string.description_altitude_loss);
|
||||
// Altitude gain & loss
|
||||
if (stats.altitudeGainLoss() != null) {
|
||||
writeAltitude(stats.altitudeGainLoss().gain_m(), builder, R.string.description_altitude_gain);
|
||||
writeAltitude(stats.altitudeGainLoss().loss_m(), builder, R.string.description_altitude_loss);
|
||||
}
|
||||
|
||||
// Recorded time
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTimeWithOffset(OffsetDateTime.ofInstant(stats.getStartTime(), ZoneId.systemDefault()))));
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTimeWithOffset(OffsetDateTime.ofInstant(stats.startTime(), ZoneId.systemDefault()))));
|
||||
builder.append(TEXT_LINE_BREAK);
|
||||
|
||||
return builder.toString();
|
||||
|
||||
@@ -100,14 +100,15 @@ public class ExtremityMonitor {
|
||||
* @param min the minimum value
|
||||
* @param max the maximum value
|
||||
*/
|
||||
public void set(double min, double max) {
|
||||
public void set(Double min, Double max) {
|
||||
if (min == null || max == null) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if has data.
|
||||
*/
|
||||
public boolean hasData() {
|
||||
return min != Double.POSITIVE_INFINITY && max != Double.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
+67
-91
@@ -24,10 +24,13 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
|
||||
@@ -38,8 +41,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//TODO Check that data ranges are valid (not less than zero etc.)
|
||||
//TODO Should be a Java record
|
||||
public class TrackStatistics {
|
||||
public class SegmentStatisticUpdater {
|
||||
|
||||
// The min and max altitude (meters) seen on this track.
|
||||
private final ExtremityMonitor altitudeExtremities = new ExtremityMonitor();
|
||||
@@ -50,7 +52,11 @@ public class TrackStatistics {
|
||||
private Instant stopTime; //TODO Should never be null!
|
||||
|
||||
private Distance totalDistance;
|
||||
// Updated when new points are received, may be stale.
|
||||
/**
|
||||
* Updated when new points are received, may be stale.
|
||||
* This statistic is only updated when a new point is added to the statistics, so it may be off.
|
||||
* If you need to calculate the proper totalTime, use startTime with the current time.
|
||||
*/
|
||||
private Duration totalTime;
|
||||
// Based on when we believe the user is traveling.
|
||||
private Duration movingTime;
|
||||
@@ -64,7 +70,7 @@ public class TrackStatistics {
|
||||
|
||||
private boolean isIdle;
|
||||
|
||||
public TrackStatistics() {
|
||||
public SegmentStatisticUpdater() {
|
||||
reset();
|
||||
}
|
||||
|
||||
@@ -73,31 +79,27 @@ public class TrackStatistics {
|
||||
*
|
||||
* @param other another statistics data object to copy from
|
||||
*/
|
||||
public TrackStatistics(TrackStatistics other) {
|
||||
startTime = other.startTime;
|
||||
stopTime = other.stopTime;
|
||||
totalDistance = other.totalDistance;
|
||||
totalTime = other.totalTime;
|
||||
movingTime = other.movingTime;
|
||||
maxSpeed = other.maxSpeed;
|
||||
altitudeExtremities.set(other.altitudeExtremities.getMin(), other.altitudeExtremities.getMax());
|
||||
totalAltitudeGain_m = other.totalAltitudeGain_m;
|
||||
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
|
||||
avgHeartRate = other.avgHeartRate;
|
||||
avgPower = other.avgPower;
|
||||
isIdle = other.isIdle;
|
||||
public SegmentStatisticUpdater(SegmentStatisticUpdater other) {
|
||||
this(other.getStatistics());
|
||||
this.isIdle = other.isIdle();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public TrackStatistics(String startTime, String stopTime, double totalDistance_m, int totalTime_s, int movingTime_s, float maxSpeed_mps, Float totalAltitudeGain_m, Float totalAltitudeLoss_m) {
|
||||
this.startTime = Instant.parse(startTime);
|
||||
this.stopTime = Instant.parse(stopTime);
|
||||
this.totalDistance = Distance.of(totalDistance_m);
|
||||
this.totalTime = Duration.ofSeconds(totalTime_s);
|
||||
this.movingTime = Duration.ofSeconds(movingTime_s);
|
||||
this.maxSpeed = Speed.of(maxSpeed_mps);
|
||||
this.totalAltitudeGain_m = totalAltitudeGain_m;
|
||||
this.totalAltitudeLoss_m = totalAltitudeLoss_m;
|
||||
public SegmentStatisticUpdater(Statistics statistics) {
|
||||
startTime = statistics.startTime();
|
||||
stopTime = statistics.stopTime();
|
||||
totalDistance = statistics.totalDistance();
|
||||
totalTime = statistics.totalTime();
|
||||
movingTime = statistics.movingTime();
|
||||
maxSpeed = statistics.maxSpeed();
|
||||
if (statistics.altitudeExtremities() != null) {
|
||||
altitudeExtremities.set(statistics.altitudeExtremities().min_m(), statistics.altitudeExtremities().max_m());
|
||||
}
|
||||
if (statistics.altitudeGainLoss() != null) {
|
||||
totalAltitudeGain_m = statistics.altitudeGainLoss().gain_m();
|
||||
totalAltitudeLoss_m = statistics.altitudeGainLoss().loss_m();
|
||||
}
|
||||
avgHeartRate = statistics.avgHeartRate();
|
||||
avgPower = statistics.avgPower();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +109,7 @@ public class TrackStatistics {
|
||||
* @param other another statistics data object
|
||||
*/
|
||||
//TODO Should be refactored to append only [mainly due to isIdle] (NOTE: This requires to use a custom value object for AggregatedStatistics; this is anyhow recommended).
|
||||
public void merge(TrackStatistics other) {
|
||||
public void merge(SegmentStatisticUpdater other) {
|
||||
if (startTime == null) {
|
||||
startTime = other.startTime;
|
||||
} else {
|
||||
@@ -183,12 +185,12 @@ public class TrackStatistics {
|
||||
startTime = null;
|
||||
stopTime = null;
|
||||
|
||||
setTotalDistance(Distance.of(0));
|
||||
setTotalTime(Duration.ofSeconds(0));
|
||||
setMovingTime(Duration.ofSeconds(0));
|
||||
setMaxSpeed(Speed.zero());
|
||||
setTotalAltitudeGain(null);
|
||||
setTotalAltitudeLoss(null);
|
||||
totalTime = Duration.ofSeconds(0);
|
||||
movingTime = Duration.ofSeconds(0);
|
||||
totalDistance = Distance.of(0);
|
||||
maxSpeed = Speed.zero();
|
||||
totalAltitudeGain_m = null;
|
||||
totalAltitudeLoss_m = null;
|
||||
|
||||
isIdle = false;
|
||||
}
|
||||
@@ -198,6 +200,22 @@ public class TrackStatistics {
|
||||
setStartTime(startTime);
|
||||
}
|
||||
|
||||
public Statistics getStatistics() {
|
||||
return new Statistics(
|
||||
startTime,
|
||||
stopTime,
|
||||
totalTime,
|
||||
movingTime,
|
||||
|
||||
totalDistance,
|
||||
getMaxSpeed(),
|
||||
altitudeExtremities.hasData() ? new AltitudeExtremities(altitudeExtremities.getMin(), altitudeExtremities.getMax()) : null,
|
||||
totalAltitudeGain_m != null && totalAltitudeLoss_m != null ? new AltitudeGainLoss(totalAltitudeGain_m, totalAltitudeLoss_m) : null,
|
||||
avgHeartRate,
|
||||
avgPower
|
||||
);
|
||||
}
|
||||
|
||||
public Instant getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
@@ -226,6 +244,7 @@ public class TrackStatistics {
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setTotalDistance(Distance totalDistance_m) {
|
||||
this.totalDistance = totalDistance_m;
|
||||
}
|
||||
@@ -234,11 +253,6 @@ public class TrackStatistics {
|
||||
totalDistance = totalDistance.plus(distance_m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total time in milliseconds that this track has been active.
|
||||
* This statistic is only updated when a new point is added to the statistics, so it may be off.
|
||||
* If you need to calculate the proper total time, use {@link #getStartTime} with the current time.
|
||||
*/
|
||||
public Duration getTotalTime() {
|
||||
return totalTime;
|
||||
}
|
||||
@@ -267,10 +281,6 @@ public class TrackStatistics {
|
||||
movingTime = movingTime.plus(time);
|
||||
}
|
||||
|
||||
public Duration getStoppedTime() {
|
||||
return totalTime.minus(movingTime);
|
||||
}
|
||||
|
||||
public boolean isIdle() {
|
||||
return isIdle;
|
||||
}
|
||||
@@ -279,19 +289,11 @@ public class TrackStatistics {
|
||||
isIdle = idle;
|
||||
}
|
||||
|
||||
public boolean hasAverageHeartRate() {
|
||||
return avgHeartRate != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public HeartRate getAverageHeartRate() {
|
||||
return avgHeartRate;
|
||||
}
|
||||
|
||||
public boolean hasPower() {
|
||||
return avgPower != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Power getAveragePower() {
|
||||
return avgPower;
|
||||
@@ -320,10 +322,6 @@ public class TrackStatistics {
|
||||
this.maxSpeed = maxSpeed;
|
||||
}
|
||||
|
||||
public boolean hasAltitudeExtremities() {
|
||||
return altitudeExtremities.hasData();
|
||||
}
|
||||
|
||||
public double getMinAltitude() {
|
||||
return altitudeExtremities.getMin();
|
||||
}
|
||||
@@ -362,64 +360,42 @@ public class TrackStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTotalAltitudeGain() {
|
||||
return totalAltitudeGain_m != null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public Float getTotalAltitudeGain() {
|
||||
return totalAltitudeGain_m;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Deprecated
|
||||
public void setTotalAltitudeGain(Float totalAltitudeGain_m) {
|
||||
this.totalAltitudeGain_m = totalAltitudeGain_m;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void addTotalAltitudeGain(float gain_m) {
|
||||
if (totalAltitudeGain_m == null) {
|
||||
totalAltitudeGain_m = 0f;
|
||||
}
|
||||
totalAltitudeGain_m += gain_m;
|
||||
}
|
||||
|
||||
public boolean hasTotalAltitudeLoss() {
|
||||
return totalAltitudeLoss_m != null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public Float getTotalAltitudeLoss() {
|
||||
return totalAltitudeLoss_m;
|
||||
}
|
||||
|
||||
public void setTotalAltitudeLoss(Float totalAltitudeLoss_m) {
|
||||
this.totalAltitudeLoss_m = totalAltitudeLoss_m;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void addTotalAltitudeLoss(float loss_m) {
|
||||
public void addTotalAltitudeGainLoss(AltitudeGainLoss altitudeGainLoss) {
|
||||
if (totalAltitudeGain_m == null) {
|
||||
totalAltitudeGain_m = 0f;
|
||||
}
|
||||
totalAltitudeGain_m += altitudeGainLoss.gain_m();
|
||||
|
||||
if (totalAltitudeLoss_m == null) {
|
||||
totalAltitudeLoss_m = 0f;
|
||||
}
|
||||
totalAltitudeLoss_m += loss_m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof TrackStatistics)) return false;
|
||||
|
||||
return toString().equals(o.toString());
|
||||
totalAltitudeLoss_m += altitudeGainLoss.loss_m();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TrackStatistics { Start Time: " + getStartTime() + "; Stop Time: " + getStopTime()
|
||||
+ "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime()
|
||||
+ "; Moving Time: " + getMovingTime() + "; Max Speed: " + getMaxSpeed()
|
||||
+ "; Min Altitude: " + getMinAltitude() + "; Max Altitude: " + getMaxAltitude()
|
||||
+ "; Altitude Gain: " + getTotalAltitudeGain()
|
||||
+ "; Altitude Loss: " + getTotalAltitudeLoss() + "}";
|
||||
return "SegmentStatisticUpdater{" + getStatistics() + "}";
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
@@ -25,12 +26,13 @@ import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Updater for {@link TrackStatistics}.
|
||||
* For updating track {@link TrackStatistics} as new {@link TrackPoint}s are added.
|
||||
* Updater for {@link SegmentStatisticUpdater}.
|
||||
* For updating track {@link SegmentStatisticUpdater} as new {@link TrackPoint}s are added.
|
||||
* NOTE: Some of the locations represent pause/resume separator.
|
||||
* NOTE: Has still support for segments (at the moment unused).
|
||||
*
|
||||
@@ -41,7 +43,7 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
private static final String TAG = TrackStatisticsUpdater.class.getSimpleName();
|
||||
|
||||
private final TrackStatistics trackStatistics;
|
||||
private final SegmentStatisticUpdater segmentStatisticUpdater;
|
||||
|
||||
private float averageHeartRateBPM;
|
||||
private Duration totalHeartRateDuration = Duration.ZERO;
|
||||
@@ -49,38 +51,49 @@ public class TrackStatisticsUpdater {
|
||||
private Duration totalPowerDuration = Duration.ZERO;
|
||||
|
||||
// The current segment's statistics
|
||||
private final TrackStatistics currentSegment;
|
||||
private final SegmentStatisticUpdater currentSegment;
|
||||
// Current segment's last trackPoint
|
||||
private TrackPoint lastTrackPoint;
|
||||
|
||||
public TrackStatisticsUpdater() {
|
||||
this(new TrackStatistics());
|
||||
this(new SegmentStatisticUpdater());
|
||||
}
|
||||
|
||||
public TrackStatisticsUpdater(TrackStatistics trackStatistics) {
|
||||
this.trackStatistics = trackStatistics;
|
||||
this.currentSegment = new TrackStatistics();
|
||||
public TrackStatisticsUpdater(Statistics statistics) {
|
||||
this(new SegmentStatisticUpdater(statistics));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private TrackStatisticsUpdater(SegmentStatisticUpdater segmentStatisticUpdater) {
|
||||
this.segmentStatisticUpdater = segmentStatisticUpdater;
|
||||
this.currentSegment = new SegmentStatisticUpdater();
|
||||
|
||||
resetAverageHeartRate();
|
||||
}
|
||||
|
||||
public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) {
|
||||
this.currentSegment = new TrackStatistics(toCopy.currentSegment);
|
||||
this.trackStatistics = new TrackStatistics(toCopy.trackStatistics);
|
||||
this.currentSegment = new SegmentStatisticUpdater(toCopy.currentSegment);
|
||||
this.segmentStatisticUpdater = new SegmentStatisticUpdater(toCopy.segmentStatisticUpdater);
|
||||
|
||||
this.lastTrackPoint = toCopy.lastTrackPoint;
|
||||
resetAverageHeartRate();
|
||||
}
|
||||
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
@Deprecated //TODO Remove
|
||||
@VisibleForTesting
|
||||
public SegmentStatisticUpdater getSegmentStatisticUpdater() {
|
||||
// Take a snapshot - we don't want anyone messing with our trackStatistics
|
||||
TrackStatistics stats = new TrackStatistics(trackStatistics);
|
||||
SegmentStatisticUpdater stats = new SegmentStatisticUpdater(segmentStatisticUpdater);
|
||||
stats.merge(currentSegment);
|
||||
return stats;
|
||||
}
|
||||
public TrackStatistics getCurrentSegment() {
|
||||
// Take a snapshot - we don't want anyone messing with our currentSegment
|
||||
return new TrackStatistics(currentSegment);
|
||||
|
||||
public Statistics getTrackStatistics() {
|
||||
return getSegmentStatisticUpdater().getStatistics();
|
||||
}
|
||||
|
||||
public Statistics getCurrentSegment() {
|
||||
return currentSegment.getStatistics();
|
||||
}
|
||||
|
||||
public void addTrackPoints(List<TrackPoint> trackPoints) {
|
||||
@@ -101,12 +114,8 @@ public class TrackStatisticsUpdater {
|
||||
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));
|
||||
|
||||
// Process sensor data: barometer
|
||||
if (trackPoint.hasAltitudeGain()) {
|
||||
currentSegment.addTotalAltitudeGain(trackPoint.getAltitudeGain());
|
||||
}
|
||||
|
||||
if (trackPoint.hasAltitudeLoss()) {
|
||||
currentSegment.addTotalAltitudeLoss(trackPoint.getAltitudeLoss());
|
||||
if (trackPoint.hasAltitudeGainLoss()) {
|
||||
currentSegment.addTotalAltitudeGainLoss(trackPoint.getAltitudeGainLoss());
|
||||
}
|
||||
|
||||
//Update absolute (GPS-based) altitude
|
||||
@@ -182,7 +191,7 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
private void reset(TrackPoint trackPoint) {
|
||||
if (currentSegment.isInitialized()) {
|
||||
trackStatistics.merge(currentSegment);
|
||||
segmentStatisticUpdater.merge(currentSegment);
|
||||
}
|
||||
currentSegment.reset(trackPoint.getTime());
|
||||
|
||||
@@ -209,7 +218,7 @@ public class TrackStatisticsUpdater {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TrackStatisticsUpdater{" +
|
||||
"trackStatistics=" + trackStatistics +
|
||||
"trackStatistics=" + segmentStatisticUpdater +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package de.dennisguse.opentracks.ui;
|
||||
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -269,10 +267,7 @@ public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||
// Not recording -> open detail activity.
|
||||
Intent newIntent = IntentUtils.newIntent(context, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
context,
|
||||
new Pair<>(viewBinding.getRoot().findViewById(R.id.track_list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||
context.startActivity(newIntent, activityOptions.toBundle());
|
||||
context.startActivity(newIntent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-9
@@ -8,8 +8,9 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
|
||||
|
||||
public class AggregatedStatistics {
|
||||
|
||||
@@ -35,9 +36,9 @@ public class AggregatedStatistics {
|
||||
public void aggregate(@NonNull Track track) {
|
||||
String activityTypeLocalized = track.getActivityTypeLocalized();
|
||||
if (dataMap.containsKey(activityTypeLocalized)) {
|
||||
dataMap.get(activityTypeLocalized).add(track.getTrackStatistics());
|
||||
dataMap.get(activityTypeLocalized).add(track.getStatistics());
|
||||
} else {
|
||||
dataMap.put(activityTypeLocalized, new AggregatedStatistic(activityTypeLocalized, track.getTrackStatistics()));
|
||||
dataMap.put(activityTypeLocalized, new AggregatedStatistic(activityTypeLocalized, track.getStatistics()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,19 +56,19 @@ public class AggregatedStatistics {
|
||||
|
||||
public static class AggregatedStatistic {
|
||||
private final String activityTypeLocalized;
|
||||
private final TrackStatistics trackStatistics;
|
||||
private final SegmentStatisticUpdater trackStatistics;
|
||||
private int countTracks = 1;
|
||||
|
||||
public AggregatedStatistic(String activityTypeLocalized, TrackStatistics trackStatistics) {
|
||||
public AggregatedStatistic(String activityTypeLocalized, Statistics trackStatistics) {
|
||||
this.activityTypeLocalized = activityTypeLocalized;
|
||||
this.trackStatistics = trackStatistics;
|
||||
this.trackStatistics = new SegmentStatisticUpdater(trackStatistics);
|
||||
}
|
||||
|
||||
public String getActivityTypeLocalized() {
|
||||
return activityTypeLocalized;
|
||||
}
|
||||
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
public SegmentStatisticUpdater getTrackStatistics() {
|
||||
return trackStatistics;
|
||||
}
|
||||
|
||||
@@ -75,8 +76,8 @@ public class AggregatedStatistics {
|
||||
return countTracks;
|
||||
}
|
||||
|
||||
void add(TrackStatistics statistics) {
|
||||
trackStatistics.merge(statistics);
|
||||
void add(Statistics statistics) {
|
||||
trackStatistics.merge(new SegmentStatisticUpdater(statistics));
|
||||
countTracks++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.stream.Collectors;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
//TODO Migrate to DataStore.
|
||||
public class RecordingLayoutIO {
|
||||
|
||||
private static final String TAG = RecordingLayout.class.getSimpleName();
|
||||
|
||||
+17
-11
@@ -19,15 +19,16 @@ import java.util.concurrent.Callable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.CustomStatsItemBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.viewmodels.Mapping;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
|
||||
|
||||
@@ -38,17 +39,22 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<Recycl
|
||||
private static final RecordingData demoData;
|
||||
|
||||
static {
|
||||
TrackStatistics trackStatistics = new TrackStatistics();
|
||||
trackStatistics.setStartTime(Instant.ofEpochMilli(0));
|
||||
trackStatistics.setMovingTime(Duration.ofMinutes(0));
|
||||
trackStatistics.setTotalTime(Duration.ofMinutes(0));
|
||||
Statistics statistics = new Statistics(
|
||||
Instant.ofEpochMilli(0),
|
||||
Instant.ofEpochMilli(0),
|
||||
Duration.ofMinutes(0),
|
||||
Duration.ofMinutes(0),
|
||||
|
||||
trackStatistics.setTotalDistance(Distance.of(0));
|
||||
Distance.of(0),
|
||||
Speed.of(0),
|
||||
null,
|
||||
|
||||
trackStatistics.setTotalAltitudeGain(0f);
|
||||
trackStatistics.setTotalAltitudeLoss(0f);
|
||||
Track track = new Track(ZoneOffset.UTC);
|
||||
track.setTrackStatistics(trackStatistics);
|
||||
new AltitudeGainLoss(0f, 0f),
|
||||
|
||||
null,
|
||||
null
|
||||
);
|
||||
Track track = new Track(ZoneOffset.UTC); //TODO Pass statistics as well
|
||||
|
||||
Position position = new Position(
|
||||
Instant.ofEpochMilli(0),
|
||||
@@ -61,7 +67,7 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<Recycl
|
||||
Speed.zero()
|
||||
);
|
||||
|
||||
demoData = new RecordingData(track, new TrackPoint(TrackPoint.Type.TRACKPOINT, position), null, trackStatistics) ;
|
||||
demoData = new RecordingData(track, new TrackPoint(TrackPoint.Type.TRACKPOINT, position), null, statistics);
|
||||
}
|
||||
|
||||
private RecordingLayout recordingLayout;
|
||||
|
||||
+12
-17
@@ -7,18 +7,20 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.databinding.IntervalStatsListItemBinding;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<IntervalStatistics.Interval> intervalList;
|
||||
private List<Statistics> intervalList = new ArrayList<>();
|
||||
private final Context context;
|
||||
private final StackMode stackMode;
|
||||
private UnitSystem unitSystem;
|
||||
@@ -43,46 +45,39 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
|
||||
int nextPosition = actualPosition + 1;
|
||||
boolean isLast = actualPosition == getItemCount() - 1;
|
||||
IntervalStatisticsAdapter.ViewHolder viewHolder = (IntervalStatisticsAdapter.ViewHolder) holder;
|
||||
IntervalStatistics.Interval interval = intervalList.get(actualPosition);
|
||||
Statistics interval = intervalList.get(actualPosition);
|
||||
viewHolder.itemView.setTag(actualPosition);
|
||||
|
||||
Distance sumDistance;
|
||||
if (isLast && actualPosition > 0) {
|
||||
sumDistance = intervalList.get(actualPosition - 1).getDistance()
|
||||
sumDistance = intervalList.get(actualPosition - 1).totalDistance()
|
||||
.multipliedBy(actualPosition)
|
||||
.plus(interval.getDistance());
|
||||
.plus(interval.totalDistance());
|
||||
} else {
|
||||
sumDistance = interval.getDistance().multipliedBy(nextPosition);
|
||||
sumDistance = interval.totalDistance().multipliedBy(nextPosition);
|
||||
}
|
||||
viewHolder.viewBinding.intervalItemDistance.setText(DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(context).formatDistance(sumDistance));
|
||||
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(isReportSpeed).build(context);
|
||||
viewHolder.viewBinding.intervalItemRate.setText(formatter.formatSpeed(interval.getSpeed()));
|
||||
viewHolder.viewBinding.intervalItemRate.setText(formatter.formatSpeed(interval.getAverageSpeed()));
|
||||
|
||||
viewHolder.viewBinding.intervalItemGain.setText(StringUtils.formatAltitude(context, interval.getGain_m(), unitSystem));
|
||||
viewHolder.viewBinding.intervalItemLoss.setText(StringUtils.formatAltitude(context, interval.getLoss_m(), unitSystem));
|
||||
viewHolder.viewBinding.intervalItemGain.setText(StringUtils.formatAltitude(context, interval.altitudeGainLoss() != null ? interval.altitudeGainLoss().gain_m() : null, unitSystem));
|
||||
viewHolder.viewBinding.intervalItemLoss.setText(StringUtils.formatAltitude(context, interval.altitudeGainLoss() != null ? interval.altitudeGainLoss().loss_m() : null, unitSystem));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (intervalList == null) {
|
||||
return 0;
|
||||
}
|
||||
return intervalList.size();
|
||||
}
|
||||
|
||||
public List<IntervalStatistics.Interval> swapData(List<IntervalStatistics.Interval> data, UnitSystem unitSystem, boolean isReportSpeed) {
|
||||
public void swapData(@NonNull List<Statistics> data, UnitSystem unitSystem, boolean isReportSpeed) {
|
||||
this.unitSystem = unitSystem;
|
||||
this.isReportSpeed = isReportSpeed;
|
||||
intervalList = data;
|
||||
|
||||
if (data != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
return data;
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,12 +17,13 @@ import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.util.AutoCloseableIterator;
|
||||
|
||||
/**
|
||||
* This model is used to load intervals for a track.
|
||||
@@ -32,8 +33,8 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
|
||||
private static final String TAG = IntervalStatisticsModel.class.getSimpleName();
|
||||
|
||||
private MutableLiveData<List<IntervalStatistics.Interval>> intervalsLiveData;
|
||||
private IntervalStatistics intervalStatistics;
|
||||
private MutableLiveData<List<Statistics>> intervalsLiveData;
|
||||
private IntervalStatisticsUpdater intervalStatistics;
|
||||
private Distance distanceInterval;
|
||||
private final ContentResolver contentResolver;
|
||||
private ContentObserver trackPointsTableObserver;
|
||||
@@ -65,7 +66,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
handler = null;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<IntervalStatistics.Interval>> getIntervalStats(Track.Id trackId, UnitSystem unitSystem, @Nullable IntervalOption interval) {
|
||||
public MutableLiveData<List<Statistics>> getIntervalStats(Track.Id trackId, UnitSystem unitSystem, @Nullable IntervalOption interval) {
|
||||
if (intervalsLiveData == null) {
|
||||
if (interval == null) {
|
||||
interval = IntervalOption.OPTION_1;
|
||||
@@ -73,7 +74,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
|
||||
intervalsLiveData = new MutableLiveData<>();
|
||||
distanceInterval = interval.getDistance(unitSystem);
|
||||
intervalStatistics = new IntervalStatistics(distanceInterval);
|
||||
intervalStatistics = new IntervalStatisticsUpdater(distanceInterval);
|
||||
|
||||
loadIntervalStatistics(trackId);
|
||||
}
|
||||
@@ -92,7 +93,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
private void loadIntervalStatistics(Track.Id trackId) {
|
||||
executor.execute(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication());
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, lastTrackPointId)) {
|
||||
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, lastTrackPointId)) {
|
||||
lastTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator);
|
||||
intervalsLiveData.postValue(intervalStatistics.getIntervalList());
|
||||
}
|
||||
@@ -112,7 +113,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
|
||||
lastTrackPointId = null;
|
||||
distanceInterval = interval.getDistance(unitSystem);
|
||||
intervalStatistics = new IntervalStatistics(distanceInterval);
|
||||
intervalStatistics = new IntervalStatisticsUpdater(distanceInterval);
|
||||
loadIntervalStatistics(trackId);
|
||||
}
|
||||
|
||||
|
||||
+51
-42
@@ -4,18 +4,19 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
|
||||
public class IntervalStatistics {
|
||||
public class IntervalStatisticsUpdater {
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
private final List<Interval> intervalList;
|
||||
private final Distance distanceInterval;
|
||||
@@ -24,7 +25,7 @@ public class IntervalStatistics {
|
||||
/**
|
||||
* @param distanceInterval distance of every interval.
|
||||
*/
|
||||
public IntervalStatistics(Distance distanceInterval) {
|
||||
public IntervalStatisticsUpdater(Distance distanceInterval) {
|
||||
this.distanceInterval = distanceInterval;
|
||||
|
||||
interval = new Interval();
|
||||
@@ -38,7 +39,7 @@ public class IntervalStatistics {
|
||||
*
|
||||
* @return the last track point's id used to compute the intervals.
|
||||
*/
|
||||
public TrackPoint.Id addTrackPoints(TrackPointIterator trackPointIterator) {
|
||||
public TrackPoint.Id addTrackPoints(Iterator<TrackPoint> trackPointIterator) {
|
||||
boolean newIntervalAdded = false;
|
||||
TrackPoint trackPoint = null;
|
||||
|
||||
@@ -46,7 +47,7 @@ public class IntervalStatistics {
|
||||
trackPoint = trackPointIterator.next();
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint);
|
||||
|
||||
if (trackStatisticsUpdater.getTrackStatistics().getTotalDistance().plus(interval.distance).greaterOrEqualThan(distanceInterval)) {
|
||||
if (trackStatisticsUpdater.getTrackStatistics().totalDistance().plus(interval.distance).greaterOrEqualThan(distanceInterval)) {
|
||||
interval.add(trackStatisticsUpdater.getTrackStatistics(), trackPoint);
|
||||
|
||||
double adjustFactor = distanceInterval.dividedBy(interval.distance);
|
||||
@@ -74,8 +75,8 @@ public class IntervalStatistics {
|
||||
return trackPoint != null ? trackPoint.getId() : null;
|
||||
}
|
||||
|
||||
public List<Interval> getIntervalList() {
|
||||
return intervalList;
|
||||
public List<Statistics> getIntervalList() {
|
||||
return intervalList.stream().map(Interval::toStatistics).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,21 +85,24 @@ public class IntervalStatistics {
|
||||
*
|
||||
* @return the interval object or null if any interval is completed.
|
||||
*/
|
||||
public Interval getLastInterval() {
|
||||
@Nullable
|
||||
public Statistics getLastInterval() {
|
||||
if (intervalList.size() == 1 && intervalList.get(0).getDistance().lessThan(distanceInterval)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = intervalList.size() - 1; i >= 0; i--) {
|
||||
if (intervalList.get(i).getDistance().greaterOrEqualThan(distanceInterval)) {
|
||||
return this.intervalList.get(i);
|
||||
return this.intervalList.get(i).toStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class Interval {
|
||||
//TODO Could be replaced with Statistics?
|
||||
@Deprecated
|
||||
private static class Interval {
|
||||
private Distance distance = Distance.of(0);
|
||||
private Duration time = Duration.ofSeconds(0);
|
||||
private Float gain_m;
|
||||
@@ -144,18 +148,6 @@ public class IntervalStatistics {
|
||||
return gain_m != null;
|
||||
}
|
||||
|
||||
public Float getGain_m() {
|
||||
return gain_m;
|
||||
}
|
||||
|
||||
public boolean hasLoss() {
|
||||
return loss_m != null;
|
||||
}
|
||||
|
||||
public Float getLoss_m() {
|
||||
return loss_m;
|
||||
}
|
||||
|
||||
public boolean hasAverageHeartRate() {
|
||||
return avgHeartRate != null;
|
||||
}
|
||||
@@ -172,31 +164,48 @@ public class IntervalStatistics {
|
||||
return avgPower;
|
||||
}
|
||||
|
||||
private void add(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
|
||||
distance = distance.plus(trackStatistics.getTotalDistance());
|
||||
time = time.plus(trackStatistics.getTotalTime());
|
||||
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
|
||||
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
|
||||
avgHeartRate = trackStatistics.getAverageHeartRate();
|
||||
avgPower = trackStatistics.getAveragePower();
|
||||
private void add(Statistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
|
||||
distance = distance.plus(trackStatistics.totalDistance());
|
||||
time = time.plus(trackStatistics.totalTime());
|
||||
if (trackStatistics.altitudeGainLoss() != null) {
|
||||
gain_m = trackStatistics.altitudeGainLoss().gain_m();
|
||||
loss_m = trackStatistics.altitudeGainLoss().loss_m();
|
||||
}
|
||||
avgHeartRate = trackStatistics.avgHeartRate();
|
||||
avgPower = trackStatistics.avgPower();
|
||||
if (lastTrackPoint == null) {
|
||||
return;
|
||||
}
|
||||
if (hasGain() && lastTrackPoint.hasAltitudeGain()) {
|
||||
gain_m = gain_m - lastTrackPoint.getAltitudeGain();
|
||||
}
|
||||
if (hasLoss() && lastTrackPoint.hasAltitudeLoss()) {
|
||||
loss_m = loss_m - lastTrackPoint.getAltitudeLoss();
|
||||
if (hasGain() && lastTrackPoint.hasAltitudeGainLoss()) {
|
||||
gain_m = gain_m - lastTrackPoint.getAltitudeGainLoss().gain_m();
|
||||
loss_m = loss_m - lastTrackPoint.getAltitudeGainLoss().loss_m();
|
||||
}
|
||||
}
|
||||
|
||||
private void set(TrackStatistics trackStatistics) {
|
||||
distance = trackStatistics.getTotalDistance();
|
||||
time = trackStatistics.getTotalTime();
|
||||
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
|
||||
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
|
||||
avgHeartRate = trackStatistics.getAverageHeartRate();
|
||||
avgPower = trackStatistics.getAveragePower();
|
||||
private void set(Statistics trackStatistics) {
|
||||
distance = trackStatistics.totalDistance();
|
||||
time = trackStatistics.totalTime();
|
||||
if (trackStatistics.altitudeGainLoss() != null) {
|
||||
gain_m = trackStatistics.altitudeGainLoss().gain_m();
|
||||
loss_m = trackStatistics.altitudeGainLoss().loss_m();
|
||||
}
|
||||
avgHeartRate = trackStatistics.avgHeartRate();
|
||||
avgPower = trackStatistics.avgPower();
|
||||
}
|
||||
|
||||
private Statistics toStatistics() {
|
||||
return new Statistics(
|
||||
null,
|
||||
null,
|
||||
time,
|
||||
null,
|
||||
distance,
|
||||
null,
|
||||
null,
|
||||
gain_m != null ? new AltitudeGainLoss(gain_m, loss_m) : null,
|
||||
avgHeartRate,
|
||||
avgPower
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import java.util.Objects;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
@@ -192,7 +193,7 @@ public class IntervalsFragment extends Fragment {
|
||||
}
|
||||
|
||||
viewBinding.intervalRate.setText(isReportSpeed ? getString(R.string.stats_speed) : getString(R.string.stats_pace));
|
||||
LiveData<List<IntervalStatistics.Interval>> liveData = viewModel.getIntervalStats(trackId, unitSystem, selectedInterval);
|
||||
LiveData<List<Statistics>> liveData = viewModel.getIntervalStats(trackId, unitSystem, selectedInterval);
|
||||
liveData.observe(getActivity(), intervalList -> adapter.swapData(intervalList, unitSystem, isReportSpeed));
|
||||
|
||||
setIntervalsDropdownText();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public abstract class AutoCloseableIterator<T> implements Iterator<T>, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Override: AutoClosable.close() throws Exception.
|
||||
*/
|
||||
public abstract void close();
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Pair<String, String> valueAndUnit = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(getContext()).getDistanceParts(data.getStatisticsTrack().getTotalDistance());
|
||||
.build(getContext()).getDistanceParts(data.getStatisticsTrack().totalDistance());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
@@ -46,7 +46,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Pair<String, String> valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(data.getStatisticsTrack().getTotalTime()), null);
|
||||
Pair<String, String> valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(data.getStatisticsTrack().totalTime()), null);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
@@ -58,7 +58,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
String value = StringUtils.formatElapsedTime(data.getStatisticsTrack().getMovingTime());
|
||||
String value = StringUtils.formatElapsedTime(data.getStatisticsTrack().movingTime());
|
||||
|
||||
getBinding().statsValue.setText(value);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_moving_time));
|
||||
@@ -157,7 +157,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getStatisticsTrack().getMaxSpeed());
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getStatisticsTrack().maxSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
@@ -208,7 +208,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getStatisticsCurrentSegment().getAverageMovingSpeed());
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.currentSegment().getAverageMovingSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
@@ -220,7 +220,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
String value = StringUtils.formatElapsedTime(data.getStatisticsCurrentSegment().getTotalTime());
|
||||
String value = StringUtils.formatElapsedTime(data.currentSegment().totalTime());
|
||||
|
||||
getBinding().statsValue.setText(value);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_elapsed_segment_time));
|
||||
@@ -236,7 +236,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getStatisticsTrack().getMaxSpeed());
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getStatisticsTrack().maxSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
@@ -264,8 +264,9 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Float gain = data.getStatisticsTrack().altitudeGainLoss() != null ? data.getStatisticsTrack().altitudeGainLoss().gain_m() : null;
|
||||
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getStatisticsTrack().getTotalAltitudeGain(), unitSystem);
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), gain, unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
@@ -277,8 +278,9 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Float loss = data.getStatisticsTrack().altitudeGainLoss() != null ? data.getStatisticsTrack().altitudeGainLoss().loss_m() : null;
|
||||
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getStatisticsTrack().getTotalAltitudeLoss(), unitSystem);
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), loss, unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
|
||||
Reference in New Issue
Block a user