forked from upstream-mirrors/OpenTracks
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae4d2da262 |
+3
-3
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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 {
|
defaultConfig {
|
||||||
applicationId 'de.dennisguse.opentracks'
|
applicationId 'de.dennisguse.opentracks'
|
||||||
versionCode 6603
|
versionCode 6564
|
||||||
versionName "v4.26.0"
|
versionName "v4.25.2"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
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
|
|
||||||
+3
-3
@@ -89,12 +89,12 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|||||||
public class ExportImportTest {
|
public class ExportImportTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(50, TimeUnit.SECONDS); //TODO 5s
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
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
|
@Rule
|
||||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||||
|
|
||||||
@@ -747,7 +747,7 @@ public class ExportImportTest {
|
|||||||
sensorManager.getAltitudeChangeHandler().setAggregator(aggregatorBarometer);
|
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 location = new Location("mock");
|
||||||
location.setLatitude(latitude);
|
location.setLatitude(latitude);
|
||||||
location.setLongitude(longitude);
|
location.setLongitude(longitude);
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ public class TrackPointAssert {
|
|||||||
|
|
||||||
Assert.assertEquals("has altitude", expected.position().hasAltitude(), actual.position().hasAltitude());
|
Assert.assertEquals("has altitude", expected.position().hasAltitude(), actual.position().hasAltitude());
|
||||||
if (expected.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);
|
Assert.assertEquals("altitude", expected.position().altitude().toM(), actual.position().altitude().toM(), delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -135,7 +135,7 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
// then
|
// then
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().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
|
@MediumTest
|
||||||
|
|||||||
-112
@@ -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 String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 42;
|
private static final int DATABASE_VERSION = 43;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 40 -> upgradeFrom39to40(db);
|
case 40 -> upgradeFrom39to40(db);
|
||||||
case 41 -> upgradeFrom40to41(db);
|
case 41 -> upgradeFrom40to41(db);
|
||||||
case 42 -> upgradeFrom41to42(db);
|
case 42 -> upgradeFrom41to42(db);
|
||||||
|
case 43 -> upgradeFrom42to43(db);
|
||||||
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +116,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 39 -> downgradeFrom40to39(db);
|
case 39 -> downgradeFrom40to39(db);
|
||||||
case 40 -> downgradeFrom41to40(db);
|
case 40 -> downgradeFrom41to40(db);
|
||||||
case 41 -> downgradeFrom42to41(db);
|
case 41 -> downgradeFrom42to41(db);
|
||||||
|
case 42 -> downgradeFrom43to42(db);
|
||||||
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -794,4 +796,18 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
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
|
@Nullable
|
||||||
public static HeartRate ofOrNull(Float value) {
|
public static HeartRate ofOrNull(Float value) {
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
return new HeartRate(value);
|
HeartRate rate = of(value);
|
||||||
|
if (rate.isValid()) return rate;
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getBPM() {
|
public float getBPM() {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public record Position(
|
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 latitude,
|
||||||
@Nullable Double longitude,
|
@Nullable Double longitude,
|
||||||
@Nullable Distance horizontalAccuracy,
|
@Nullable Distance horizontalAccuracy,
|
||||||
@@ -17,6 +17,10 @@ public record Position(
|
|||||||
@Nullable Float bearing,
|
@Nullable Float bearing,
|
||||||
@Nullable Speed speed
|
@Nullable Speed speed
|
||||||
) {
|
) {
|
||||||
|
@Deprecated
|
||||||
|
public static Position empty() {
|
||||||
|
return of(null);
|
||||||
|
}
|
||||||
|
|
||||||
public static Position of(@NonNull Instant time) {
|
public static Position of(@NonNull Instant time) {
|
||||||
return new Position(
|
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) {
|
public TrackPoint with(Position newPosition) {
|
||||||
return new TrackPoint(
|
return new TrackPoint(
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -167,11 +167,8 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
|
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
if (createExportTasks(allInOneFile)) {
|
createExportTasks(allInOneFile);
|
||||||
nextExport(null);
|
nextExport(null);
|
||||||
} else {
|
|
||||||
onExportEnded();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}).start();
|
}).start();
|
||||||
} else {
|
} else {
|
||||||
@@ -220,12 +217,8 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
exportTasks.clear();
|
exportTasks.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean createExportTasks(boolean allInOneFile) {
|
private void createExportTasks(boolean allInOneFile) {
|
||||||
List<Track> tracks = contentProviderUtils.getTracks();
|
List<Track> tracks = contentProviderUtils.getTracks();
|
||||||
if (tracks.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
exportTasks = new ArrayList<>();
|
exportTasks = new ArrayList<>();
|
||||||
if (allInOneFile) {
|
if (allInOneFile) {
|
||||||
String filename = "OpenTracks-Backup";
|
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());
|
exportTasks.addAll(tracks.stream().map(it -> new ExportTask(null, trackFileFormat, List.of(it.id()))).toList());
|
||||||
}
|
}
|
||||||
trackExportTotalCount = exportTasks.size();
|
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.
|
* 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.
|
* @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;
|
HeartRate parsedHeartRate = null;
|
||||||
if (heartrate != null) {
|
if (heartrate != null) {
|
||||||
try {
|
try {
|
||||||
parsedHeartRate = HeartRate.of(Float.parseFloat(heartrate));
|
parsedHeartRate = HeartRate.ofOrNull(Float.parseFloat(heartrate));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), 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);
|
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.Cadence;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
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.Position;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||||
@@ -125,7 +126,6 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
|||||||
gpsHandler.resetAggregated();
|
gpsHandler.resetAggregated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public GpsHandler getGpsHandler() {
|
public GpsHandler getGpsHandler() {
|
||||||
return gpsHandler;
|
return gpsHandler;
|
||||||
}
|
}
|
||||||
@@ -185,6 +185,17 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
|||||||
return handler;
|
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) {
|
private SensorData<Cadence> getCadence(Instant now) {
|
||||||
{
|
{
|
||||||
SensorData<Cadence> value = cyclingCadenceHandler.getSensorData(now);
|
SensorData<Cadence> value = cyclingCadenceHandler.getSensorData(now);
|
||||||
@@ -239,19 +250,16 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SensorDataSet getSensorDataSet(Instant now) {
|
public SensorDataSet getSensorDataSet(Instant now) {
|
||||||
//We always need a Position with now
|
|
||||||
SensorData<Position> gpsSensorData = gpsHandler.getSensorData(now);
|
SensorData<Position> gpsSensorData = gpsHandler.getSensorData(now);
|
||||||
if (gpsSensorData != null && gpsSensorData.data() != null) {
|
|
||||||
gpsSensorData = new SensorData<>(gpsSensorData.data().with(now), gpsSensorData.sensorNameOrAddress());
|
var a = temperatureHandler.getSensorData(now);
|
||||||
} else {
|
var b = altitudeChangeHandler.getSensorData(now);
|
||||||
gpsSensorData = new SensorData<>(Position.of(now), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SensorDataSet(
|
return new SensorDataSet(
|
||||||
gpsSensorData,
|
gpsSensorData != null ? gpsSensorData : new SensorData<>(Position.of(now), ""), //We always need a Position(now)
|
||||||
getSpeed(now),
|
getSpeed(now),
|
||||||
getDistance(now),
|
getDistance(now),
|
||||||
heartRateHandler.getSensorData(now),
|
getHeartRate(now),
|
||||||
temperatureHandler.getSensorData(now),
|
temperatureHandler.getSensorData(now),
|
||||||
getCadence(now),
|
getCadence(now),
|
||||||
cyclingPowerHandler.getSensorData(now),
|
cyclingPowerHandler.getSensorData(now),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.dennisguse.opentracks.sensors.driver;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
@@ -13,6 +14,8 @@ import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
|||||||
|
|
||||||
public class HeartRateBluetooth implements BluetoothDriver.BluetoothParser<HeartRate> {
|
public class HeartRateBluetooth implements BluetoothDriver.BluetoothParser<HeartRate> {
|
||||||
|
|
||||||
|
private static final String TAG = BluetoothHandlerManagerHeartRate.class.getSimpleName();
|
||||||
|
|
||||||
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
|
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
|
||||||
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
|
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
|
||||||
new UUID(0x2A3700001000L, 0x800000805f9b34fbL)
|
new UUID(0x2A3700001000L, 0x800000805f9b34fbL)
|
||||||
@@ -35,7 +38,16 @@ public class HeartRateBluetooth implements BluetoothDriver.BluetoothParser<Heart
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
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
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public abstract class Aggregator<InputType, OutputType> {
|
|||||||
previous = next;
|
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.
|
* @return did we process data from a sensor.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
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.AltitudeGainLoss;
|
||||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||||
import de.dennisguse.opentracks.sensors.PressureSensorUtils;
|
import de.dennisguse.opentracks.sensors.PressureSensorUtils;
|
||||||
@@ -15,7 +14,7 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(@NonNull Raw<AtmosphericPressure> current) {
|
protected void computeValue(Raw<AtmosphericPressure> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
lastAcceptedSensorValue = current.value();
|
lastAcceptedSensorValue = current.value();
|
||||||
output = new AltitudeGainLoss(0f, 0f);
|
output = new AltitudeGainLoss(0f, 0f);
|
||||||
@@ -41,4 +40,6 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
|
|||||||
public void resetAggregated() {
|
public void resetAggregated() {
|
||||||
output = null;
|
output = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record Data(Altitude gain, Altitude loss) {}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -2,8 +2,6 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
@@ -19,7 +17,7 @@ public class AggregatorCyclingCadence extends Aggregator<CyclingCadenceBluetooth
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(@NonNull Raw<CyclingCadenceBluetooth.CrankData> current) {
|
protected void computeValue(Raw<CyclingCadenceBluetooth.CrankData> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -2,8 +2,6 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
@@ -22,7 +20,7 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<CyclingDistanceSp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(@NonNull Raw<CyclingDistanceSpeedBluetooth.WheelData> current) {
|
protected void computeValue(Raw<CyclingDistanceSpeedBluetooth.WheelData> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -1,7 +1,5 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Power;
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||||
|
|
||||||
@@ -12,7 +10,7 @@ public class AggregatorCyclingPower extends Aggregator<CyclingPowerBluetooth.Dat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void computeValue(@NonNull Raw<CyclingPowerBluetooth.Data> current) {
|
public void computeValue(Raw<CyclingPowerBluetooth.Data> current) {
|
||||||
this.output = current.value().power();
|
this.output = current.value().power();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.location.Location;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Position;
|
import de.dennisguse.opentracks.data.models.Position;
|
||||||
|
|
||||||
public class AggregatorGPS extends Aggregator<Location, Position> {
|
public class AggregatorGPS extends Aggregator<Location, Position> {
|
||||||
@@ -13,7 +15,7 @@ public class AggregatorGPS extends Aggregator<Location, Position> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(@NonNull Raw<Location> current) {
|
protected void computeValue(Raw<Location> current) {
|
||||||
output = Position.of(current.value(), current.time());
|
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.
|
* 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).
|
* 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;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
|
|
||||||
public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
|
public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
|
||||||
@@ -11,9 +9,7 @@ public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(@NonNull Raw<HeartRate> current) {
|
protected void computeValue(Raw<HeartRate> current) {
|
||||||
if (current.value().isValid()) {
|
|
||||||
this.output = current.value();
|
this.output = current.value();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public final class AggregatorRunning extends Aggregator<RunningSpeedAndCadenceBl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void computeValue(@NonNull Raw<RunningSpeedAndCadenceBluetooth.Data> current) {
|
public void computeValue(Raw<RunningSpeedAndCadenceBluetooth.Data> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -1,7 +1,5 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Temperature;
|
import de.dennisguse.opentracks.data.models.Temperature;
|
||||||
@@ -12,7 +10,7 @@ public class AggregatorTemperature extends Aggregator<Temperature, Temperature>
|
|||||||
super(sensorAddress, sensorName);
|
super(sensorAddress, sensorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void computeValue(@NonNull Raw<Temperature> current) {
|
protected void computeValue(Raw<Temperature> current) {
|
||||||
this.output = current.value();
|
this.output = current.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
|||||||
*/
|
*/
|
||||||
public record RecordingData(
|
public record RecordingData(
|
||||||
Track track,
|
Track track,
|
||||||
/*
|
|
||||||
* The current view on the SensorData.
|
|
||||||
* May contain data from previous measurements that are not present in SensorDataSet.
|
|
||||||
*/
|
|
||||||
TrackPoint latestTrackPoint,
|
TrackPoint latestTrackPoint,
|
||||||
SensorDataSet sensorDataSet,
|
SensorDataSet sensorDataSet,
|
||||||
Statistics currentSegment) {
|
Statistics currentSegment) {
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
|||||||
|
|
||||||
RecordingData getDataForUI() {
|
RecordingData getDataForUI() {
|
||||||
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation);
|
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
|
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
@@ -145,7 +145,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
|||||||
TrackBuilder trackBuilder = new TrackBuilder(track);
|
TrackBuilder trackBuilder = new TrackBuilder(track);
|
||||||
trackBuilder.setStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
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)
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||||
|
|
||||||
@VisibleForTesting
|
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
||||||
public static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
|
||||||
|
|
||||||
public static final RecordingStatus STATUS_DEFAULT = RecordingStatus.notRecording();
|
public static final RecordingStatus STATUS_DEFAULT = RecordingStatus.notRecording();
|
||||||
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
||||||
|
|||||||
@@ -102,10 +102,10 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lastTrackPointUISpeed != null)
|
if (lastTrackPointUISpeed != null)
|
||||||
position = position.with(lastTrackPointUISpeed.position().speed());
|
position.with(lastTrackPointUISpeed.position().speed());
|
||||||
|
|
||||||
if (lastTrackPointUIAltitude != null)
|
if (lastTrackPointUIAltitude != null)
|
||||||
position = position.with(lastTrackPointUIAltitude.position().altitude());
|
position.with(lastTrackPointUIAltitude.position().altitude());
|
||||||
|
|
||||||
TrackPoint currentTrackPoint = new TrackPoint(
|
TrackPoint currentTrackPoint = new TrackPoint(
|
||||||
null,
|
null,
|
||||||
|
|||||||
+1
-2
@@ -5,7 +5,6 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ public class RecordingLayoutIO {
|
|||||||
public static List<RecordingLayout> fromCsv(@NonNull String csv, @NonNull Resources resources) {
|
public static List<RecordingLayout> fromCsv(@NonNull String csv, @NonNull Resources resources) {
|
||||||
return CsvLayoutUtils.getCsvLines(csv).stream()
|
return CsvLayoutUtils.getCsvLines(csv).stream()
|
||||||
.map(line -> RecordingLayoutIO.fromCsvLine(line, resources))
|
.map(line -> RecordingLayoutIO.fromCsvLine(line, resources))
|
||||||
.collect(Collectors.toCollection(ArrayList::new));
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecordingLayout fromCsvLine(@NonNull String csvLine, @NonNull Resources resources) {
|
public static RecordingLayout fromCsvLine(@NonNull String csvLine, @NonNull Resources resources) {
|
||||||
|
|||||||
Reference in New Issue
Block a user