TrackPoint is now a record.

This commit is contained in:
Dennis Guse
2025-12-23 22:58:33 +01:00
committed by Dennis Guse
parent 42ae582cd8
commit 015352c989
48 changed files with 1851 additions and 1310 deletions
@@ -22,11 +22,11 @@ import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
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.data.statistics.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.TrackNameUtils;
public class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -160,14 +160,14 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
* @return TrackPoint was stored?
*/
synchronized boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) {
if (trackPoint.hasSpeed()) {
if (trackPoint.position().hasSpeed()) {
lastTrackPointUIWithSpeed = trackPoint;
}
if (trackPoint.hasAltitude()) {
if (trackPoint.position().hasAltitude()) {
lastTrackPointUIWithAltitude = trackPoint;
}
if (trackPoint.getType() == TrackPoint.Type.IDLE) {
if (trackPoint.type() == TrackPoint.Type.IDLE) {
insertTrackPoint(trackPoint, true);
handler.removeCallbacks(ON_IDLE);
return true;
@@ -180,14 +180,14 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
return true;
}
if (trackPoint.hasLocation() && lastStoredTrackPointWithLocation == null) {
if (trackPoint.position().hasLocation() && lastStoredTrackPointWithLocation == null) {
insertTrackPoint(trackPoint, true);
scheduleNewIdleTimeout();
return true;
}
if (!trackPoint.hasLocation() && !trackPoint.hasSensorDistance()) {
if (!trackPoint.position().hasLocation() && trackPoint.sensorDistance() == null) {
Duration minStorageInterval = Duration.ofSeconds(10); // TODO Should be configurable.
boolean shouldStore = lastStoredTrackPoint.getTime().plus(minStorageInterval)
.isBefore(trackPoint.getTime());
@@ -201,14 +201,14 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
}
Distance distanceToLastStoredTrackPoint;
if (trackPoint.hasLocation() && !lastStoredTrackPoint.hasLocation()) {
if (trackPoint.position().hasLocation() && !lastStoredTrackPoint.position().hasLocation()) {
distanceToLastStoredTrackPoint = trackPoint.distanceToPreviousFromLocation(lastStoredTrackPointWithLocation);
} else {
distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint);
}
if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
trackPoint = trackPoint.with(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackPoint, true);
scheduleNewIdleTimeout();
@@ -248,8 +248,8 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
} else {
insertTrackPointHelper(lastTrackPoint);
// Remove the sensorDistance from trackPoint that is already going be stored with lastTrackPoint.
trackPoint.minusCumulativeSensorData(lastTrackPoint);
// Remove the sensorDistance from trackPoint that is already going be stored with lastTrackPoint.
trackPoint = trackPoint.minusCumulativeSensorData(lastTrackPoint);
}
}
lastTrackPoint = null;
@@ -264,7 +264,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics());
lastStoredTrackPoint = trackPoint;
if (trackPoint.hasLocation()) {
if (trackPoint.position().hasLocation()) {
lastStoredTrackPointWithLocation = lastStoredTrackPoint;
}
} catch (SQLiteException e) {
@@ -255,7 +255,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
"",
"",
"",
trackPoint.getPosition(),
trackPoint.position(),
null
);
return new ContentProviderUtils(this).insertMarker(marker);
@@ -109,17 +109,17 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
.build(context);
//TODO The decision if a trackpoint is not accurate enough should happen in the TrackRecordingManager (via RecordingData).
if (recordingData.latestTrackPoint().hasHorizontalAccuracy()) {
formattedAccuracy = formatter.formatDistance(recordingData.latestTrackPoint().getHorizontalAccuracy());
if (recordingData.latestTrackPoint().position().hasHorizontalAccuracy()) {
formattedAccuracy = formatter.formatDistance(recordingData.latestTrackPoint().position().horizontalAccuracy());
boolean currentLocationWasAccurate = recordingData.latestTrackPoint().getHorizontalAccuracy().lessThan(thresholdHorizontalAccuracy);
boolean currentLocationWasAccurate = recordingData.latestTrackPoint().position().horizontalAccuracy().lessThan(thresholdHorizontalAccuracy);
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
notificationBuilder.setOnlyAlertOnce(!shouldAlert);
previousLocationWasAccurate = currentLocationWasAccurate;
}
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(recordingData.trackStatistics().totalDistance())));
String formattedSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context).formatSpeed(recordingData.latestTrackPoint().getSpeed());
String formattedSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context).formatSpeed(recordingData.latestTrackPoint().position().speed());
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, formattedSpeed));
notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
updateNotification();
@@ -18,7 +18,6 @@ import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.TtsSpan;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -35,9 +34,10 @@ 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.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.util.StringUtils;
class VoiceAnnouncementUtils {
@@ -160,9 +160,9 @@ class VoiceAnnouncementUtils {
}
}
Pair<HeartRate, String> heartrate = sensorDataSet.getHeartRate();
if (shouldVoiceAnnounceHeartRateCurrent() && heartrate != null && heartrate.first != null && heartrate.first.isValid()) { //TODO Check has an announcable value?
int averageHeartRate = Math.round(heartrate.first.getBPM());
SensorData<HeartRate> heartrate = sensorDataSet.heartRate();
if (shouldVoiceAnnounceHeartRateCurrent() && heartrate != null && heartrate.data().isValid()) { //TODO Check has an announceable value?
int averageHeartRate = Math.round(heartrate.data().getBPM());
builder.append(" ")
.append(context.getString(R.string.current_heart_rate));
@@ -19,22 +19,22 @@ public class AltitudeCorrectionManager {
private static final String TAG = AltitudeCorrectionManager.class.getSimpleName();
public void correctAltitude(Context context, TrackPoint trackPoint) {
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
return;
public TrackPoint correctAltitude(Context context, TrackPoint trackPoint) {
if (!trackPoint.position().hasLocation() || !trackPoint.position().hasAltitude()) {
return trackPoint;
}
// TODO The following is doing IO and should not be done in main thread.
final TrackPoint[] newTrackPoint = {null}; //TODO this is kind of ugly
// AltitudeConverterCompat uses internally a RoomDatabase that cannot be access from main thread and thus fails on version <= 34.
Thread t = new Thread(() -> {
try {
Location loc = trackPoint.getPosition().toLocation();
AltitudeConverterCompat.addMslAltitudeToLocation(context, loc);
trackPoint.setAltitude(Altitude.EGM2008.of(LocationCompat.getMslAltitudeMeters(loc)));
} catch (IOException e) {
Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage());
}
});
try {
Location loc = trackPoint.position().toLocation();
AltitudeConverterCompat.addMslAltitudeToLocation(context, loc);
newTrackPoint[0] = TrackPoint.with(trackPoint, Altitude.EGM2008.of(LocationCompat.getMslAltitudeMeters(loc)));
} catch (IOException e) {
Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage());
}
});
t.start();
try {
@@ -42,5 +42,7 @@ public class AltitudeCorrectionManager {
} catch (InterruptedException e) {
Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage());
}
return newTrackPoint[0];
}
}
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
@@ -15,6 +14,7 @@ import java.time.Instant;
import java.time.ZoneId;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.GpsStatusValue;
import de.dennisguse.opentracks.sensors.SensorManager;
@@ -55,15 +55,6 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
sensorManager.reset();
}
private SensorDataSet addSensorData(TrackPoint trackPoint) {
if (!isStarted()) {
Log.w(TAG, "Not started, should not be called.");
return null;
}
return sensorManager.fill(trackPoint);
}
public void stop() {
sensorManager.stop(context);
this.context = null;
@@ -72,13 +63,8 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
/**
* Got a new TrackPoint from Bluetooth only; contains no GPS location.
*/
public synchronized void onChange(@NonNull SensorDataSet unused) {
onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, createNow()));
}
@VisibleForTesting
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
addSensorData(trackPoint);
public synchronized void onChange() {
TrackPoint trackPoint = createTrackPoint(TrackPoint.Type.TRACKPOINT);
boolean stored = service.newTrackPoint(trackPoint, PreferencesUtils.getThresholdHorizontalAccuracy()); //TODO Cache preference for performance
if (stored) {
@@ -91,40 +77,73 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
}
public synchronized TrackPoint createSegmentEnd() {
TrackPoint segmentEnd = TrackPoint.createSegmentEndWithTime(createNow());
addSensorData(segmentEnd);
TrackPoint segmentEnd = createTrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
reset();
return segmentEnd;
}
public synchronized TrackPoint createIdle() {
TrackPoint idle = new TrackPoint(TrackPoint.Type.IDLE, createNow());
addSensorData(idle);
TrackPoint idle = createTrackPoint(TrackPoint.Type.IDLE);
reset();
return idle;
}
public Pair<TrackPoint, SensorDataSet> createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) {
TrackPoint currentTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, createNow());
Instant now = createNow();
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
SensorDataSet sensorDataSet = addSensorData(currentTrackPoint);
Position position = sensorDataSet.position().data();
if (!currentTrackPoint.hasLocation() && lastStoredTrackPointWithLocation != null && lastStoredTrackPointWithLocation.hasLocation()) {
if (!position.hasLocation()
&& lastStoredTrackPointWithLocation != null
&& lastStoredTrackPointWithLocation.position().hasLocation()) {
//We are taking the coordinates from the last stored TrackPoint, so the distance is monotonously increasing.
currentTrackPoint.setPosition(lastStoredTrackPointWithLocation.getPosition());
position = position.withCoordinates(lastStoredTrackPointWithLocation.position());
}
if (lastTrackPointUISpeed != null) {
currentTrackPoint.setSpeed(lastTrackPointUISpeed.getSpeed());
}
if (lastTrackPointUISpeed != null)
position.with(lastTrackPointUISpeed.position().speed());
if (lastTrackPointUIAltitude != null) {
currentTrackPoint.setAltitude(lastTrackPointUIAltitude.getAltitude());
}
if (lastTrackPointUIAltitude != null)
position.with(lastTrackPointUIAltitude.position().altitude());
TrackPoint currentTrackPoint = new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
position,
sensorDataSet.distance() != null ? sensorDataSet.distance().data() : null,
sensorDataSet.heartRate() != null ? sensorDataSet.heartRate().data() : null,
sensorDataSet.cadence() != null ? sensorDataSet.cadence().data() : null,
sensorDataSet.power() != null ? sensorDataSet.power().data() : null,
sensorDataSet.altitudeGainLoss() != null ? sensorDataSet.altitudeGainLoss().data() : null
);
return new Pair<>(currentTrackPoint, sensorDataSet);
}
private TrackPoint createTrackPoint(TrackPoint.Type type) {
Instant now = createNow();
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
Position position = sensorDataSet.position().data();
if (sensorDataSet.speed() != null) {
position = position.with(sensorDataSet.speed().data());
}
return
new TrackPoint(
null,
type,
position,
sensorDataSet.distance() != null ? sensorDataSet.distance().data() : null,
sensorDataSet.heartRate() != null ? sensorDataSet.heartRate().data() : null,
sensorDataSet.cadence() != null ? sensorDataSet.cadence().data() : null,
sensorDataSet.power() != null ? sensorDataSet.power().data() : null,
sensorDataSet.altitudeGainLoss() != null ? sensorDataSet.altitudeGainLoss().data() : null
);
}
public Instant createNow() {
return Instant.now(clock);
}