Compare commits

..

1 Commits

Author SHA1 Message Date
Dennis Guse ae4d2da262 Heartrate: ignore "0" if sent from sensor as this is an invalid value.
On import "0" is ignored.
2026-02-10 18:42:14 +01:00
29 changed files with 124 additions and 197 deletions
+3 -3
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:9.0.1'
classpath 'com.android.tools.build:gradle:9.0.0'
}
}
@@ -47,8 +47,8 @@ android {
defaultConfig {
applicationId 'de.dennisguse.opentracks'
versionCode 6603
versionName "v4.26.0"
versionCode 6564
versionName "v4.25.2"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -1,12 +0,0 @@
v4.26.0: OpenTracks
Changes:
- GPS is optional: configurable in the settings
- BLE sensor address/name is only shown after successful connection
Bugfix:
- Altitude is now shown again in EGM2008 (introduced in v4.25.0)
- Altitude and speed were shown as 0.0 briefly when data was stored
Developer:
- Refactored sensor subsystem
@@ -89,12 +89,12 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
public class ExportImportTest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(50, TimeUnit.SECONDS); //TODO 5s
@Rule
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
//For csv_export_only() as the timezone is hardcoded in the expectation.
//For csv_export_only() as we the timezone is hardcoded in the expectation.
@Rule
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
@@ -747,7 +747,7 @@ public class ExportImportTest {
sensorManager.getAltitudeChangeHandler().setAggregator(aggregatorBarometer);
}
public static void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
Location location = new Location("mock");
location.setLatitude(latitude);
location.setLongitude(longitude);
@@ -26,7 +26,6 @@ public class TrackPointAssert {
Assert.assertEquals("has altitude", expected.position().hasAltitude(), actual.position().hasAltitude());
if (expected.position().hasAltitude()) {
Assert.assertEquals("altitude", expected.position().altitude().getClass(), actual.position().altitude().getClass());
Assert.assertEquals("altitude", expected.position().altitude().toM(), actual.position().altitude().toM(), delta);
}
@@ -135,7 +135,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue()); TODO BUG: GPS can only be stopped if a service is recording.
}
@MediumTest
@@ -1,112 +0,0 @@
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertEquals;
import static de.dennisguse.opentracks.services.TrackRecordingService.RECORDING_DATA_UPDATE_INTERVAL;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import androidx.core.location.LocationManagerCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.importer.ExportImportTest;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceUITest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
private static final Context context = ApplicationProvider.getApplicationContext();
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@Test
public void getDataForUI_gps() throws TimeoutException, InterruptedException {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
RecordingData recordingData;
// given
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock("2020-02-02T02:02:02Z");
// when
service.startNewTrack();
//We do not want the real GPS to interfere.
GpsInternal gpsInternal = (GpsInternal)trackPointCreator.getSensorManager().getGpsHandler().driver;
LocationManagerCompat.removeUpdates(gpsInternal.locationManager, gpsInternal.locationListenerCompat);
Thread.sleep(RECORDING_DATA_UPDATE_INTERVAL.toMillis());
// then
assertEquals(new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
Position.of( Instant.parse("2020-02-02T02:02:02Z")),
null,
null,
null,
null,
null,
null
),
service.getRecordingDataObservable().getValue().latestTrackPoint());
// when
// In this step, the data is saved (and Aggregator.resetAggregated called), but data should still be shown in the UI.
trackPointCreator.setClock("2020-02-02T02:02:03Z");
ExportImportTest.sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 1f);
// then
Thread.sleep(RECORDING_DATA_UPDATE_INTERVAL.toMillis());
recordingData = service.getRecordingDataObservable().getValue();
new TrackPointAssert().assertEquals(new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3.1234567, 14.0014567, null,
Altitude.EGM2008.of(1013.05), null,
null,
Speed.of(15)),
null,
null,
null,
null,
null,
new AltitudeGainLoss(1, 1)
),
recordingData.latestTrackPoint());
}
}
@@ -29,7 +29,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 42;
private static final int DATABASE_VERSION = 43;
private final Context context;
@@ -86,6 +86,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 40 -> upgradeFrom39to40(db);
case 41 -> upgradeFrom40to41(db);
case 42 -> upgradeFrom41to42(db);
case 43 -> upgradeFrom42to43(db);
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
}
}
@@ -115,6 +116,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 39 -> downgradeFrom40to39(db);
case 40 -> downgradeFrom41to40(db);
case 41 -> downgradeFrom42to41(db);
case 42 -> downgradeFrom43to42(db);
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
}
}
@@ -794,4 +796,18 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.setTransactionSuccessful();
db.endTransaction();
}
private void upgradeFrom42to43(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("UPDATE trackpoints SET heartrate = null WHERE heartrate = 0.0");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom43to42(SQLiteDatabase db) {
// Downgrade not possible: invalid data replaced with null.
}
}
@@ -12,7 +12,10 @@ public record HeartRate(float value) {
@Nullable
public static HeartRate ofOrNull(Float value) {
if (value == null) return null;
return new HeartRate(value);
HeartRate rate = of(value);
if (rate.isValid()) return rate;
return null;
}
public float getBPM() {
@@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
import java.time.Instant;
public record Position(
@Nullable Instant time, //TODO It may be a good idea to move time out of Position
@Nullable Instant time,
@Nullable Double latitude,
@Nullable Double longitude,
@Nullable Distance horizontalAccuracy,
@@ -17,6 +17,10 @@ public record Position(
@Nullable Float bearing,
@Nullable Speed speed
) {
@Deprecated
public static Position empty() {
return of(null);
}
public static Position of(@NonNull Instant time) {
return new Position(
@@ -119,6 +119,19 @@ public record TrackPoint(
);
}
public TrackPoint removeHeartRate() {
return new TrackPoint(
id,
type,
position,
sensorDistance,
null,
cadence,
power,
altitudeGainLoss
);
}
public TrackPoint with(Position newPosition) {
return new TrackPoint(
id,
@@ -167,11 +167,8 @@ public class ExportActivity extends AbstractActivity {
new Thread(() -> {
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
runOnUiThread(() -> {
if (createExportTasks(allInOneFile)) {
nextExport(null);
} else {
onExportEnded();
}
createExportTasks(allInOneFile);
nextExport(null);
});
}).start();
} else {
@@ -220,12 +217,8 @@ public class ExportActivity extends AbstractActivity {
exportTasks.clear();
}
private boolean createExportTasks(boolean allInOneFile) {
private void createExportTasks(boolean allInOneFile) {
List<Track> tracks = contentProviderUtils.getTracks();
if (tracks.isEmpty()) {
return false;
}
exportTasks = new ArrayList<>();
if (allInOneFile) {
String filename = "OpenTracks-Backup";
@@ -234,7 +227,6 @@ public class ExportActivity extends AbstractActivity {
exportTasks.addAll(tracks.stream().map(it -> new ExportTask(null, trackFileFormat, List.of(it.id()))).toList());
}
trackExportTotalCount = exportTasks.size();
return true;
}
/**
@@ -399,7 +391,7 @@ public class ExportActivity extends AbstractActivity {
/**
* Try to resolve the conflict if user gave the info needed.
* Otherwise, shows the buttons and views needed to gives the user the possibility of take a decision.
* Otherwise shows the buttons and views needed to gives the user the possibility of take a decision.
*
* @return true if it could resolve the conflict or false otherwise.
*/
@@ -356,7 +356,7 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
HeartRate parsedHeartRate = null;
if (heartrate != null) {
try {
parsedHeartRate = HeartRate.of(Float.parseFloat(heartrate));
parsedHeartRate = HeartRate.ofOrNull(Float.parseFloat(heartrate));
} catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
}
@@ -190,6 +190,15 @@ public class TrackImporter {
}
trackPoints.set(i, newCurrent);
}
}
for (int i = 0; i < trackPoints.size(); i++) {
TrackPoint current = trackPoints.get(i);
if (!current.heartRate().isValid()) {
current = current.removeHeartRate();
trackPoints.set(i, current);
}
}
}
@@ -14,6 +14,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
@@ -125,7 +126,6 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
gpsHandler.resetAggregated();
}
@VisibleForTesting
public GpsHandler getGpsHandler() {
return gpsHandler;
}
@@ -185,6 +185,17 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
return handler;
}
private SensorData<HeartRate> getHeartRate(Instant now) {
SensorData<HeartRate> value = heartRateHandler.getSensorData(now);
if (value != null) {
//TODO Move this check into HeartRateHandler
if (value.data().isValid()) return value;
}
return null;
}
private SensorData<Cadence> getCadence(Instant now) {
{
SensorData<Cadence> value = cyclingCadenceHandler.getSensorData(now);
@@ -239,19 +250,16 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
}
public SensorDataSet getSensorDataSet(Instant now) {
//We always need a Position with now
SensorData<Position> gpsSensorData = gpsHandler.getSensorData(now);
if (gpsSensorData != null && gpsSensorData.data() != null) {
gpsSensorData = new SensorData<>(gpsSensorData.data().with(now), gpsSensorData.sensorNameOrAddress());
} else {
gpsSensorData = new SensorData<>(Position.of(now), "");
}
var a = temperatureHandler.getSensorData(now);
var b = altitudeChangeHandler.getSensorData(now);
return new SensorDataSet(
gpsSensorData,
gpsSensorData != null ? gpsSensorData : new SensorData<>(Position.of(now), ""), //We always need a Position(now)
getSpeed(now),
getDistance(now),
heartRateHandler.getSensorData(now),
getHeartRate(now),
temperatureHandler.getSensorData(now),
getCadence(now),
cyclingPowerHandler.getSensorData(now),
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.sensors.driver;
import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -13,6 +14,8 @@ import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
public class HeartRateBluetooth implements BluetoothDriver.BluetoothParser<HeartRate> {
private static final String TAG = BluetoothHandlerManagerHeartRate.class.getSimpleName();
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
new UUID(0x2A3700001000L, 0x800000805f9b34fbL)
@@ -35,7 +38,16 @@ public class HeartRateBluetooth implements BluetoothDriver.BluetoothParser<Heart
@Override
public HeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
return parseHeartRate(characteristic);
HeartRate heartRate = parseHeartRate(characteristic);
if (heartRate == null) return null;
if (heartRate.value() == 0.0) {
Log.d(TAG, "Invalid heartrate received. Ignoring.");
return null;
}
return heartRate;
}
@VisibleForTesting
@@ -34,7 +34,7 @@ public abstract class Aggregator<InputType, OutputType> {
previous = next;
}
protected abstract void computeValue(@NonNull Raw<InputType> current);
protected abstract void computeValue(Raw<InputType> current);
/**
* @return did we process data from a sensor.
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
import de.dennisguse.opentracks.sensors.PressureSensorUtils;
@@ -15,7 +14,7 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
}
@Override
protected void computeValue(@NonNull Raw<AtmosphericPressure> current) {
protected void computeValue(Raw<AtmosphericPressure> current) {
if (previous == null) {
lastAcceptedSensorValue = current.value();
output = new AltitudeGainLoss(0f, 0f);
@@ -41,4 +40,6 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
public void resetAggregated() {
output = null;
}
public record Data(Altitude gain, Altitude loss) {}
}
@@ -2,8 +2,6 @@ package de.dennisguse.opentracks.sensors.sensorData;
import android.util.Log;
import androidx.annotation.NonNull;
import java.time.Duration;
import de.dennisguse.opentracks.data.models.Cadence;
@@ -19,7 +17,7 @@ public class AggregatorCyclingCadence extends Aggregator<CyclingCadenceBluetooth
}
@Override
protected void computeValue(@NonNull Raw<CyclingCadenceBluetooth.CrankData> current) {
protected void computeValue(Raw<CyclingCadenceBluetooth.CrankData> current) {
if (previous == null) {
return;
}
@@ -2,8 +2,6 @@ package de.dennisguse.opentracks.sensors.sensorData;
import android.util.Log;
import androidx.annotation.NonNull;
import java.time.Duration;
import de.dennisguse.opentracks.data.models.Distance;
@@ -22,7 +20,7 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<CyclingDistanceSp
}
@Override
protected void computeValue(@NonNull Raw<CyclingDistanceSpeedBluetooth.WheelData> current) {
protected void computeValue(Raw<CyclingDistanceSpeedBluetooth.WheelData> current) {
if (previous == null) {
return;
}
@@ -1,7 +1,5 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
@@ -12,7 +10,7 @@ public class AggregatorCyclingPower extends Aggregator<CyclingPowerBluetooth.Dat
}
@Override
public void computeValue(@NonNull Raw<CyclingPowerBluetooth.Data> current) {
public void computeValue(Raw<CyclingPowerBluetooth.Data> current) {
this.output = current.value().power();
}
}
@@ -4,6 +4,8 @@ import android.location.Location;
import androidx.annotation.NonNull;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Position;
public class AggregatorGPS extends Aggregator<Location, Position> {
@@ -13,7 +15,7 @@ public class AggregatorGPS extends Aggregator<Location, Position> {
}
@Override
protected void computeValue(@NonNull Raw<Location> current) {
protected void computeValue(Raw<Location> current) {
output = Position.of(current.value(), current.time());
}
@@ -22,7 +24,17 @@ public class AggregatorGPS extends Aggregator<Location, Position> {
/*
* GPS data is not an aggregated value, but for now we want to ensure to only save the data once.
* The data is too large to save it more often than needed (i.e., duplicated values).
* TODO: this behavior can be changed if TrackRecordingManager.insertTrackPoint() would strip GPS data if it was already saved. This would simplify TrackPointCreator.createCurrentTrackPoint()
*/
resetOutdated();
output = null;
}
@NonNull
@Override
public Position getAggregatedValue(Instant now) {
Position value = super.getAggregatedValue(now);
if (value == null) value = Position.empty();
return value.with(now);
}
}
@@ -1,7 +1,5 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.HeartRate;
public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
@@ -11,9 +9,7 @@ public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
}
@Override
protected void computeValue(@NonNull Raw<HeartRate> current) {
if (current.value().isValid()) {
this.output = current.value();
}
protected void computeValue(Raw<HeartRate> current) {
this.output = current.value();
}
}
@@ -19,7 +19,7 @@ public final class AggregatorRunning extends Aggregator<RunningSpeedAndCadenceBl
}
@Override
public void computeValue(@NonNull Raw<RunningSpeedAndCadenceBluetooth.Data> current) {
public void computeValue(Raw<RunningSpeedAndCadenceBluetooth.Data> current) {
if (previous == null) {
return;
}
@@ -1,7 +1,5 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import java.time.Duration;
import de.dennisguse.opentracks.data.models.Temperature;
@@ -12,7 +10,7 @@ public class AggregatorTemperature extends Aggregator<Temperature, Temperature>
super(sensorAddress, sensorName);
}
protected void computeValue(@NonNull Raw<Temperature> current) {
protected void computeValue(Raw<Temperature> current) {
this.output = current.value();
}
@@ -10,10 +10,6 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
*/
public record RecordingData(
Track track,
/*
* The current view on the SensorData.
* May contain data from previous measurements that are not present in SensorDataSet.
*/
TrackPoint latestTrackPoint,
SensorDataSet sensorDataSet,
Statistics currentSegment) {
@@ -131,10 +131,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
RecordingData getDataForUI() {
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation);
TrackPoint trackPoint = current.first;
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, trackPoint);
TrackPoint TrackPointWithEGM2008 = ALTITUDE_CORRECTION_MANAGER.correctAltitude(context, trackPoint);
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, current.first);
ALTITUDE_CORRECTION_MANAGER.correctAltitude(context, current.first);
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
if (track == null) {
@@ -145,7 +145,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
TrackBuilder trackBuilder = new TrackBuilder(track);
trackBuilder.setStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
return new RecordingData(trackBuilder.getTrack(), TrackPointWithEGM2008, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
return new RecordingData(trackBuilder.getTrack(), current.first, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@@ -48,8 +48,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private static final String TAG = TrackRecordingService.class.getSimpleName();
@VisibleForTesting
public static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
public static final RecordingStatus STATUS_DEFAULT = RecordingStatus.notRecording();
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
@@ -102,10 +102,10 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
}
if (lastTrackPointUISpeed != null)
position = position.with(lastTrackPointUISpeed.position().speed());
position.with(lastTrackPointUISpeed.position().speed());
if (lastTrackPointUIAltitude != null)
position = position.with(lastTrackPointUIAltitude.position().altitude());
position.with(lastTrackPointUIAltitude.position().altitude());
TrackPoint currentTrackPoint = new TrackPoint(
null,
@@ -5,7 +5,6 @@ import android.util.Log;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -23,7 +22,7 @@ public class RecordingLayoutIO {
public static List<RecordingLayout> fromCsv(@NonNull String csv, @NonNull Resources resources) {
return CsvLayoutUtils.getCsvLines(csv).stream()
.map(line -> RecordingLayoutIO.fromCsvLine(line, resources))
.collect(Collectors.toCollection(ArrayList::new));
.toList();
}
public static RecordingLayout fromCsvLine(@NonNull String csvLine, @NonNull Resources resources) {