mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-01 01:02:21 +02:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6566904906 | |||
| 1d0e8f59b7 | |||
| be7987f88a | |||
| a92a1e7838 | |||
| 8cba53cfe4 | |||
| fcedb9c444 | |||
| 2ec82af8f1 | |||
| 447d1bb7a3 | |||
| 2f6765e45f | |||
| 90bf2e6ab0 | |||
| d12423522e | |||
| 24f052491b | |||
| 6b4e7d7d9b | |||
| 00fcbb070a | |||
| 708cbf0b2a | |||
| 60e2fe57f9 | |||
| 14cebf3fec | |||
| b651a8ca6a | |||
| d75472ef11 | |||
| 0b849c3d55 | |||
| 1c767c41a7 | |||
| dfa80d61d7 | |||
| fa558c9ab7 | |||
| a1a363698f | |||
| b79acfb942 | |||
| 24ea718ff2 | |||
| 6d187fa66d | |||
| ea27db13fa | |||
| 29d2e7c78c | |||
| 4b7badbecc | |||
| 0dcb8010e2 | |||
| 0a9d4b207c | |||
| c6cb388ce4 | |||
| e6e009ba14 | |||
| f8aa456549 | |||
| 167d90332e | |||
| 719a052f34 | |||
| 244ca26210 | |||
| 476f3a517b | |||
| 289e93eb5d | |||
| 18ff96733e | |||
| 0f2c5adf26 | |||
| 74954df600 | |||
| 1a14fed287 | |||
| 744853ee91 | |||
| 597e1b2c7f | |||
| 8156993921 | |||
| 9890dc28bd |
+3
-3
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:9.0.0'
|
||||
classpath 'com.android.tools.build:gradle:9.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'de.dennisguse.opentracks'
|
||||
versionCode 6554
|
||||
versionName "v4.25.1"
|
||||
versionCode 6609
|
||||
versionName "v4.26.1"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.25.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Restored compatibility with OSMDashboard (Dashboard API protocol version 2)
|
||||
|
||||
Bugfix:
|
||||
- Update UI immediately after resuming a track
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
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
|
||||
@@ -0,0 +1,6 @@
|
||||
v4.26.1: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Crash when deleting a UI layout
|
||||
- Crash when heartrate sensor data could not be decoded
|
||||
- Crash when exporting zero tracks
|
||||
+27
-25
@@ -66,7 +66,8 @@ import de.dennisguse.opentracks.data.models.TrackBuilder;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||
@@ -74,10 +75,9 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpee
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
@@ -94,17 +94,19 @@ public class ExportImportTest {
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
//For csv_export_only() as we the timezone is hardcoded in the expectation.
|
||||
//For csv_export_only() as the timezone is hardcoded in the expectation.
|
||||
@Rule
|
||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
PreferencesUtils.resetPreferences(context, true);
|
||||
}
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
@@ -162,7 +164,7 @@ public class ExportImportTest {
|
||||
"Marker 1 desc",
|
||||
"Marker 1 typeLocalized",
|
||||
new Position(
|
||||
Instant.parse("2020-02-02T02:02:03Z"),
|
||||
Instant.parse("2020-02-02T02:02:03Z"),
|
||||
3.1234567, 14.0014567,
|
||||
Distance.of(10),
|
||||
Altitude.WGS84.of(1020.25),
|
||||
@@ -197,7 +199,7 @@ public class ExportImportTest {
|
||||
));
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||
trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
|
||||
trackPointCreator.getSensorManager().clear();
|
||||
service.endCurrentTrack();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||
@@ -212,7 +214,7 @@ public class ExportImportTest {
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f);
|
||||
|
||||
trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
|
||||
trackPointCreator.getSensorManager().clear();
|
||||
trackPointCreator.setClock("2020-02-02T02:04:00Z");
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -660,17 +662,17 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain, float temperature) {
|
||||
SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
|
||||
private static void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain, float temperature) {
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
|
||||
AggregatorCyclingPower cyclingPower = new AggregatorCyclingPower("", "");
|
||||
cyclingPower.add(new Raw<>(trackPointCreator.createNow(), new BluetoothHandlerCyclingPower.Data(Power.of(power), null)));
|
||||
sensorDataAggregator.add(cyclingPower);
|
||||
cyclingPower.add(trackPointCreator.getNow(), new CyclingPowerBluetooth.Data(Power.of(power), null));
|
||||
sensorManager.setAggregator(cyclingPower);
|
||||
|
||||
|
||||
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
|
||||
avgHeartRate.add(new Raw<>(trackPointCreator.createNow(), HeartRate.of(heartRate)));
|
||||
sensorDataAggregator.add(avgHeartRate);
|
||||
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(heartRate));
|
||||
sensorManager.setAggregator(avgHeartRate);
|
||||
|
||||
AggregatorCyclingCadence cyclingCadence = new AggregatorCyclingCadence("", "") {
|
||||
@NonNull
|
||||
@@ -684,7 +686,7 @@ public class ExportImportTest {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sensorDataAggregator.add(cyclingCadence);
|
||||
sensorManager.setAggregator(cyclingCadence);
|
||||
|
||||
if (distance != null && speed != null) {
|
||||
AggregatorCyclingDistanceSpeed aggregatorCyclingDistanceSpeed = new AggregatorCyclingDistanceSpeed("", "") {
|
||||
@@ -699,14 +701,14 @@ public class ExportImportTest {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sensorDataAggregator.add(aggregatorCyclingDistanceSpeed);
|
||||
sensorManager.setAggregator(aggregatorCyclingDistanceSpeed);
|
||||
} else {
|
||||
sensorDataAggregator.add(new AggregatorCyclingDistanceSpeed("", ""));
|
||||
sensorManager.setAggregator(new AggregatorCyclingDistanceSpeed("", ""));
|
||||
}
|
||||
|
||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||
|
||||
sensorDataAggregator.add(new AggregatorTemperature("", "'") {
|
||||
sensorManager.setAggregator(new AggregatorTemperature("", "'") {
|
||||
@NonNull
|
||||
@Override
|
||||
public Temperature getAggregatedValue(Instant now) {
|
||||
@@ -722,11 +724,11 @@ public class ExportImportTest {
|
||||
trackPointCreator.onChange();
|
||||
}
|
||||
|
||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) {
|
||||
SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
|
||||
private static void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) {
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
|
||||
if (altitudeGain == null) {
|
||||
sensorDataAggregator.add(new AggregatorBarometer("test", null));
|
||||
sensorManager.getAltitudeChangeHandler().setAggregator(new AggregatorBarometer("test", null));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -742,10 +744,10 @@ public class ExportImportTest {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sensorDataAggregator.add(aggregatorBarometer);
|
||||
sensorManager.getAltitudeChangeHandler().setAggregator(aggregatorBarometer);
|
||||
}
|
||||
|
||||
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
|
||||
public static 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);
|
||||
@@ -757,6 +759,6 @@ public class ExportImportTest {
|
||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||
|
||||
trackPointCreator.setClock(time);
|
||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
||||
trackPointCreator.getSensorManager().getGpsHandler().onDataReceived(location);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package de.dennisguse.opentracks.publicapi;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -18,152 +19,206 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DataProviderTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final ContentResolver resolver = ApplicationProvider.getApplicationContext().getContentResolver();
|
||||
|
||||
@Ignore(value = "Compatibility to v2 is not implemented.") //TODO
|
||||
@Test
|
||||
public void supportsVersion2() {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
public void supportsVersion2_tracks() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
String[] expectedTracksColumns = new String[]{
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"activity_type_localized",
|
||||
"starttime",
|
||||
"stoptime",
|
||||
"totaltime",
|
||||
"movingtime",
|
||||
"totaldistance",
|
||||
"maxspeed",
|
||||
"avgspeed",
|
||||
"avgmovingspeed",
|
||||
"minelevation",
|
||||
"maxelevation",
|
||||
"elevationgain",
|
||||
"elevationloss"
|
||||
};
|
||||
|
||||
//Tracks
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/tracks"),
|
||||
new String[]{
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"activity_type_localized",
|
||||
"starttime",
|
||||
"stoptime",
|
||||
"totaltime",
|
||||
"movingtime",
|
||||
"totaldistance",
|
||||
"maxspeed",
|
||||
"avgspeed",
|
||||
"avgmovingspeed",
|
||||
"minelevation",
|
||||
"maxelevation",
|
||||
"elevationgain",
|
||||
"elevationloss"
|
||||
},
|
||||
uri,
|
||||
expectedTracksColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
Assert.assertNotNull(cursor);
|
||||
}
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.track", resolver.getType(uri));
|
||||
|
||||
//TrackPoints
|
||||
try (Cursor cursor = resolver.query(
|
||||
Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/trackpoints"),
|
||||
new String[]{
|
||||
"_id",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"time",
|
||||
"type",
|
||||
"latitude",
|
||||
"speed"
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
Assert.assertNotNull(cursor);
|
||||
}
|
||||
|
||||
//Markers
|
||||
try (Cursor cursor = resolver.query(
|
||||
Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/markers"),
|
||||
new String[]{
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"trackid",
|
||||
"longitude",
|
||||
"latitude",
|
||||
"photoUrl"
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
Assert.assertNotNull(cursor);
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTracksColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion3() {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
public void supportsVersion2_trackpoints() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
String[] expectedTrackpointsColumns = {
|
||||
"_id",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"time",
|
||||
"type",
|
||||
"speed"
|
||||
};
|
||||
|
||||
//Tracks
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/tracks"),
|
||||
new String[]{
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"activity_type",
|
||||
"activity_type_localized",
|
||||
"time_start",
|
||||
"time_stop",
|
||||
"duration_total",
|
||||
"duration_moving",
|
||||
"distance",
|
||||
"speed_max",
|
||||
"altitude_min",
|
||||
"altitude_max",
|
||||
"altitude_gain",
|
||||
"altitude_loss"
|
||||
},
|
||||
uri,
|
||||
expectedTrackpointsColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
Assert.assertNotNull(cursor);
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTrackpointsColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
//TrackPoints
|
||||
@Test
|
||||
public void supportsVersion2_markers() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
String[] expectedMarkersColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"trackid",
|
||||
"longitude",
|
||||
"latitude",
|
||||
"photoUrl",
|
||||
"icon"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/trackpoints"),
|
||||
new String[]{
|
||||
"_id",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"time",
|
||||
"type",
|
||||
"latitude",
|
||||
"speed"
|
||||
},
|
||||
uri,
|
||||
expectedMarkersColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
Assert.assertNotNull(cursor);
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.waypoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedMarkersColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
//Markers
|
||||
@Test
|
||||
public void supportsVersion3_tracks() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
String[] expectedTracksColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"activity_type",
|
||||
"activity_type_localized",
|
||||
"time_start",
|
||||
"time_stop",
|
||||
"duration_total",
|
||||
"duration_moving",
|
||||
"distance",
|
||||
"speed_max",
|
||||
"altitude_min",
|
||||
"altitude_max",
|
||||
"altitude_gain",
|
||||
"altitude_loss"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/markers"),
|
||||
new String[]{
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"marker_type_localized",
|
||||
"trackid",
|
||||
"longitude",
|
||||
"latitude",
|
||||
"photoUrl"
|
||||
},
|
||||
uri,
|
||||
expectedTracksColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
Assert.assertNotNull(cursor);
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.track", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTracksColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion3_trackpoints() {
|
||||
//given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
String[] expectedTrackpointsColumns = {
|
||||
"_id",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"time",
|
||||
"type",
|
||||
"speed"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedTrackpointsColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
//then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTrackpointsColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion3_markers() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
String[] expectedMarkersColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"marker_type_localized",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"photoUrl"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedMarkersColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.waypoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedMarkersColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -13,11 +13,11 @@ public class BluetoothHandlerBarometricPressureTest {
|
||||
@Test
|
||||
public void parseEnvironmentalSensing_Pa() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerBarometricPressure.BAROMETRIC_PRESSURE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BarometerBluetooth.BAROMETRIC_PRESSURE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{(byte) 0xB2, (byte) 0x48, (byte) 0x0F, (byte) 0x00});
|
||||
|
||||
// when
|
||||
AtmosphericPressure pressure = BluetoothHandlerBarometricPressure.parseEnvironmentalSensing(characteristic);
|
||||
AtmosphericPressure pressure = BarometerBluetooth.parseEnvironmentalSensing(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(AtmosphericPressure.ofHPA(1001.65f), pressure);
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -13,11 +13,11 @@ public class BluetoothHandlerHeartRateTest {
|
||||
@Test
|
||||
public void parseHeartRate_uint8() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(HeartRateBluetooth.HEARTRATE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||
|
||||
// when
|
||||
HeartRate heartRate = BluetoothHandlerHeartRate.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = HeartRateBluetooth.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(HeartRate.of(60), heartRate);
|
||||
@@ -26,11 +26,11 @@ public class BluetoothHandlerHeartRateTest {
|
||||
@Test
|
||||
public void parseHeartRate_uint16() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(HeartRateBluetooth.HEARTRATE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||
|
||||
// when
|
||||
HeartRate heartRate = BluetoothHandlerHeartRate.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = HeartRateBluetooth.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(HeartRate.of(257), heartRate);
|
||||
+8
-8
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -8,14 +8,14 @@ import android.util.Pair;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BluetoothHandlerCyclingDistanceSpeedTest {
|
||||
public class CyclingDistanceSpeedBluetoothTest {
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||
|
||||
// when
|
||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||
|
||||
// then
|
||||
assertNull(sensor.first);
|
||||
@@ -24,11 +24,11 @@ public class BluetoothHandlerCyclingDistanceSpeedTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(65535 + 16777216, sensor.first.wheelRevolutionsCount());
|
||||
@@ -37,11 +37,11 @@ public class BluetoothHandlerCyclingDistanceSpeedTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankWheel() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(200 + 16777216, sensor.first.wheelRevolutionsCount());
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -6,15 +6,15 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BluetoothHandlerCyclingPowerTest {
|
||||
public class CyclingPowerBluetoothTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingPowerBluetooth.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
||||
|
||||
// when
|
||||
BluetoothHandlerCyclingPower.Data powerCadence = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
||||
CyclingPowerBluetooth.Data powerCadence = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(40, powerCadence.power().getW(), 0.01);
|
||||
@@ -22,11 +22,11 @@ public class BluetoothHandlerCyclingPowerTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power_with_cadence() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingPowerBluetooth.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
|
||||
|
||||
// when
|
||||
BluetoothHandlerCyclingPower.Data powerCadence = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
||||
CyclingPowerBluetooth.Data powerCadence = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(0, powerCadence.power().getW(), 0.01);
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -10,15 +10,15 @@ import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
|
||||
public class BluetoothHandlerRunningSpeedAndCadenceTest {
|
||||
public class RunningSpeedAndCadenceBluetoothTest {
|
||||
|
||||
@Test
|
||||
public void parseRunningSpeedAndCadence_with_distance() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(RunningSpeedAndCadenceBluetooth.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
||||
|
||||
// when
|
||||
BluetoothHandlerRunningSpeedAndCadence.Data sensor = BluetoothHandlerRunningSpeedAndCadence.parseRunningSpeedAndCadence("sensorName", characteristic);
|
||||
RunningSpeedAndCadenceBluetooth.Data sensor = RunningSpeedAndCadenceBluetooth.parseRunningSpeedAndCadence("sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(Speed.of(5), sensor.speed());
|
||||
+3
-3
@@ -11,7 +11,7 @@ public class AggregatorBarometerTest {
|
||||
|
||||
private static void addSensorValue(AggregatorBarometer aggregatorBarometer, float[] values) {
|
||||
for (float f : values) {
|
||||
aggregatorBarometer.add(new Raw<>(Instant.MIN, AtmosphericPressure.ofHPA(f)));
|
||||
aggregatorBarometer.add(Instant.MIN, AtmosphericPressure.ofHPA(f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class AggregatorBarometerTest {
|
||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f});
|
||||
|
||||
// then
|
||||
Assert.assertEquals(0f, subject.aggregatedValue.gain_m(), 0.01);
|
||||
Assert.assertEquals(15f, subject.aggregatedValue.loss_m(), 0.01);
|
||||
Assert.assertEquals(0f, subject.output.gain_m(), 0.01);
|
||||
Assert.assertEquals(15f, subject.output.loss_m(), 0.01);
|
||||
}
|
||||
}
|
||||
+21
-22
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -12,10 +13,9 @@ import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SensorDataCyclingTest {
|
||||
@@ -25,8 +25,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 2048)));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 2048));
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
@@ -37,8 +37,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 6184)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 8016)));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 6184));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 8016));
|
||||
|
||||
// then
|
||||
assertEquals(33.53, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
@@ -49,8 +49,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 2048)));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 2048));
|
||||
|
||||
// then
|
||||
assertEquals(Cadence.of(0), current.getAggregatedValue(Instant.MIN));
|
||||
@@ -62,8 +62,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 1024)));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 1024));
|
||||
|
||||
// then
|
||||
assertFalse(current.hasReceivedData()); //TODO Cadence should be 0?
|
||||
@@ -74,8 +74,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, UintUtils.UINT16_MAX - 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 0)));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, UintUtils.UINT16_MAX - 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 0));
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
@@ -87,13 +87,13 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(UintUtils.UINT32_MAX - 1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(0, 2048)));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(UintUtils.UINT32_MAX - 1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(0, 2048));
|
||||
|
||||
// then
|
||||
// TODO See #953
|
||||
// assertEquals(60, current.getValue().getRPM(), 0.01);
|
||||
assertEquals(Cadence.of(0), current.getAggregatedValue(Instant.MIN));
|
||||
assertNull(current.getAggregatedValue(Instant.MIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -102,8 +102,8 @@ public class SensorDataCyclingTest {
|
||||
current.setWheelCircumference(Distance.ofMM(2150));
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(1, 6184)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(2, 8016)));
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(1, 6184));
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(2, 8016));
|
||||
|
||||
// then
|
||||
assertEquals(2.15, current.getAggregatedValue(Instant.MIN).distance().toM(), 0.01);
|
||||
@@ -117,14 +117,13 @@ public class SensorDataCyclingTest {
|
||||
current.setWheelCircumference(Distance.ofMM(2000));
|
||||
|
||||
// when
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(UintUtils.UINT32_MAX - 1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(0, 2048)));
|
||||
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(UintUtils.UINT32_MAX - 1, 1024));
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(0, 2048));
|
||||
|
||||
// then
|
||||
// TODO See #953
|
||||
// assertEquals(2, current.getValue().getDistance().toM(), 0.01);
|
||||
// assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
|
||||
assertEquals(new AggregatorCyclingDistanceSpeed.Data(Distance.ZERO, Distance.ZERO, Speed.ZERO), current.getAggregatedValue(Instant.MIN));
|
||||
assertNull(current.getAggregatedValue(Instant.MIN));
|
||||
}
|
||||
}
|
||||
+30
-17
@@ -6,7 +6,6 @@ import static org.junit.Assert.assertFalse;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -44,12 +43,12 @@ 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.io.file.importer.TrackPointAssert;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@@ -154,7 +153,8 @@ public class TrackRecordingServiceRecordingTest {
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
//We do not want the real GPS to interfere.
|
||||
LocationManagerCompat.removeUpdates((LocationManager) context.getSystemService(Context.LOCATION_SERVICE), trackPointCreator.getSensorManager().getGpsManager());
|
||||
GpsInternal gpsInternal = (GpsInternal)trackPointCreator.getSensorManager().getGpsHandler().driver;
|
||||
LocationManagerCompat.removeUpdates(gpsInternal.locationManager, gpsInternal.locationListenerCompat);
|
||||
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
|
||||
@@ -339,16 +339,21 @@ public class TrackRecordingServiceRecordingTest {
|
||||
mockAltitudeChange(trackPointCreator, 0);
|
||||
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
sensorManager.sensorDataAggregator.add(new AggregatorHeartRate("", ""));
|
||||
// when
|
||||
String sensor1 = "2020-02-02T02:02:03Z";
|
||||
trackPointCreator.setClock(sensor1);
|
||||
|
||||
sensorManager.onChanged(new Raw<>(sensor1, HeartRate.of(5))); //Should be ignored
|
||||
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
|
||||
sensorManager.setAggregator(avgHeartRate);
|
||||
|
||||
//Should be ignored
|
||||
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(5));
|
||||
sensorManager.onChange();
|
||||
|
||||
String sensor3 = "2020-02-02T02:02:13Z";
|
||||
trackPointCreator.setClock(sensor3);
|
||||
sensorManager.onChanged(new Raw<>(sensor3, HeartRate.of(7)));
|
||||
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(7));
|
||||
sensorManager.onChange();
|
||||
|
||||
String stopTime = "2020-02-02T02:02:15Z";
|
||||
trackPointCreator.setClock(stopTime);
|
||||
@@ -808,19 +813,22 @@ public class TrackRecordingServiceRecordingTest {
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
AggregatorRunning aggregatorRunning = new AggregatorRunning("", "");
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
sensorManager.sensorDataAggregator.add(new AggregatorRunning("", ""));
|
||||
sensorManager.sensorDataAggregator.barometer = null;
|
||||
sensorManager.setAggregator(aggregatorRunning);
|
||||
|
||||
// when
|
||||
String sensor1 = "2020-02-02T02:02:03Z";
|
||||
trackPointCreator.setClock(sensor1);
|
||||
sensorManager.onChanged(new Raw<>(sensor1, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(1), Distance.ZERO))); //Should be ignored
|
||||
//Should be ignored
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(1), Distance.ZERO));
|
||||
sensorManager.onChange();
|
||||
|
||||
// when
|
||||
String sensor2 = "2020-02-02T02:02:04Z";
|
||||
trackPointCreator.setClock(sensor2);
|
||||
sensorManager.onChanged(new Raw<>(sensor2, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(2), Distance.of(2))));
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(2), Distance.of(2)));
|
||||
sensorManager.onChange();
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:05Z";
|
||||
@@ -829,12 +837,15 @@ public class TrackRecordingServiceRecordingTest {
|
||||
// when
|
||||
String sensor3 = "2020-02-02T02:02:06Z";
|
||||
trackPointCreator.setClock(sensor3);
|
||||
sensorManager.onChanged(new Raw<>(sensor3, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(3), Distance.of(12))));
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(3), Distance.of(12)));
|
||||
sensorManager.onChange();
|
||||
|
||||
// when
|
||||
String sensor4 = "2020-02-02T02:02:07Z";
|
||||
trackPointCreator.setClock(sensor4);
|
||||
sensorManager.onChanged(new Raw<>(sensor4, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(4), Distance.of(14)))); //Should be ignored
|
||||
//Should be ignored
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(4), Distance.of(14)));
|
||||
sensorManager.onChange();
|
||||
|
||||
// when
|
||||
String gps2 = "2020-02-02T02:02:08Z";
|
||||
@@ -843,7 +854,9 @@ public class TrackRecordingServiceRecordingTest {
|
||||
// when
|
||||
String sensor5 = "2020-02-02T02:02:10Z";
|
||||
trackPointCreator.setClock(sensor5);
|
||||
sensorManager.onChanged(new Raw<>(sensor5, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(5), Distance.of(16)))); //Should be ignored
|
||||
//Should be ignored
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(5), Distance.of(16)));
|
||||
sensorManager.onChange();
|
||||
|
||||
// when
|
||||
String gps3 = "2020-02-02T02:02:12Z";
|
||||
@@ -962,7 +975,7 @@ public class TrackRecordingServiceRecordingTest {
|
||||
}
|
||||
|
||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
||||
trackPointCreator.getSensorManager().sensorDataAggregator.barometer = new AggregatorBarometer("", "") {
|
||||
trackPointCreator.getSensorManager().getAltitudeChangeHandler().setAggregator(new AggregatorBarometer("", "") {
|
||||
@Override
|
||||
public boolean hasReceivedData() {
|
||||
return true;
|
||||
@@ -973,7 +986,7 @@ public class TrackRecordingServiceRecordingTest {
|
||||
public AltitudeGainLoss getAggregatedValue(Instant now) {
|
||||
return new AltitudeGainLoss(altitudeGain, altitudeGain);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
|
||||
@@ -985,6 +998,6 @@ public class TrackRecordingServiceRecordingTest {
|
||||
location.setSpeed(speed);
|
||||
|
||||
trackPointCreator.setClock(time);
|
||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
||||
trackPointCreator.getSensorManager().getGpsHandler().onDataReceived(location);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()); TODO BUG: GPS can only be stopped if a service is recording.
|
||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -69,9 +69,11 @@ public class ContentProviderUtils {
|
||||
|
||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||
|
||||
//TODO Move to CustomContentProvider
|
||||
// The authority (the first part of the URI) for the app's content provider.
|
||||
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
||||
|
||||
//TODO Move to CustomContentProvider
|
||||
// The base URI for the app's content provider.
|
||||
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ 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.publicapi.DataProvider;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -94,7 +95,6 @@ public class CustomContentProvider extends ContentProvider {
|
||||
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
||||
|
||||
public CustomContentProvider() {
|
||||
//TODO Add tests for used URIs (incl. Dashboard API)
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
||||
@@ -108,6 +108,10 @@ public class CustomContentProvider extends ContentProvider {
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath() + "/#", UrlType.MARKERS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.MARKERS_BY_TRACKID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKS_BY_IDS.getPath() + "*", UrlType.DASHBOARD_TRACKS_BY_IDS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,12 +174,21 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri url) {
|
||||
String contentType = getUrlType(url).contentType;
|
||||
if (contentType != null) {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
//TODO replace with getUrlType(url).getContentType()
|
||||
return switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
|
||||
case TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
|
||||
case TRACKS -> TracksColumns.CONTENT_TYPE;
|
||||
case MARKERS -> MarkerColumns.CONTENT_TYPE;
|
||||
case MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
|
||||
case DASHBOARD_TRACKS_BY_IDS -> UrlType.DASHBOARD_TRACKS_BY_IDS.contentType;
|
||||
case DASHBOARD_TRACKPOINTS_BY_TRACKIDS -> UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.contentType;
|
||||
case DASHBOARD_MARKERS_BY_TRACKIDS -> UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.contentType;
|
||||
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||
};
|
||||
}
|
||||
@@ -225,6 +238,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
String sortOrder = null;
|
||||
String groupBy = null;
|
||||
//TODO queryBuilder.setStrict(true);
|
||||
//TODO queryBuilder.appendWhere() should use selectionArgs?
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> {
|
||||
@@ -239,6 +253,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case DASHBOARD_TRACKPOINTS_BY_TRACKIDS -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_TRACKPOINTS);
|
||||
}
|
||||
case TRACKS -> {
|
||||
if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)");
|
||||
@@ -251,6 +270,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case DASHBOARD_TRACKS_BY_IDS -> {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_TRACKS);
|
||||
}
|
||||
case TRACKS_SENSOR_STATS -> {
|
||||
//TODO Replace with SQLQueryBuilder?
|
||||
long trackId = ContentUris.parseId(url);
|
||||
@@ -281,6 +305,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case DASHBOARD_MARKERS_BY_TRACKIDS -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_MARKERS);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
|
||||
@@ -372,9 +401,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
throw new SQLException("Failed to insert a marker " + url);
|
||||
}
|
||||
|
||||
private enum UrlType {
|
||||
//TODO Add ContentType as attribute
|
||||
public enum UrlType {
|
||||
TRACKPOINTS,
|
||||
TRACKPOINTS_BY_ID,
|
||||
//TODO Check if still used
|
||||
TRACKPOINTS_BY_TRACKID,
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
@@ -382,6 +413,36 @@ public class CustomContentProvider extends ContentProvider {
|
||||
TRACKS_AGGREGATED_STATISTICS,
|
||||
MARKERS,
|
||||
MARKERS_BY_ID,
|
||||
MARKERS_BY_TRACKID
|
||||
@Deprecated //TODO Should not be used anymore
|
||||
MARKERS_BY_TRACKID,
|
||||
|
||||
DASHBOARD_TRACKS_BY_IDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/"), TracksColumns.CONTENT_TYPE),
|
||||
DASHBOARD_TRACKPOINTS_BY_TRACKIDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/"), TrackPointsColumns.CONTENT_TYPE),
|
||||
DASHBOARD_MARKERS_BY_TRACKIDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/"), MarkerColumns.CONTENT_TYPE);
|
||||
|
||||
private final Uri uri;
|
||||
|
||||
private final String contentType;
|
||||
|
||||
//TODO Add Uri as attribute:
|
||||
//TODO Add ContentType as attribute:
|
||||
@Deprecated()
|
||||
UrlType() {
|
||||
this.uri = null;
|
||||
this.contentType = null;
|
||||
}
|
||||
|
||||
UrlType(Uri uri, String contentType) {
|
||||
this.uri = uri;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return uri.getPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
|
||||
import java.time.Instant;
|
||||
|
||||
public record Position(
|
||||
@Nullable Instant time,
|
||||
@Nullable Instant time, //TODO It may be a good idea to move time out of Position
|
||||
@Nullable Double latitude,
|
||||
@Nullable Double longitude,
|
||||
@Nullable Distance horizontalAccuracy,
|
||||
@@ -17,10 +17,6 @@ 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(
|
||||
|
||||
@@ -167,8 +167,11 @@ public class ExportActivity extends AbstractActivity {
|
||||
new Thread(() -> {
|
||||
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
|
||||
runOnUiThread(() -> {
|
||||
createExportTasks(allInOneFile);
|
||||
nextExport(null);
|
||||
if (createExportTasks(allInOneFile)) {
|
||||
nextExport(null);
|
||||
} else {
|
||||
onExportEnded();
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
} else {
|
||||
@@ -217,8 +220,12 @@ public class ExportActivity extends AbstractActivity {
|
||||
exportTasks.clear();
|
||||
}
|
||||
|
||||
private void createExportTasks(boolean allInOneFile) {
|
||||
private boolean createExportTasks(boolean allInOneFile) {
|
||||
List<Track> tracks = contentProviderUtils.getTracks();
|
||||
if (tracks.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
exportTasks = new ArrayList<>();
|
||||
if (allInOneFile) {
|
||||
String filename = "OpenTracks-Backup";
|
||||
@@ -227,6 +234,7 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,7 +399,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.
|
||||
*/
|
||||
|
||||
@@ -11,9 +11,11 @@ import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
@@ -33,7 +35,7 @@ public class DataProvider {
|
||||
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
||||
|
||||
/**
|
||||
* Assume "v1" if not present.
|
||||
* Assume "1" if not present.
|
||||
*/
|
||||
private static final String EXTRAS_PROTOCOL_VERSION = "PROTOCOL_VERSION";
|
||||
|
||||
@@ -42,7 +44,7 @@ public class DataProvider {
|
||||
* version 2: replaced pause/resume trackpoints for track segmentation (lat=100 / lat=200) by TrackPoint.Type.
|
||||
* version 3: renamed track database columns.
|
||||
*/
|
||||
private static final int CURRENT_VERSION = 3;
|
||||
private static final int CURRENT_VERSION = 2;
|
||||
|
||||
private static final String EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK = "EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK";
|
||||
private static final String EXTRAS_SHOULD_KEEP_SCREEN_ON = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||
@@ -52,7 +54,78 @@ public class DataProvider {
|
||||
private static final int TRACK_URI_INDEX = 0;
|
||||
private static final int TRACKPOINTS_URI_INDEX = 1;
|
||||
private static final int MARKERS_URI_INDEX = 2;
|
||||
private static final int NONE_SELECTED = -1;
|
||||
|
||||
public static final Map<String, String> DATA_PROJECTIONMAP_TRACKS = Map.ofEntries(
|
||||
//V2
|
||||
Map.entry("_id", TracksColumns._ID),
|
||||
Map.entry("name", TracksColumns.NAME),
|
||||
Map.entry("description", TracksColumns.DESCRIPTION),
|
||||
Map.entry("category", TracksColumns.ACTIVITY_TYPE + " AS category"),
|
||||
Map.entry("activity_type_localized", TracksColumns.ACTIVITY_TYPE_LOCALIZED),
|
||||
Map.entry("starttime", TracksColumns.STARTTIME + " AS starttime"),
|
||||
Map.entry("stoptime", TracksColumns.STOPTIME + " AS stoptime"),
|
||||
Map.entry("totaltime", TracksColumns.TOTALTIME + " AS totaltime"),
|
||||
Map.entry("movingtime", TracksColumns.MOVINGTIME + " AS movingtime"),
|
||||
Map.entry("totaldistance", TracksColumns.TOTALDISTANCE + " AS totaldistance"),
|
||||
Map.entry("maxspeed", TracksColumns.MAXSPEED + " AS maxspeed"),
|
||||
Map.entry("avgspeed", "null AS avgspeed"),
|
||||
Map.entry("avgmovingspeed", "null AS avgmovingspeed"),
|
||||
Map.entry("minelevation", TracksColumns.MIN_ALTITUDE + " AS minelevation"),
|
||||
Map.entry("maxelevation", TracksColumns.MAX_ALTITUDE + " AS maxelevation"),
|
||||
Map.entry("elevationgain", TracksColumns.ALTITUDE_GAIN + " AS elevationgain"),
|
||||
Map.entry("elevationloss", TracksColumns.ALTITUDE_LOSS + " AS elevationloss"),
|
||||
|
||||
//V3
|
||||
// Map.entry("_id", TracksColumns._ID),
|
||||
// Map.entry("name",TracksColumns.NAME),
|
||||
// Map.entry("description",TracksColumns.DESCRIPTION),
|
||||
Map.entry("activity_type", TracksColumns.ACTIVITY_TYPE),
|
||||
// Map.entry("activity_type_localized",TracksColumns.ACTIVITY_TYPE_LOCALIZED),
|
||||
Map.entry("time_start", TracksColumns.STARTTIME),
|
||||
Map.entry("time_stop", TracksColumns.STOPTIME),
|
||||
Map.entry("duration_total", TracksColumns.TOTALTIME),
|
||||
Map.entry("duration_moving", TracksColumns.MOVINGTIME),
|
||||
Map.entry("distance", TracksColumns.TOTALDISTANCE),
|
||||
Map.entry("speed_max", TracksColumns.MAXSPEED),
|
||||
Map.entry("altitude_min", TracksColumns.MIN_ALTITUDE),
|
||||
Map.entry("altitude_max", TracksColumns.MAX_ALTITUDE),
|
||||
Map.entry("altitude_gain", TracksColumns.ALTITUDE_GAIN),
|
||||
Map.entry("altitude_loss", TracksColumns.ALTITUDE_LOSS)
|
||||
);
|
||||
|
||||
public static final Map<String, String> DATA_PROJECTIONMAP_TRACKPOINTS = Map.ofEntries(
|
||||
//V2 and V3
|
||||
Map.entry("_id", TrackPointsColumns._ID),
|
||||
Map.entry("trackid", TrackPointsColumns.TRACKID),
|
||||
Map.entry("latitude", TrackPointsColumns.LATITUDE),
|
||||
Map.entry("longitude", TrackPointsColumns.LONGITUDE),
|
||||
Map.entry("time", TrackPointsColumns.TIME),
|
||||
Map.entry("type", TrackPointsColumns.TYPE),
|
||||
Map.entry("speed", TrackPointsColumns.SPEED)
|
||||
);
|
||||
public static final Map<String, String> DATA_PROJECTIONMAP_MARKERS = Map.ofEntries(
|
||||
//V2
|
||||
Map.entry("_id", MarkerColumns._ID),
|
||||
Map.entry("trackid", MarkerColumns.TRACKID),
|
||||
Map.entry("name", MarkerColumns.NAME),
|
||||
Map.entry("description", MarkerColumns.DESCRIPTION),
|
||||
Map.entry("category", MarkerColumns.TYPE_LOCALIZED + " AS category"),
|
||||
Map.entry("latitude", MarkerColumns.LATITUDE),
|
||||
Map.entry("longitude", MarkerColumns.LONGITUDE),
|
||||
Map.entry("photoUrl", MarkerColumns.PHOTOURL),
|
||||
Map.entry("icon", "null AS icon"),
|
||||
|
||||
//V3
|
||||
// Map.entry("_id", MarkerColumns._ID),
|
||||
// Map.entry("trackid",MarkerColumns.TRACKID),
|
||||
// Map.entry("name",MarkerColumns.NAME),
|
||||
// Map.entry("description",MarkerColumns.DESCRIPTION),
|
||||
Map.entry("marker_type_localized", MarkerColumns.TYPE_LOCALIZED)
|
||||
// Map.entry("latitude",MarkerColumns.LATITUDE),
|
||||
// Map.entry("longitude",MarkerColumns.LONGITUDE),
|
||||
// Map.entry("photoUrl",MarkerColumns.PHOTOURL)
|
||||
);
|
||||
|
||||
|
||||
private DataProvider() {
|
||||
}
|
||||
@@ -60,9 +133,9 @@ public class DataProvider {
|
||||
/**
|
||||
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||
*
|
||||
* @param context the context
|
||||
* @param context the context
|
||||
* @param isRecording are we currently recording?
|
||||
* @param trackIds the track ids
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static void showTrackOnMap(Context context, boolean isRecording, Track.Id... trackIds) {
|
||||
startDashboard(context, isRecording, null, null, trackIds);
|
||||
@@ -73,11 +146,11 @@ public class DataProvider {
|
||||
* By providing a targetPackage and targetClass an explicit intent can be sent,
|
||||
* thus bypassing the need for the user to select an app.
|
||||
*
|
||||
* @param context the context
|
||||
* @param isRecording are we currently recording?
|
||||
* @param context the context
|
||||
* @param isRecording are we currently recording?
|
||||
* @param targetPackage the target package
|
||||
* @param targetClass the target class
|
||||
* @param trackIds the track ids
|
||||
* @param targetClass the target class
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static void startDashboard(Context context, boolean isRecording, @Nullable String targetPackage, @Nullable String targetClass, Track.Id... trackIds) {
|
||||
if (trackIds.length == 0) {
|
||||
@@ -87,9 +160,9 @@ public class DataProvider {
|
||||
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
||||
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
uris.add(MARKERS_URI_INDEX, Uri.withAppendedPath(MarkerColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_TRACKS_BY_IDS.getUri(), trackIdList));
|
||||
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.getUri(), trackIdList));
|
||||
uris.add(MARKERS_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.getUri(), trackIdList));
|
||||
|
||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||
intent.putExtra(EXTRAS_PROTOCOL_VERSION, CURRENT_VERSION);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
||||
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
|
||||
/**
|
||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||
*/
|
||||
//TODO Rename class
|
||||
public class AltitudeChangeHandler extends SensorHandler<AtmosphericPressure, AltitudeGainLoss> {
|
||||
|
||||
public AltitudeChangeHandler(SensorManager sensorManager) {
|
||||
super(sensorManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverInternal() {
|
||||
return new BarometerInternal(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
return new BluetoothDriver<>(
|
||||
BluetoothUtils.getAdapter(getContext()),
|
||||
this,
|
||||
new BarometerBluetooth()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AggregatorBarometer createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorBarometer(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_pressure_key;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
|
||||
public class BluetoothHandlerCyclingCadence implements SensorHandlerInterface {
|
||||
private static final String TAG = BluetoothHandlerCyclingCadence.class.getSimpleName();
|
||||
|
||||
public static final List<ServiceMeasurementUUID> CYCLING_CADENCE = List.of(
|
||||
BluetoothHandlerCyclingPower.CYCLING_POWER,
|
||||
BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<ServiceMeasurementUUID> getServices() {
|
||||
return CYCLING_CADENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorCyclingCadence createEmptySensorData(String address, String name) {
|
||||
return new AggregatorCyclingCadence(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
if (serviceMeasurementUUID.equals(BluetoothHandlerCyclingPower.CYCLING_POWER)) {
|
||||
BluetoothHandlerCyclingPower.Data data = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
||||
if (data != null && data.crank() != null) {
|
||||
observer.onChange(new Raw<>(observer.getNow(), data.crank()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceMeasurementUUID.equals(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE)) {
|
||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, CrankData> data = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
||||
|
||||
if (data != null && data.second != null) {
|
||||
observer.onChange(new Raw<>(observer.getNow(), data.second));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.e(TAG, "Don't know how to decode this payload.");
|
||||
}
|
||||
|
||||
public record CrankData(
|
||||
long crankRevolutionsCount, // UINT32
|
||||
int crankRevolutionsTime // UINT16; 1/1024s
|
||||
) {}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import static de.dennisguse.opentracks.sensors.BluetoothHandlerBarometricPressure.ENVIRONMENTAL_SENSING_SERVICE;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Temperature;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
|
||||
public class BluetoothHandlerTemperature implements SensorHandlerInterface {
|
||||
public static final ServiceMeasurementUUID TEMPERATURE = new ServiceMeasurementUUID(
|
||||
ENVIRONMENTAL_SENSING_SERVICE,
|
||||
new UUID(0x2A6E00001000L, 0x800000805f9b34fbL)
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<ServiceMeasurementUUID> getServices() {
|
||||
return List.of(TEMPERATURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Aggregator<?, ?> createEmptySensorData(String address, String name) {
|
||||
return new AggregatorTemperature(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
Temperature value = parseEnvironmentalSensing(characteristic);
|
||||
if (value == null) return;
|
||||
|
||||
observer.onChange(new Raw<>(observer.getNow(), value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoding: org.bluetooth.characteristic.temperature.xml
|
||||
*/
|
||||
public static Temperature parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] raw = characteristic.getValue();
|
||||
|
||||
if (raw.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer pressure = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 0);
|
||||
return Temperature.ofCentiCelsius(pressure);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
||||
* <p>
|
||||
* Note: should only be instantiated once.
|
||||
* TODO: listen for Bluetooth enabled/disabled events.
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothRemoteSensorManager implements SensorConnector, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final Handler handler;
|
||||
private boolean started = false;
|
||||
|
||||
private final BluetoothConnectionManager heartRate;
|
||||
private final BluetoothConnectionManager temperature;
|
||||
private final BluetoothConnectionManager cyclingCadence;
|
||||
private final BluetoothConnectionManager cyclingSpeed;
|
||||
private final BluetoothConnectionManager cyclingPower;
|
||||
private final BluetoothConnectionManager runningSpeedAndCadence;
|
||||
|
||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @Nullable SensorManager.SensorDataChangedObserver observer) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||
this.heartRate = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerHeartRate());
|
||||
this.temperature = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerTemperature());
|
||||
this.cyclingCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingCadence());
|
||||
this.cyclingSpeed = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingDistanceSpeed());
|
||||
this.cyclingPower = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingPower());
|
||||
this.runningSpeedAndCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerRunningSpeedAndCadence());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Context context, Handler handler) {
|
||||
started = true;
|
||||
|
||||
// Triggers connection startup
|
||||
onSharedPreferenceChanged(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stop(Context context) {
|
||||
heartRate.disconnect();
|
||||
temperature.disconnect();
|
||||
cyclingCadence.disconnect();
|
||||
cyclingSpeed.disconnect();
|
||||
cyclingPower.disconnect();
|
||||
runningSpeedAndCadence.disconnect();
|
||||
|
||||
started = false;
|
||||
}
|
||||
|
||||
private synchronized void connect(BluetoothConnectionManager connectionManager, String address) {
|
||||
connectionManager.connect(context, handler, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences unused, @Nullable String key) {
|
||||
if (!started) return;
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress();
|
||||
connect(heartRate, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_temperature_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothTemperatureSensorAddress();
|
||||
connect(temperature, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress();
|
||||
connect(cyclingCadence, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress();
|
||||
|
||||
connect(cyclingSpeed, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress();
|
||||
|
||||
connect(cyclingPower, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress();
|
||||
|
||||
connect(runningSpeedAndCadence, address);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||
|
||||
public class CyclingCadenceHandler extends SensorHandler<CyclingCadenceBluetooth.CrankData, Cadence> {
|
||||
|
||||
protected CyclingCadenceHandler(SensorManager sensorManager) {
|
||||
super(sensorManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
return new BluetoothDriver<>(
|
||||
BluetoothUtils.getAdapter(getContext()),
|
||||
this,
|
||||
new CyclingCadenceBluetooth()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AggregatorCyclingCadence createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorCyclingCadence(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_cycling_cadence_key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class CyclingDistanceSpeedHandler extends SensorHandler<CyclingDistanceSpeedBluetooth.WheelData, AggregatorCyclingDistanceSpeed.Data> {
|
||||
|
||||
protected CyclingDistanceSpeedHandler(SensorManager sensorManager) {
|
||||
super(sensorManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
return new BluetoothDriver<>(
|
||||
BluetoothUtils.getAdapter(getContext()),
|
||||
this,
|
||||
new CyclingDistanceSpeedBluetooth()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AggregatorCyclingDistanceSpeed createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorCyclingDistanceSpeed(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_cycling_speed_key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataReceived(CyclingDistanceSpeedBluetooth.WheelData value) {
|
||||
//TODO Fetch once and then listen for changes.
|
||||
((AggregatorCyclingDistanceSpeed) aggregator).setWheelCircumference(PreferencesUtils.getWheelCircumference());
|
||||
|
||||
super.onDataReceived(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||
|
||||
public class CyclingPowerHandler extends SensorHandler<CyclingPowerBluetooth.Data, Power> {
|
||||
|
||||
protected CyclingPowerHandler(SensorManager sensorManager) {
|
||||
super(sensorManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
return new BluetoothDriver<>(
|
||||
BluetoothUtils.getAdapter(getContext()),
|
||||
this,
|
||||
new CyclingPowerBluetooth()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AggregatorCyclingPower createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorCyclingPower(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_cycling_power_key;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||
*/
|
||||
public class GainManager implements SensorConnector {
|
||||
|
||||
private static final String TAG = GainManager.class.getSimpleName();
|
||||
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver listener;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> connect();
|
||||
|
||||
private Context context;
|
||||
private Handler handler;
|
||||
private Driver driver;
|
||||
|
||||
public GainManager(SensorManager.SensorDataChangedObserver listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
Log.d(TAG, "Stop");
|
||||
this.context = null;
|
||||
this.handler = null;
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
onDisconnect();
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
onDisconnect();
|
||||
|
||||
String address = PreferencesUtils.getBarometerSensorAddress();
|
||||
switch (PreferencesUtils.getSensorType(address)) {
|
||||
case NONE -> {
|
||||
driver = null;
|
||||
listener.onRemove(new AggregatorBarometer(null, null));
|
||||
return;
|
||||
}
|
||||
case INTERNAL -> driver = new BarometerInternal(listener);
|
||||
case REMOTE -> driver =
|
||||
new BluetoothConnectionManager(
|
||||
BluetoothUtils.getAdapter(context),
|
||||
listener,
|
||||
new BluetoothHandlerBarometricPressure()
|
||||
);
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
driver.connect(context, handler, address);
|
||||
}
|
||||
|
||||
private void onDisconnect() {
|
||||
if (driver == null) return;
|
||||
|
||||
driver.disconnect();
|
||||
listener.onDisconnect(new AggregatorBarometer("GainManager", null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorGPS;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public class GpsHandler extends SensorHandler<Location, Position> {
|
||||
|
||||
private final String TAG = GpsHandler.class.getSimpleName();
|
||||
|
||||
//TODO Refactor to just pass information via SensorManager.
|
||||
private TrackPointCreator trackPointCreator;
|
||||
|
||||
private GpsStatusManager gpsStatusManager;
|
||||
private Distance thresholdHorizontalAccuracy;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
||||
if (gpsStatusManager != null) {
|
||||
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval();
|
||||
gpsStatusManager.onRecordingDistanceChanged(gpsMinDistance);
|
||||
}
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.recording_gps_accuracy_key, key)) {
|
||||
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.min_sampling_interval_key, key)) {
|
||||
if (gpsStatusManager != null) {
|
||||
gpsStatusManager.onMinSamplingIntervalChanged(PreferencesUtils.getMinSamplingInterval());
|
||||
}
|
||||
|
||||
//TODO
|
||||
connect();
|
||||
}
|
||||
};
|
||||
|
||||
public GpsHandler(SensorManager sensorManager, TrackPointCreator trackPointCreator) {
|
||||
super(sensorManager);
|
||||
this.trackPointCreator = trackPointCreator;
|
||||
gpsStatusManager = new GpsStatusManager(sensorManager.getContext(), trackPointCreator::sendGpsStatus, sensorManager.getHandler());
|
||||
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(String sensorAddress, String sensorName) {
|
||||
super.onConnected(sensorAddress, sensorName);
|
||||
gpsStatusManager.onGpsEnabled();
|
||||
gpsStatusManager.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected() {
|
||||
super.onDisconnected();
|
||||
gpsStatusManager.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorDeactivated() {
|
||||
super.onSensorDeactivated();
|
||||
gpsStatusManager.onGpsDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataReceived(Location location) {
|
||||
// Send each update to the status; please note that this TrackPoint is not stored.
|
||||
Position position = Position.of(location, trackPointCreator.getNow());
|
||||
gpsStatusManager.onNewTrackPoint(position);
|
||||
|
||||
//TODO We could move the following check into the AggregatorGPS
|
||||
if (!position.hasValidLocation()) {
|
||||
Log.w(TAG, "Ignore newTrackPoint. Location is invalid.");
|
||||
return;
|
||||
}
|
||||
if (!position.fulfillsAccuracy(thresholdHorizontalAccuracy)) {
|
||||
Log.d(TAG, "Ignore newTrackPoint. Poor accuracy.");
|
||||
return;
|
||||
}
|
||||
|
||||
super.onDataReceived(location);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverInternal() {
|
||||
return new GpsInternal(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AggregatorGPS createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorGPS(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_gps_key;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSensorPreferenceDefaultValue() {
|
||||
return SensorType.INTERNAL.getPreferenceValue();
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.location.LocationListenerCompat;
|
||||
import androidx.core.location.LocationManagerCompat;
|
||||
import androidx.core.location.LocationRequestCompat;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorGPS;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public class GpsManager implements SensorConnector, LocationListenerCompat, GpsStatusManager.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private final String TAG = GpsManager.class.getSimpleName();
|
||||
|
||||
private static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||
|
||||
private TrackPointCreator trackPointCreator;
|
||||
|
||||
private SensorManager.SensorDataChangedObserver observer;
|
||||
private Context context;
|
||||
private Handler handler;
|
||||
|
||||
private LocationManager locationManager;
|
||||
private GpsStatusManager gpsStatusManager;
|
||||
private Duration gpsInterval;
|
||||
private Distance thresholdHorizontalAccuracy;
|
||||
|
||||
public GpsManager(TrackPointCreator trackPointCreator, SensorManager.SensorDataChangedObserver observer) {
|
||||
this.trackPointCreator = trackPointCreator;
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
public void start(@NonNull Context context, @NonNull Handler handler) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
|
||||
onSharedPreferenceChanged(null, null);
|
||||
|
||||
gpsStatusManager = new GpsStatusManager(context, this, handler);
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
|
||||
observer.onConnect(new AggregatorGPS("internal"));
|
||||
|
||||
registerLocationListener();
|
||||
gpsStatusManager.start();
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
return locationManager != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MissingPermission"})
|
||||
public void stop(Context context) {
|
||||
if (isStarted()) {
|
||||
LocationManagerCompat.removeUpdates(locationManager, this);
|
||||
}
|
||||
locationManager = null;
|
||||
this.context = null;
|
||||
handler = null;
|
||||
|
||||
gpsStatusManager.stop();
|
||||
gpsStatusManager = null;
|
||||
|
||||
observer.onDisconnect(new AggregatorGPS("internal"));
|
||||
observer = null;
|
||||
|
||||
trackPointCreator = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
boolean registerListener = false;
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.min_sampling_interval_key, key)) {
|
||||
registerListener = true;
|
||||
|
||||
gpsInterval = PreferencesUtils.getMinSamplingInterval();
|
||||
|
||||
if (gpsStatusManager != null) {
|
||||
gpsStatusManager.onMinSamplingIntervalChanged(gpsInterval);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(R.string.recording_gps_accuracy_key, key)) {
|
||||
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
|
||||
}
|
||||
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
||||
registerListener = true;
|
||||
|
||||
if (gpsStatusManager != null) {
|
||||
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval();
|
||||
gpsStatusManager.onRecordingDistanceChanged(gpsMinDistance);
|
||||
}
|
||||
}
|
||||
|
||||
if (registerListener && isStarted()) {
|
||||
registerLocationListener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if location is valid and builds a track point that will be send through TrackPointCreator.
|
||||
*
|
||||
* @param location {@link Location} object.
|
||||
*/
|
||||
@Override
|
||||
public void onLocationChanged(@NonNull Location location) {
|
||||
// Send each update to the status; please note that this TrackPoint is not stored.
|
||||
Position position = Position.of(location, trackPointCreator.createNow());
|
||||
gpsStatusManager.onNewTrackPoint(position);
|
||||
|
||||
if (!position.hasValidLocation()) {
|
||||
Log.w(TAG, "Ignore newTrackPoint. Location is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!position.fulfillsAccuracy(thresholdHorizontalAccuracy)) {
|
||||
Log.d(TAG, "Ignore newTrackPoint. Poor accuracy.");
|
||||
return;
|
||||
}
|
||||
|
||||
observer.onChange(new Raw<>(observer.getNow(), position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(@NonNull String provider, int status, Bundle extras) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(@NonNull String provider) {
|
||||
gpsStatusManager.onGpsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(@NonNull String provider) {
|
||||
gpsStatusManager.onGpsDisabled();
|
||||
}
|
||||
|
||||
private void registerLocationListener() {
|
||||
if (!LocationManagerCompat.hasProvider(locationManager, LOCATION_PROVIDER)) {
|
||||
Log.e(TAG, "Device doesn't have GPS.");
|
||||
return;
|
||||
}
|
||||
|
||||
LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(gpsInterval.toMillis())
|
||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||
.setMaxUpdateDelayMillis(0)
|
||||
.build();
|
||||
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
try {
|
||||
Log.i(TAG, "Register for location updates " + context);
|
||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, handler::post, this);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGpsStatusChanged(GpsStatusValue currentStatus) {
|
||||
trackPointCreator.sendGpsStatus(currentStatus);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class GpsStatusManager {
|
||||
private Duration signalLostThreshold;
|
||||
|
||||
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
|
||||
private GpsStatusListener client;
|
||||
private GpsStatusListener observer;
|
||||
private final Context context;
|
||||
|
||||
@Nullable
|
||||
@@ -44,8 +44,8 @@ class GpsStatusManager {
|
||||
};
|
||||
|
||||
|
||||
public GpsStatusManager(Context context, GpsStatusListener client, Handler handler) {
|
||||
this.client = client;
|
||||
public GpsStatusManager(Context context, GpsStatusListener observer, Handler handler) {
|
||||
this.observer = observer;
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
|
||||
@@ -54,7 +54,7 @@ class GpsStatusManager {
|
||||
}
|
||||
|
||||
public void start() {
|
||||
client.onGpsStatusChanged(GpsStatusValue.GPS_ENABLED);
|
||||
observer.onGpsStatusChanged(GpsStatusValue.GPS_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,9 +62,7 @@ class GpsStatusManager {
|
||||
*/
|
||||
public void stop() {
|
||||
stopTimer();
|
||||
client.onGpsStatusChanged(GpsStatusValue.GPS_NONE);
|
||||
client = null;
|
||||
handler = null;
|
||||
observer.onGpsStatusChanged(GpsStatusValue.GPS_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,8 +157,8 @@ class GpsStatusManager {
|
||||
|
||||
private void setGpsStatus(GpsStatusValue current) {
|
||||
gpsStatus = current;
|
||||
if (client != null) {
|
||||
client.onGpsStatusChanged(current);
|
||||
if (observer != null) {
|
||||
observer.onGpsStatusChanged(current);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.driver.HeartRateBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
|
||||
public class HeartRateHandler extends SensorHandler<HeartRate, HeartRate> {
|
||||
|
||||
protected HeartRateHandler(SensorManager sensorManager) {
|
||||
super(sensorManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
return new BluetoothDriver<>(
|
||||
BluetoothUtils.getAdapter(getContext()),
|
||||
this,
|
||||
new HeartRateBluetooth()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AggregatorHeartRate createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorHeartRate(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_heart_rate_key;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||
|
||||
public class RunningSpeedCadenceDistanceHandler extends SensorHandler<RunningSpeedAndCadenceBluetooth.Data, AggregatorRunning.Data> {
|
||||
|
||||
protected RunningSpeedCadenceDistanceHandler(SensorManager sensorManager) {
|
||||
super(sensorManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
return new BluetoothDriver<>(
|
||||
BluetoothUtils.getAdapter(getContext()),
|
||||
this,
|
||||
new RunningSpeedAndCadenceBluetooth()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AggregatorRunning createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorRunning("", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_running_speed_and_cadence_key;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
public interface SensorConnector {
|
||||
|
||||
void start(Context context, Handler handler);
|
||||
|
||||
void stop(Context context);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.driver.DriverObserver;
|
||||
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
|
||||
public abstract class SensorHandler<AggregatorInput, AggregatorOutput> implements DriverObserver<AggregatorInput> {
|
||||
|
||||
private static final String TAG = SensorHandler.class.getSimpleName();
|
||||
|
||||
private final SensorManager sensorManager;
|
||||
|
||||
@VisibleForTesting
|
||||
public Driver driver;
|
||||
|
||||
protected Aggregator<AggregatorInput, AggregatorOutput> aggregator;
|
||||
|
||||
protected SensorHandler(SensorManager sensorManager) {
|
||||
this.sensorManager = sensorManager;
|
||||
}
|
||||
|
||||
void connect() {
|
||||
disconnect();
|
||||
|
||||
String address = PreferencesUtils.getString(getSensorPreferenceKey(), getSensorPreferenceDefaultValue());
|
||||
|
||||
switch (PreferencesUtils.getSensorType(address)) {
|
||||
case NONE -> {
|
||||
onDisconnected();
|
||||
return;
|
||||
}
|
||||
case INTERNAL -> {
|
||||
driver = createDriverInternal();
|
||||
}
|
||||
case REMOTE -> {
|
||||
driver = createDriverBluetooth(address);
|
||||
}
|
||||
default -> throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
driver.connect(sensorManager.getContext(), sensorManager.getHandler(), address);
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
if (driver == null) return;
|
||||
|
||||
driver.disconnect();
|
||||
driver = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Driver createDriverInternal() {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract Aggregator<AggregatorInput, AggregatorOutput> createAggregator(String sensorAddress, String sensorName);
|
||||
|
||||
protected abstract int getSensorPreferenceKey();
|
||||
|
||||
protected String getSensorPreferenceDefaultValue() {
|
||||
return SensorType.NONE.getPreferenceValue();
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return sensorManager.getContext();
|
||||
}
|
||||
|
||||
void resetAggregated() {
|
||||
if (aggregator != null) {
|
||||
aggregator.resetAggregated();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SensorData<AggregatorOutput> getSensorData(Instant now) {
|
||||
if (aggregator == null) return null;
|
||||
if (!aggregator.hasReceivedData()) return null;
|
||||
|
||||
return aggregator.getAggregatedValueWithSensorName(now);
|
||||
}
|
||||
|
||||
//TODO REMOVE
|
||||
@VisibleForTesting
|
||||
public void setAggregator(Aggregator<AggregatorInput, AggregatorOutput> aggregator) {
|
||||
this.aggregator = aggregator;
|
||||
}
|
||||
|
||||
public void onConnected(String sensorAddress, String sensorName) {
|
||||
SensorHandler.this.aggregator = createAggregator(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionLost() {
|
||||
if (SensorHandler.this.aggregator == null) {
|
||||
Log.d(TAG, "Connection lost, but where not connected.");
|
||||
return;
|
||||
}
|
||||
|
||||
SensorHandler.this.aggregator.resetAggregated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataReceived(AggregatorInput value) {
|
||||
if (aggregator == null) {
|
||||
throw new RuntimeException("Received data while being disconnected.");
|
||||
}
|
||||
SensorHandler.this.aggregator.add(sensorManager.getNow(), value);
|
||||
sensorManager.onChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected() {
|
||||
SensorHandler.this.aggregator = null;
|
||||
}
|
||||
}
|
||||
@@ -6,153 +6,321 @@ import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
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.sensors.sensorData.AggregatorCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
public class SensorManager implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String TAG = SensorManager.class.getSimpleName();
|
||||
|
||||
//TODO Should be final and not be visible for testing
|
||||
@VisibleForTesting
|
||||
public SensorDataAggregator sensorDataAggregator;
|
||||
private Context context;
|
||||
|
||||
private Handler handler;
|
||||
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
|
||||
private final TrackPointCreator observer;
|
||||
|
||||
private final SensorDataChangedObserver listener = new SensorDataChangedObserver() {
|
||||
private HeartRateHandler heartRateHandler;
|
||||
private TemperatureHandler temperatureHandler;
|
||||
private CyclingCadenceHandler cyclingCadenceHandler;
|
||||
private CyclingDistanceSpeedHandler cyclingDistanceSpeedHandler;
|
||||
private CyclingPowerHandler cyclingPowerHandler;
|
||||
private RunningSpeedCadenceDistanceHandler runningSpeedCadenceDistanceHandler;
|
||||
|
||||
@Override
|
||||
public void onConnect(Aggregator<?, ?> aggregator) {
|
||||
sensorDataAggregator.add(aggregator);
|
||||
}
|
||||
private AltitudeChangeHandler altitudeChangeHandler;
|
||||
|
||||
@Override
|
||||
public void onChange(Raw<?> data) {
|
||||
sensorDataAggregator.update(data);
|
||||
observer.onChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(Aggregator<?, ?> aggregator) {
|
||||
sensorDataAggregator.add(aggregator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(Aggregator<?, ?> aggregator) {
|
||||
sensorDataAggregator.remove(aggregator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getNow() {
|
||||
return observer.createNow();
|
||||
}
|
||||
};
|
||||
|
||||
private BluetoothRemoteSensorManager bluetoothSensorManager;
|
||||
|
||||
private GainManager altitudeSumManager;
|
||||
|
||||
private GpsManager gpsManager;
|
||||
private GpsHandler gpsHandler;
|
||||
|
||||
public SensorManager(TrackPointCreator observer) {
|
||||
this.observer = observer;
|
||||
this.sensorDataAggregator = new SensorDataAggregator();
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
if (gpsManager != null) {
|
||||
if (isStarted()) {
|
||||
throw new RuntimeException("SensorManager cannot be started twice; stop first.");
|
||||
}
|
||||
|
||||
wakeLock = SystemUtils.acquireWakeLock(context, wakeLock);
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
|
||||
gpsManager = new GpsManager(observer, listener);
|
||||
altitudeSumManager = new GainManager(listener);
|
||||
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener);
|
||||
gpsHandler = new GpsHandler(this, observer);
|
||||
altitudeChangeHandler = new AltitudeChangeHandler(this);
|
||||
heartRateHandler = new HeartRateHandler(this);
|
||||
temperatureHandler = new TemperatureHandler(this);
|
||||
cyclingCadenceHandler = new CyclingCadenceHandler(this);
|
||||
cyclingDistanceSpeedHandler = new CyclingDistanceSpeedHandler(this);
|
||||
cyclingPowerHandler = new CyclingPowerHandler(this);
|
||||
runningSpeedCadenceDistanceHandler = new RunningSpeedCadenceDistanceHandler(this);
|
||||
|
||||
onSharedPreferenceChanged(null, null);
|
||||
|
||||
gpsManager.start(context, handler);
|
||||
altitudeSumManager.start(context, handler);
|
||||
bluetoothSensorManager.start(context, handler);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
public void stop() {
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
|
||||
bluetoothSensorManager.stop(context);
|
||||
bluetoothSensorManager = null;
|
||||
heartRateHandler.disconnect();
|
||||
heartRateHandler = null;
|
||||
|
||||
altitudeSumManager.stop(context);
|
||||
altitudeSumManager = null;
|
||||
temperatureHandler.disconnect();
|
||||
temperatureHandler = null;
|
||||
|
||||
gpsManager.stop(context);
|
||||
gpsManager = null;
|
||||
cyclingCadenceHandler.disconnect();
|
||||
cyclingCadenceHandler = null;
|
||||
|
||||
sensorDataAggregator.clear();
|
||||
}
|
||||
cyclingDistanceSpeedHandler.disconnect();
|
||||
cyclingDistanceSpeedHandler = null;
|
||||
|
||||
public SensorDataSet getSensorDataSet(Instant now) {
|
||||
return sensorDataAggregator.getSensorData(now);
|
||||
cyclingPowerHandler.disconnect();
|
||||
cyclingPowerHandler = null;
|
||||
|
||||
runningSpeedCadenceDistanceHandler.disconnect();
|
||||
runningSpeedCadenceDistanceHandler = null;
|
||||
|
||||
altitudeChangeHandler.disconnect();
|
||||
altitudeChangeHandler = null;
|
||||
|
||||
gpsHandler.disconnect();
|
||||
gpsHandler = null;
|
||||
|
||||
context = null;
|
||||
handler = null;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (bluetoothSensorManager == null || altitudeSumManager == null) {
|
||||
if (gpsHandler == null || altitudeChangeHandler == null) {
|
||||
Log.d(TAG, "No recording running and no reset necessary.");
|
||||
return;
|
||||
}
|
||||
sensorDataAggregator.reset();
|
||||
|
||||
Log.i(TAG, "Resetting data");
|
||||
|
||||
heartRateHandler.resetAggregated();
|
||||
temperatureHandler.resetAggregated();
|
||||
cyclingCadenceHandler.resetAggregated();
|
||||
cyclingDistanceSpeedHandler.resetAggregated();
|
||||
cyclingPowerHandler.resetAggregated();
|
||||
runningSpeedCadenceDistanceHandler.resetAggregated();
|
||||
altitudeChangeHandler.resetAggregated();
|
||||
gpsHandler.resetAggregated();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void onChanged(Raw<?> data) {
|
||||
listener.onChange(data);
|
||||
}
|
||||
|
||||
public GpsManager getGpsManager() {
|
||||
return gpsManager;
|
||||
public GpsHandler getGpsHandler() {
|
||||
return gpsHandler;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public GainManager getAltitudeSumManager() {
|
||||
return altitudeSumManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setAltitudeSumManager(GainManager altitudeSumManager) {
|
||||
this.altitudeSumManager = altitudeSumManager;
|
||||
public AltitudeChangeHandler getAltitudeChangeHandler() {
|
||||
return altitudeChangeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
|
||||
if (gpsManager != null) {
|
||||
gpsManager.onSharedPreferenceChanged(sharedPreferences, key);
|
||||
bluetoothSensorManager.onSharedPreferenceChanged(sharedPreferences, key);
|
||||
if (!isStarted()) return;
|
||||
|
||||
if (PreferencesUtils.isKey(gpsHandler.getSensorPreferenceKey(), key)) {
|
||||
gpsHandler.connect();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(altitudeChangeHandler.getSensorPreferenceKey(), key)) {
|
||||
altitudeChangeHandler.connect();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(heartRateHandler.getSensorPreferenceKey(), key)) {
|
||||
heartRateHandler.connect();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(temperatureHandler.getSensorPreferenceKey(), key)) {
|
||||
temperatureHandler.connect();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(cyclingCadenceHandler.getSensorPreferenceKey(), key)) {
|
||||
cyclingCadenceHandler.connect();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(cyclingDistanceSpeedHandler.getSensorPreferenceKey(), key)) {
|
||||
cyclingDistanceSpeedHandler.connect();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(cyclingCadenceHandler.getSensorPreferenceKey(), key)) {
|
||||
cyclingPowerHandler.connect();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(runningSpeedCadenceDistanceHandler.getSensorPreferenceKey(), key)) {
|
||||
runningSpeedCadenceDistanceHandler.connect();
|
||||
}
|
||||
}
|
||||
|
||||
public interface SensorDataChangedObserver {
|
||||
public Instant getNow() {
|
||||
return observer.getNow();
|
||||
}
|
||||
|
||||
void onConnect(Aggregator<?, ?> sensorData);
|
||||
void onChange(Raw<?> sensorData);
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
void onDisconnect(Aggregator<?, ?> sensorData);
|
||||
public Handler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
void onRemove(Aggregator<?, ?> sensorData);
|
||||
private SensorData<Cadence> getCadence(Instant now) {
|
||||
{
|
||||
SensorData<Cadence> value = cyclingCadenceHandler.getSensorData(now);
|
||||
if (value != null) {
|
||||
return cyclingCadenceHandler.getSensorData(now);
|
||||
}
|
||||
}
|
||||
|
||||
Instant getNow();
|
||||
SensorData<AggregatorRunning.Data> value = runningSpeedCadenceDistanceHandler.getSensorData(now);
|
||||
if (value != null && value.data().cadence() != null) {
|
||||
return new SensorData<>(value.data().cadence(), value.sensorNameOrAddress());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private SensorData<Distance> getDistance(Instant now) {
|
||||
{
|
||||
SensorData<AggregatorCyclingDistanceSpeed.Data> value = cyclingDistanceSpeedHandler.getSensorData(now);
|
||||
if (value != null) {
|
||||
return new SensorData<>(value.data().distanceOverall(), value.sensorNameOrAddress());
|
||||
}
|
||||
}
|
||||
|
||||
SensorData<AggregatorRunning.Data> value = runningSpeedCadenceDistanceHandler.getSensorData(now);
|
||||
if (value != null) {
|
||||
return new SensorData<>(value.data().distance(), value.sensorNameOrAddress());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//TOOD simplify?
|
||||
private SensorData<Speed> getSpeed(Instant now) {
|
||||
{
|
||||
SensorData<AggregatorCyclingDistanceSpeed.Data> value = cyclingDistanceSpeedHandler.getSensorData(now);
|
||||
if (value != null && value.data() != null && value.data().speed() != null) {
|
||||
return new SensorData<>(value.data().speed(), value.sensorNameOrAddress());
|
||||
}
|
||||
}
|
||||
|
||||
SensorData<AggregatorRunning.Data> value = runningSpeedCadenceDistanceHandler.getSensorData(now);
|
||||
if (value != null && value.data() != null && value.data().speed() != null) {
|
||||
return new SensorData<>(value.data().speed(), value.sensorNameOrAddress());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onChange() {
|
||||
observer.onChange();
|
||||
}
|
||||
|
||||
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), "");
|
||||
}
|
||||
|
||||
return new SensorDataSet(
|
||||
gpsSensorData,
|
||||
getSpeed(now),
|
||||
getDistance(now),
|
||||
heartRateHandler.getSensorData(now),
|
||||
temperatureHandler.getSensorData(now),
|
||||
getCadence(now),
|
||||
cyclingPowerHandler.getSensorData(now),
|
||||
altitudeChangeHandler.getSensorData(now)
|
||||
);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void clear() {
|
||||
Log.i(TAG, "Removing all aggregators");
|
||||
|
||||
//TODO This is to ExportImportTest alive; refactor test.
|
||||
if (this.altitudeChangeHandler != null) {
|
||||
this.altitudeChangeHandler.setAggregator(null);
|
||||
}
|
||||
if (this.heartRateHandler != null) {
|
||||
this.heartRateHandler.setAggregator(null);
|
||||
}
|
||||
if (this.temperatureHandler != null) {
|
||||
this.temperatureHandler.setAggregator(null);
|
||||
}
|
||||
if (this.cyclingCadenceHandler != null) {
|
||||
this.cyclingCadenceHandler.setAggregator(null);
|
||||
}
|
||||
if (cyclingDistanceSpeedHandler != null) {
|
||||
cyclingDistanceSpeedHandler.setAggregator(null);
|
||||
}
|
||||
if (cyclingPowerHandler != null) {
|
||||
cyclingPowerHandler.setAggregator(null);
|
||||
}
|
||||
if (runningSpeedCadenceDistanceHandler != null) {
|
||||
runningSpeedCadenceDistanceHandler.setAggregator(null);
|
||||
}
|
||||
if (this.gpsHandler != null) {
|
||||
this.gpsHandler.setAggregator(null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
return wakeLock != null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setAggregator(AggregatorHeartRate data) {
|
||||
heartRateHandler.setAggregator(data);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setAggregator(@NonNull AggregatorTemperature data) {
|
||||
temperatureHandler.setAggregator(data);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setAggregator(@NonNull AggregatorCyclingCadence data) {
|
||||
cyclingCadenceHandler.setAggregator(data);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setAggregator(@NonNull AggregatorCyclingDistanceSpeed data) {
|
||||
cyclingDistanceSpeedHandler.setAggregator(data);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setAggregator(@NonNull AggregatorCyclingPower data) {
|
||||
cyclingPowerHandler.setAggregator(data);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setAggregator(@NonNull AggregatorRunning data) {
|
||||
runningSpeedCadenceDistanceHandler.setAggregator(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Temperature;
|
||||
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.driver.TemperatureBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||
|
||||
public class TemperatureHandler extends SensorHandler<Temperature, Temperature> {
|
||||
|
||||
protected TemperatureHandler(SensorManager sensorManager) {
|
||||
super(sensorManager);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Driver createDriverBluetooth(String address) {
|
||||
return new BluetoothDriver<>(
|
||||
BluetoothUtils.getAdapter(getContext()),
|
||||
this,
|
||||
new TemperatureBluetooth()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AggregatorTemperature createAggregator(String sensorAddress, String sensorName) {
|
||||
return new AggregatorTemperature(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSensorPreferenceKey() {
|
||||
return R.string.settings_sensor_bluetooth_temperature_key;
|
||||
}
|
||||
}
|
||||
+5
-16
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
@@ -6,12 +6,9 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public class BluetoothHandlerBarometricPressure implements SensorHandlerInterface {
|
||||
public class BarometerBluetooth implements BluetoothDriver.BluetoothParser<AtmosphericPressure> {
|
||||
static final UUID ENVIRONMENTAL_SENSING_SERVICE = new UUID(0x181A00001000L, 0x800000805f9b34fbL);
|
||||
public static final ServiceMeasurementUUID BAROMETRIC_PRESSURE = new ServiceMeasurementUUID(
|
||||
ENVIRONMENTAL_SENSING_SERVICE,
|
||||
@@ -24,16 +21,8 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac
|
||||
}
|
||||
|
||||
@Override
|
||||
public Aggregator<?, ?> createEmptySensorData(String address, String name) {
|
||||
return new AggregatorBarometer(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
AtmosphericPressure value = parseEnvironmentalSensing(characteristic);
|
||||
if (value == null) return;
|
||||
|
||||
observer.onChange(new Raw<>(observer.getNow(), value));
|
||||
public AtmosphericPressure parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
return parseEnvironmentalSensing(characteristic);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -12,9 +12,6 @@ import androidx.annotation.NonNull;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
|
||||
public class BarometerInternal implements Driver {
|
||||
|
||||
@@ -22,9 +19,9 @@ public class BarometerInternal implements Driver {
|
||||
|
||||
private static final int SAMPLING_PERIOD = (int) TimeUnit.SECONDS.toMicros(5);
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver observer;
|
||||
private final DriverObserver<AtmosphericPressure> observer;
|
||||
|
||||
private Context context;
|
||||
private android.hardware.SensorManager sensorService;
|
||||
|
||||
private final SensorEventListener sensorEventListener = new SensorEventListener() {
|
||||
@Override
|
||||
@@ -34,7 +31,7 @@ public class BarometerInternal implements Driver {
|
||||
return;
|
||||
}
|
||||
|
||||
observer.onChange(new Raw<>(observer.getNow(), AtmosphericPressure.ofHPA(event.values[0])));
|
||||
observer.onDataReceived(AtmosphericPressure.ofHPA(event.values[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,40 +40,34 @@ public class BarometerInternal implements Driver {
|
||||
}
|
||||
};
|
||||
|
||||
public BarometerInternal(@NonNull SensorManager.SensorDataChangedObserver observer) {
|
||||
public BarometerInternal(@NonNull DriverObserver<AtmosphericPressure> observer) {
|
||||
this.observer = observer;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Context context, Handler handler, String addressIgnored) {
|
||||
sensorService = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
Sensor pressureSensor = sensorService.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
|
||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
if (pressureSensor == null) {
|
||||
Log.w(TAG, "No pressure sensor available.");
|
||||
if (pressureSensor != null && sensorService.registerListener(sensorEventListener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||
observer.onConnected(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sensorManager.registerListener(sensorEventListener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||
this.context = context;
|
||||
observer.onConnect(new AggregatorBarometer("internal", null));
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect();
|
||||
Log.w(TAG, "No pressure sensor available.");
|
||||
observer.onDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return context != null;
|
||||
return sensorService != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (!isConnected()) return;
|
||||
|
||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sensorManager.unregisterListener(sensorEventListener);
|
||||
this.context = null;
|
||||
sensorService.unregisterListener(sensorEventListener);
|
||||
sensorService = null;
|
||||
}
|
||||
}
|
||||
|
||||
+37
-49
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
@@ -31,23 +31,25 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
/**
|
||||
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
||||
*/
|
||||
//TODO Re-act to Bluetooth disabled/enabled
|
||||
@SuppressLint("MissingPermission")
|
||||
public class BluetoothConnectionManager implements Driver {
|
||||
public class BluetoothDriver<T> implements Driver {
|
||||
|
||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||
private static final String TAG = BluetoothDriver.class.getSimpleName();
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver observer;
|
||||
private final DriverObserver<T> observer;
|
||||
|
||||
private final SensorHandlerInterface sensorHandler;
|
||||
private final BluetoothParser<T> sensorHandler;
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
@@ -55,27 +57,29 @@ public class BluetoothConnectionManager implements Driver {
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
switch (newState) {
|
||||
case BluetoothProfile.STATE_CONNECTING ->
|
||||
Log.i(TAG, gatt.getDevice() + ": connecting to sensor");
|
||||
case BluetoothProfile.STATE_CONNECTING -> {
|
||||
Log.i(TAG, gatt.getDevice() + ": connecting to sensor");
|
||||
}
|
||||
case BluetoothProfile.STATE_CONNECTED -> {
|
||||
Log.i(TAG, gatt.getDevice() + ": connected to sensor; discovering services");
|
||||
gatt.discoverServices();
|
||||
}
|
||||
case BluetoothProfile.STATE_DISCONNECTING ->
|
||||
Log.i(TAG, gatt.getDevice() + ": disconnecting from sensor: ");
|
||||
case BluetoothProfile.STATE_DISCONNECTING -> Log.i(TAG, gatt.getDevice() + ": disconnecting from sensor: ");
|
||||
case BluetoothProfile.STATE_DISCONNECTED -> {
|
||||
//This is also triggered, if no connection was established (ca. 30s)
|
||||
Log.i(TAG, gatt.getDevice() + ": disconnected from sensor: trying to reconnect");
|
||||
if (gatt.connect()) {
|
||||
Log.e(TAG, gatt.getDevice() + ": could not trigger reconnect for sensor");
|
||||
}
|
||||
clearData();
|
||||
observer.onConnectionLost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
||||
BluetoothDevice device = gatt.getDevice();
|
||||
|
||||
BluetoothGattService gattService = null;
|
||||
ServiceMeasurementUUID serviceMeasurement = null;
|
||||
for (ServiceMeasurementUUID s : sensorHandler.getServices()) {
|
||||
@@ -87,13 +91,13 @@ public class BluetoothConnectionManager implements Driver {
|
||||
}
|
||||
|
||||
if (gattService == null) {
|
||||
Log.e(TAG, gatt.getDevice() + ": could not get gattService for serviceUUID=" + serviceMeasurement);
|
||||
Log.e(TAG, device + ": could not get gattService for serviceUUID=" + serviceMeasurement);
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.measurementUUID());
|
||||
if (characteristic == null) {
|
||||
Log.e(TAG, gatt.getDevice() + ": could not get BluetoothCharacteristic for serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
|
||||
Log.e(TAG, device + ": could not get BluetoothCharacteristic for serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
|
||||
return;
|
||||
}
|
||||
gatt.setCharacteristicNotification(characteristic, true);
|
||||
@@ -111,6 +115,8 @@ public class BluetoothConnectionManager implements Driver {
|
||||
if (!gatt.writeDescriptor(descriptor)) {
|
||||
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID descriptor could not be written");
|
||||
}
|
||||
|
||||
observer.onConnected(device.getAddress(), device.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,11 +133,15 @@ public class BluetoothConnectionManager implements Driver {
|
||||
return;
|
||||
}
|
||||
|
||||
sensorHandler.handlePayload(observer, serviceMeasurementUUID.get(), gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
|
||||
T value = sensorHandler.parsePayload(serviceMeasurementUUID.get(), gatt.getDevice().getName(), characteristic);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
observer.onDataReceived(value);
|
||||
}
|
||||
};
|
||||
|
||||
BluetoothConnectionManager(BluetoothAdapter bluetoothAdapter, SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) {
|
||||
public BluetoothDriver(BluetoothAdapter bluetoothAdapter, DriverObserver<T> observer, BluetoothParser<T> sensorHandler) {
|
||||
this.bluetoothAdapter = bluetoothAdapter;
|
||||
this.observer = observer;
|
||||
this.sensorHandler = sensorHandler;
|
||||
@@ -144,26 +154,11 @@ public class BluetoothConnectionManager implements Driver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SensorType.NONE.getPreferenceValue().equals(address)) {
|
||||
Log.w(TAG, "NONE: going to disconnect");
|
||||
if (isConnected()) {
|
||||
disconnect();
|
||||
observer.onRemove(sensorHandler.createEmptySensorData(null, null));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConnected()) {
|
||||
Log.w(TAG, "Already connected; ignoring.");
|
||||
Log.e(TAG, "Already connected; ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSameBluetoothDevice(address)) {
|
||||
return;
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
BluetoothDevice device;
|
||||
try {
|
||||
device = bluetoothAdapter.getRemoteDevice(address);
|
||||
@@ -175,37 +170,23 @@ public class BluetoothConnectionManager implements Driver {
|
||||
Log.d(TAG, device + ": trying to connect");
|
||||
|
||||
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);
|
||||
|
||||
observer.onConnect(sensorHandler.createEmptySensorData(device.getAddress(), device.getName()));
|
||||
}
|
||||
|
||||
private synchronized void clearData() {
|
||||
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress(), bluetoothGatt.getDevice().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void disconnect() {
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "Not connected; no need to re-connect.");
|
||||
Log.w(TAG, "Not connected; cannot disconnect.");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, bluetoothGatt.getDevice() + ": start disconnect");
|
||||
bluetoothGatt.disconnect();
|
||||
bluetoothGatt.close();
|
||||
clearData();
|
||||
Log.i(TAG, bluetoothGatt.getDevice() + ": disconnect finished");
|
||||
bluetoothGatt = null;
|
||||
|
||||
observer.onDisconnected();
|
||||
}
|
||||
|
||||
private synchronized boolean isSameBluetoothDevice(String address) {
|
||||
if (bluetoothGatt == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return address.equals(bluetoothGatt.getDevice().getAddress());
|
||||
}
|
||||
|
||||
|
||||
private boolean isBluetoothEnabled() {
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
@@ -214,4 +195,11 @@ public class BluetoothConnectionManager implements Driver {
|
||||
public boolean isConnected() {
|
||||
return bluetoothGatt != null;
|
||||
}
|
||||
|
||||
public interface BluetoothParser<T> {
|
||||
|
||||
List<ServiceMeasurementUUID> getServices();
|
||||
|
||||
T parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public class CyclingCadenceBluetooth implements BluetoothDriver.BluetoothParser<CyclingCadenceBluetooth.CrankData> {
|
||||
private static final String TAG = CyclingCadenceBluetooth.class.getSimpleName();
|
||||
|
||||
public static final List<ServiceMeasurementUUID> CYCLING_CADENCE = List.of(
|
||||
CyclingPowerBluetooth.CYCLING_POWER,
|
||||
CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<ServiceMeasurementUUID> getServices() {
|
||||
return CYCLING_CADENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrankData parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
if (serviceMeasurementUUID.equals(CyclingPowerBluetooth.CYCLING_POWER)) {
|
||||
CyclingPowerBluetooth.Data data = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||
if (data != null && data.crank() != null) {
|
||||
return data.crank();
|
||||
}
|
||||
}
|
||||
|
||||
if (serviceMeasurementUUID.equals(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE)) {
|
||||
Pair<CyclingDistanceSpeedBluetooth.WheelData, CrankData> data = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||
|
||||
if (data != null && data.second != null) {
|
||||
return data.second;
|
||||
}
|
||||
}
|
||||
|
||||
Log.e(TAG, "Don't know how to decode this payload.");
|
||||
return null;
|
||||
}
|
||||
|
||||
public record CrankData(
|
||||
long crankRevolutionsCount, // UINT32
|
||||
int crankRevolutionsTime // UINT16; 1/1024s
|
||||
) {}
|
||||
}
|
||||
+14
-18
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Pair;
|
||||
@@ -9,11 +9,9 @@ import androidx.annotation.VisibleForTesting;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterface {
|
||||
public class CyclingDistanceSpeedBluetooth implements BluetoothDriver.BluetoothParser<CyclingDistanceSpeedBluetooth.WheelData> {
|
||||
|
||||
public static final ServiceMeasurementUUID CYCLING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
||||
new UUID(0x181600001000L, 0x800000805f9b34fbL),
|
||||
@@ -26,21 +24,18 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorCyclingDistanceSpeed createEmptySensorData(String address, String name) {
|
||||
return new AggregatorCyclingDistanceSpeed(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
Pair<WheelData, BluetoothHandlerCyclingCadence.CrankData> data = parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
||||
if (data.first != null) {
|
||||
observer.onChange(new Raw<>(observer.getNow(), data.first));
|
||||
public WheelData parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
Pair<WheelData, CyclingCadenceBluetooth.CrankData> data = parseCyclingCrankAndWheel(characteristic);
|
||||
if (data != null && data.first != null) {
|
||||
return data.first;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public static Pair<WheelData, BluetoothHandlerCyclingCadence.CrankData> parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||
public static Pair<WheelData, CyclingCadenceBluetooth.CrankData> parseCyclingCrankAndWheel(@NonNull BluetoothGattCharacteristic characteristic) {
|
||||
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
|
||||
int valueLength = characteristic.getValue().length;
|
||||
if (valueLength == 0) {
|
||||
@@ -61,13 +56,13 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
|
||||
index += 2;
|
||||
}
|
||||
|
||||
BluetoothHandlerCyclingCadence.CrankData crankData = null;
|
||||
CyclingCadenceBluetooth.CrankData crankData = null;
|
||||
if (hasCrank && valueLength - index >= 4) {
|
||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||
index += 2;
|
||||
|
||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||
crankData = new BluetoothHandlerCyclingCadence.CrankData(crankCount, crankTime);
|
||||
crankData = new CyclingCadenceBluetooth.CrankData(crankCount, crankTime);
|
||||
}
|
||||
|
||||
return new Pair<>(wheelData, crankData);
|
||||
@@ -78,5 +73,6 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
|
||||
long wheelRevolutionsCount, // UINT32
|
||||
|
||||
int wheelRevolutionsTime // UINT16; 1/1024s
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
}
|
||||
+12
-20
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
@@ -9,11 +9,9 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public class BluetoothHandlerCyclingPower implements SensorHandlerInterface {
|
||||
public class CyclingPowerBluetooth implements BluetoothDriver.BluetoothParser<CyclingPowerBluetooth.Data> {
|
||||
|
||||
public static final ServiceMeasurementUUID CYCLING_POWER = new ServiceMeasurementUUID(
|
||||
new UUID(0x181800001000L, 0x800000805f9b34fbL),
|
||||
@@ -26,17 +24,8 @@ public class BluetoothHandlerCyclingPower implements SensorHandlerInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorCyclingPower createEmptySensorData(String address, String name) {
|
||||
return new AggregatorCyclingPower(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
Data cyclingPower = parseCyclingPower(characteristic);
|
||||
|
||||
if (cyclingPower != null) {
|
||||
observer.onChange(new Raw<>(observer.getNow(), cyclingPower));
|
||||
}
|
||||
public CyclingPowerBluetooth.Data parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
return parseCyclingPower(characteristic);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,19 +58,22 @@ public class BluetoothHandlerCyclingPower implements SensorHandlerInterface {
|
||||
index += 2 + 2;
|
||||
}
|
||||
|
||||
BluetoothHandlerCyclingCadence.CrankData crankData = null;
|
||||
CyclingCadenceBluetooth.CrankData crankData = null;
|
||||
if (hasCrank && valueLength - index >= 4) {
|
||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||
index += 2;
|
||||
|
||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||
|
||||
crankData = new BluetoothHandlerCyclingCadence.CrankData(crankCount, crankTime);
|
||||
crankData = new CyclingCadenceBluetooth.CrankData(crankCount, crankTime);
|
||||
}
|
||||
|
||||
return new Data(Power.of(instantaneousPower), crankData);
|
||||
}
|
||||
|
||||
public record Data(Power power, BluetoothHandlerCyclingCadence.CrankData crank) {}
|
||||
|
||||
public record Data(
|
||||
Power power,
|
||||
CyclingCadenceBluetooth.CrankData crank
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,11 @@ import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
public interface Driver {
|
||||
|
||||
//TODO Address is optional for internal drivers. Move address to driver instantiation.
|
||||
void connect(Context context, Handler handler, String address);
|
||||
|
||||
//TODO Remove from interface?
|
||||
boolean isConnected();
|
||||
|
||||
void disconnect();
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
|
||||
public interface DriverObserver<T> {
|
||||
/**
|
||||
* The Driver could connect to the sensor.
|
||||
*/
|
||||
void onConnected(String sensorAddress, String sensorName);
|
||||
|
||||
/**
|
||||
* Sensor got temporarily disconnected, Driver should try to re-connect.
|
||||
*/
|
||||
void onConnectionLost();
|
||||
|
||||
/**
|
||||
* The Driver provided data from the sensor.
|
||||
*/
|
||||
void onDataReceived(T value);
|
||||
|
||||
/**
|
||||
* The Driver was shutdown and no further data will be provided.
|
||||
*/
|
||||
//TODO check if really need this, because the driver was told to disconnect and then just signalizes that it finished this task.
|
||||
void onDisconnected();
|
||||
|
||||
/**
|
||||
* The Driver detected that the sensor got deactivated.
|
||||
* For example, the user deactivated the internal GPS.
|
||||
*/
|
||||
default void onSensorDeactivated() {}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.location.LocationListenerCompat;
|
||||
import androidx.core.location.LocationManagerCompat;
|
||||
import androidx.core.location.LocationRequestCompat;
|
||||
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
public class GpsInternal implements Driver {
|
||||
|
||||
private static final String TAG = GpsInternal.class.getSimpleName();
|
||||
|
||||
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||
|
||||
@VisibleForTesting
|
||||
public final LocationListenerCompat locationListenerCompat = new LocationListenerCompat() {
|
||||
@Override
|
||||
public void onLocationChanged(@NonNull Location location) {
|
||||
driverObserver.onDataReceived(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(@NonNull String provider) {
|
||||
driverObserver.onConnected(null, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(@NonNull String provider) {
|
||||
driverObserver.onSensorDeactivated();
|
||||
}
|
||||
};
|
||||
|
||||
private final DriverObserver<Location> driverObserver;
|
||||
|
||||
@VisibleForTesting
|
||||
public LocationManager locationManager;
|
||||
|
||||
public GpsInternal(DriverObserver<Location> driverObserver) {
|
||||
this.driverObserver = driverObserver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Context context, Handler handler, String address) {
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
|
||||
if (!LocationManagerCompat.hasProvider(locationManager, LOCATION_PROVIDER)) {
|
||||
Log.e(TAG, "Device doesn't have GPS.");
|
||||
return;
|
||||
}
|
||||
|
||||
LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis())
|
||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||
.setMaxUpdateDelayMillis(0)
|
||||
.build();
|
||||
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
try {
|
||||
Log.i(TAG, "Register for location updates " + context);
|
||||
driverObserver.onConnected(null, null);
|
||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, handler::post, locationListenerCompat);
|
||||
return;
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||
}
|
||||
}
|
||||
|
||||
driverObserver.onDisconnected();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MissingPermission"})
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (!isConnected()) return;
|
||||
|
||||
LocationManagerCompat.removeUpdates(locationManager, locationListenerCompat);
|
||||
|
||||
locationManager = null;
|
||||
driverObserver.onDisconnected();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return locationManager != null;
|
||||
}
|
||||
}
|
||||
+5
-16
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
@@ -9,11 +9,9 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public class BluetoothHandlerHeartRate implements SensorHandlerInterface {
|
||||
public class HeartRateBluetooth implements BluetoothDriver.BluetoothParser<HeartRate> {
|
||||
|
||||
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
|
||||
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
|
||||
@@ -36,17 +34,8 @@ public class BluetoothHandlerHeartRate implements SensorHandlerInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorHeartRate createEmptySensorData(String address, String name) {
|
||||
return new AggregatorHeartRate(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
HeartRate heartRate = parseHeartRate(characteristic);
|
||||
|
||||
if (heartRate != null) {
|
||||
observer.onChange(new Raw<>(observer.getNow(), heartRate));
|
||||
}
|
||||
public HeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
return parseHeartRate(characteristic);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
+5
-13
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
@@ -11,11 +11,9 @@ import java.util.UUID;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInterface {
|
||||
public class RunningSpeedAndCadenceBluetooth implements BluetoothDriver.BluetoothParser<RunningSpeedAndCadenceBluetooth.Data> {
|
||||
|
||||
|
||||
public static final ServiceMeasurementUUID RUNNING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
||||
@@ -29,14 +27,8 @@ public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInte
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorRunning createEmptySensorData(String address, String name) {
|
||||
return new AggregatorRunning(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
Data data = parseRunningSpeedAndCadence(sensorName, characteristic);
|
||||
observer.onChange(new Raw<>(observer.getNow(), data));
|
||||
public Data parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
return parseRunningSpeedAndCadence(sensorName, characteristic);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
+5
-3
@@ -1,8 +1,10 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
public enum SensorType {
|
||||
NONE("NONE"),
|
||||
INTERNAL("INTERNAL"),
|
||||
NONE("NONE"), //TODO Use R.string.sensor_type_value_none
|
||||
INTERNAL("INTERNAL"), //TODO Use R.string.sensor_type_value_none
|
||||
|
||||
//NOTE: preferenceValue of REMOTE should not be used anywhere.
|
||||
REMOTE("*");
|
||||
|
||||
private final String preferenceValue;
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static de.dennisguse.opentracks.sensors.driver.BarometerBluetooth.ENVIRONMENTAL_SENSING_SERVICE;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Temperature;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public class TemperatureBluetooth implements BluetoothDriver.BluetoothParser<Temperature> {
|
||||
public static final ServiceMeasurementUUID TEMPERATURE = new ServiceMeasurementUUID(
|
||||
ENVIRONMENTAL_SENSING_SERVICE,
|
||||
new UUID(0x2A6E00001000L, 0x800000805f9b34fbL)
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<ServiceMeasurementUUID> getServices() {
|
||||
return List.of(TEMPERATURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Temperature parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
return parseEnvironmentalSensing(characteristic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoding: org.bluetooth.characteristic.temperature.xml
|
||||
*/
|
||||
public static Temperature parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] raw = characteristic.getValue();
|
||||
|
||||
if (raw.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer pressure = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 0);
|
||||
return Temperature.ofCentiCelsius(pressure);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,74 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public abstract class Aggregator<Input, Output> {
|
||||
public abstract class Aggregator<InputType, OutputType> {
|
||||
|
||||
private static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
|
||||
|
||||
protected Raw<Input> previous;
|
||||
@Nullable
|
||||
protected Raw<InputType> previous;
|
||||
|
||||
protected Output aggregatedValue;
|
||||
//TODO Make private
|
||||
protected OutputType output;
|
||||
|
||||
private final String sensorAddress;
|
||||
private final String sensorName;
|
||||
|
||||
Aggregator(String sensorAddress) {
|
||||
this(sensorAddress, null);
|
||||
}
|
||||
|
||||
Aggregator(String sensorAddress, String sensorName) {
|
||||
this.sensorAddress = sensorAddress;
|
||||
this.sensorName = sensorName;
|
||||
}
|
||||
|
||||
public String getSensorNameOrAddress() {
|
||||
private String getSensorNameOrAddress() {
|
||||
return sensorName != null ? sensorName : sensorAddress;
|
||||
}
|
||||
|
||||
public final void add(Raw<Input> current) {
|
||||
computeValue(current);
|
||||
previous = current;
|
||||
public final void add(Instant now, InputType current) {
|
||||
Raw<InputType> next = new Raw<>(now, current);
|
||||
computeValue(next);
|
||||
previous = next;
|
||||
}
|
||||
|
||||
protected abstract void computeValue(Raw<Input> current);
|
||||
protected abstract void computeValue(@NonNull Raw<InputType> current);
|
||||
|
||||
/**
|
||||
* @return did we process data from a sensor.
|
||||
* NOTE: for some sensors this may require more than one measurement.
|
||||
*/
|
||||
public boolean hasReceivedData() {
|
||||
return aggregatedValue != null;
|
||||
return output != null;
|
||||
}
|
||||
|
||||
@Deprecated //TODO Remove: for sensor there is no default value. Make getAggregatedValue() @Nullable
|
||||
@NonNull
|
||||
protected abstract Output getNoneValue();
|
||||
|
||||
@NonNull
|
||||
public Output getAggregatedValue(Instant now) {
|
||||
if (!hasReceivedData()) {
|
||||
return getNoneValue();
|
||||
}
|
||||
@Nullable
|
||||
protected OutputType getAggregatedValue(Instant now) {
|
||||
if (isOutdated(now)) {
|
||||
resetImmediate();
|
||||
resetOutdated();
|
||||
}
|
||||
|
||||
return aggregatedValue;
|
||||
return output;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public SensorData<Output> getAggregatedValueWithSensorName(Instant now) {
|
||||
public SensorData<OutputType> getAggregatedValueWithSensorName(Instant now) {
|
||||
return new SensorData<>(getAggregatedValue(now), getSensorNameOrAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset short-term (i.e., non-aggregated) values that were directly derived from sensor data.
|
||||
*/
|
||||
protected abstract void resetImmediate();
|
||||
protected void resetOutdated() {
|
||||
output = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset long-term (i.e., aggregated) values (more than derived from previous SensorData) like overall distance.
|
||||
*/
|
||||
public abstract void resetAggregated();
|
||||
public void resetAggregated() {};
|
||||
|
||||
/**
|
||||
* Is the data recent considering the current time.
|
||||
@@ -97,6 +92,12 @@ public abstract class Aggregator<Input, Output> {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "sensorAddress=" + sensorAddress + " data=" + aggregatedValue;
|
||||
return "sensorAddress=" + sensorAddress + " data=" + output;
|
||||
}
|
||||
|
||||
protected record Raw<T>(
|
||||
@NonNull Instant time,
|
||||
@NonNull T value
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,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;
|
||||
@@ -16,35 +15,30 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeValue(Raw<AtmosphericPressure> current) {
|
||||
protected void computeValue(@NonNull Raw<AtmosphericPressure> current) {
|
||||
if (previous == null) {
|
||||
lastAcceptedSensorValue = current.value();
|
||||
aggregatedValue = getNoneValue();
|
||||
output = new AltitudeGainLoss(0f, 0f);
|
||||
return;
|
||||
}
|
||||
|
||||
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedSensorValue, previous.value(), current.value());
|
||||
if (altitudeChange != null) {
|
||||
aggregatedValue = new AltitudeGainLoss(aggregatedValue.gain_m() + altitudeChange.getAltitudeGain_m(), aggregatedValue.loss_m() + altitudeChange.getAltitudeLoss_m());
|
||||
if (output == null) {
|
||||
output = new AltitudeGainLoss(0f, 0f);
|
||||
}
|
||||
output = new AltitudeGainLoss(output.gain_m() + altitudeChange.getAltitudeGain_m(), output.loss_m() + altitudeChange.getAltitudeLoss_m());
|
||||
|
||||
lastAcceptedSensorValue = altitudeChange.currentSensorValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImmediate() {
|
||||
protected void resetOutdated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAggregated() {
|
||||
aggregatedValue = getNoneValue();
|
||||
output = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AltitudeGainLoss getNoneValue() {
|
||||
return new AltitudeGainLoss(0f, 0f);
|
||||
}
|
||||
|
||||
public record Data(Altitude gain, Altitude loss) {}
|
||||
}
|
||||
|
||||
+6
-20
@@ -7,10 +7,10 @@ import androidx.annotation.NonNull;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||
|
||||
public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCyclingCadence.CrankData, Cadence> {
|
||||
public class AggregatorCyclingCadence extends Aggregator<CyclingCadenceBluetooth.CrankData, Cadence> {
|
||||
|
||||
private final String TAG = AggregatorCyclingCadence.class.getSimpleName();
|
||||
|
||||
@@ -19,7 +19,7 @@ public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCycling
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeValue(Raw<BluetoothHandlerCyclingCadence.CrankData> current) {
|
||||
protected void computeValue(@NonNull Raw<CyclingCadenceBluetooth.CrankData> current) {
|
||||
if (previous == null) {
|
||||
return;
|
||||
}
|
||||
@@ -32,32 +32,18 @@ public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCycling
|
||||
}
|
||||
if (timeDiff.isNegative()) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
aggregatedValue = null;
|
||||
output = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO We have to treat with overflow according to the documentation: read https://codeberg.org/OpenTracksApp/OpenTracks/pulls/953#issuecomment-6466930
|
||||
if (current.value().crankRevolutionsCount() < previous.value().crankRevolutionsCount()) {
|
||||
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
|
||||
output = null;
|
||||
return;
|
||||
}
|
||||
|
||||
long crankDiff = UintUtils.diff(current.value().crankRevolutionsCount(), previous.value().crankRevolutionsCount(), UintUtils.UINT32_MAX);
|
||||
aggregatedValue = Cadence.of(crankDiff, timeDiff);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImmediate() {
|
||||
aggregatedValue = getNoneValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAggregated() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Cadence getNoneValue() {
|
||||
return Cadence.of(0);
|
||||
output = Cadence.of(crankDiff, timeDiff);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-17
@@ -8,10 +8,10 @@ import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||
|
||||
public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerCyclingDistanceSpeed.WheelData, AggregatorCyclingDistanceSpeed.Data> {
|
||||
public class AggregatorCyclingDistanceSpeed extends Aggregator<CyclingDistanceSpeedBluetooth.WheelData, AggregatorCyclingDistanceSpeed.Data> {
|
||||
|
||||
private final String TAG = AggregatorCyclingDistanceSpeed.class.getSimpleName();
|
||||
|
||||
@@ -22,7 +22,7 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerC
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeValue(Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData> current) {
|
||||
protected void computeValue(@NonNull Raw<CyclingDistanceSpeedBluetooth.WheelData> current) {
|
||||
if (previous == null) {
|
||||
return;
|
||||
}
|
||||
@@ -35,43 +35,39 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerC
|
||||
}
|
||||
if (timeDiff.isNegative()) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
aggregatedValue = null;
|
||||
output = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (current.value().wheelRevolutionsCount() < previous.value().wheelRevolutionsCount()) {
|
||||
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
|
||||
output = null;
|
||||
return;
|
||||
}
|
||||
long wheelDiff = UintUtils.diff(current.value().wheelRevolutionsCount(), previous.value().wheelRevolutionsCount(), UintUtils.UINT32_MAX);
|
||||
|
||||
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
||||
Distance distanceOverall = distance;
|
||||
if (aggregatedValue != null) {
|
||||
distanceOverall = distance.plus(aggregatedValue.distanceOverall);
|
||||
if (output != null) {
|
||||
distanceOverall = distance.plus(output.distanceOverall);
|
||||
}
|
||||
Speed speed_mps = Speed.of(distance, timeDiff);
|
||||
aggregatedValue = new Data(distance, distanceOverall, speed_mps);
|
||||
output = new Data(distance, distanceOverall, speed_mps);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImmediate() {
|
||||
aggregatedValue = new Data(Distance.ZERO, aggregatedValue.distanceOverall, Speed.ZERO);
|
||||
protected void resetOutdated() {
|
||||
Distance overallDistance = output != null ? output.distanceOverall : Distance.ZERO;
|
||||
output = new Data(Distance.ZERO, overallDistance, Speed.ZERO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAggregated() {
|
||||
if (aggregatedValue != null) {
|
||||
aggregatedValue = new Data(aggregatedValue.distance, Distance.ZERO, aggregatedValue.speed);
|
||||
if (output != null) {
|
||||
output = new Data(output.distance, Distance.ZERO, output.speed);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Data getNoneValue() {
|
||||
return new Data(Distance.ZERO, Distance.ZERO, Speed.ZERO);
|
||||
}
|
||||
|
||||
public void setWheelCircumference(Distance wheelCircumference) {
|
||||
this.wheelCircumference = wheelCircumference;
|
||||
}
|
||||
|
||||
+4
-19
@@ -3,31 +3,16 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||
|
||||
public class AggregatorCyclingPower extends Aggregator<BluetoothHandlerCyclingPower.Data, Power> {
|
||||
public class AggregatorCyclingPower extends Aggregator<CyclingPowerBluetooth.Data, Power> {
|
||||
|
||||
public AggregatorCyclingPower(String name, String address) {
|
||||
super(name, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeValue(Raw<BluetoothHandlerCyclingPower.Data> current) {
|
||||
this.aggregatedValue = current.value().power();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImmediate() {
|
||||
aggregatedValue = getNoneValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAggregated() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Power getNoneValue() {
|
||||
return Power.of(0f);
|
||||
public void computeValue(@NonNull Raw<CyclingPowerBluetooth.Data> current) {
|
||||
this.output = current.value().power();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
|
||||
public class AggregatorGPS extends Aggregator<Position, Position> {
|
||||
public class AggregatorGPS extends Aggregator<Location, Position> {
|
||||
|
||||
|
||||
public AggregatorGPS(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
public AggregatorGPS(String sensorAddress, String sensorName) {
|
||||
super(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeValue(Raw<Position> current) {
|
||||
aggregatedValue = current.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImmediate() {
|
||||
aggregatedValue = Position.empty();
|
||||
protected void computeValue(@NonNull Raw<Location> current) {
|
||||
output = Position.of(current.value(), current.time());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -28,21 +22,7 @@ public class AggregatorGPS extends Aggregator<Position, 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()
|
||||
*/
|
||||
aggregatedValue = Position.empty();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Position getNoneValue() {
|
||||
return Position.empty();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Position getAggregatedValue(Instant now) {
|
||||
return super.getAggregatedValue(now)
|
||||
.with(now);
|
||||
resetOutdated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,9 @@ public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeValue(Raw<HeartRate> current) {
|
||||
protected void computeValue(@NonNull Raw<HeartRate> current) {
|
||||
if (current.value().isValid()) {
|
||||
this.aggregatedValue = current.value();
|
||||
this.output = current.value();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImmediate() {
|
||||
aggregatedValue = getNoneValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAggregated() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected HeartRate getNoneValue() {
|
||||
return HeartRate.of(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import androidx.annotation.NonNull;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||
|
||||
/**
|
||||
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Running Speed and Cadence sensors.
|
||||
*/
|
||||
public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningSpeedAndCadence.Data, AggregatorRunning.Data> {
|
||||
public final class AggregatorRunning extends Aggregator<RunningSpeedAndCadenceBluetooth.Data, AggregatorRunning.Data> {
|
||||
|
||||
private static final String TAG = AggregatorRunning.class.getSimpleName();
|
||||
|
||||
@@ -19,7 +19,7 @@ public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningS
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeValue(Raw<BluetoothHandlerRunningSpeedAndCadence.Data> current) {
|
||||
public void computeValue(@NonNull Raw<RunningSpeedAndCadenceBluetooth.Data> current) {
|
||||
if (previous == null) {
|
||||
return;
|
||||
}
|
||||
@@ -27,33 +27,31 @@ public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningS
|
||||
Distance distance = null;
|
||||
if (previous.value().totalDistance() != null && current.value().totalDistance() != null) {
|
||||
distance = current.value().totalDistance().minus(previous.value().totalDistance());
|
||||
if (aggregatedValue != null) {
|
||||
distance = distance.plus(aggregatedValue.distance);
|
||||
if (output != null) {
|
||||
distance = distance.plus(output.distance);
|
||||
}
|
||||
}
|
||||
|
||||
aggregatedValue = new Data(current.value().speed(), current.value().cadence(), distance);
|
||||
output = new Data(current.value().speed(), current.value().cadence(), distance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImmediate() {
|
||||
aggregatedValue = new Data(Speed.ZERO, Cadence.of(0f), aggregatedValue.distance);
|
||||
protected void resetOutdated() {
|
||||
Distance distance = output != null ? output.distance : Distance.ZERO;
|
||||
output = new Data(Speed.ZERO, Cadence.of(0f), distance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAggregated() {
|
||||
if (aggregatedValue != null) {
|
||||
aggregatedValue = new Data(aggregatedValue.speed, aggregatedValue.cadence, Distance.ZERO);
|
||||
if (output != null) {
|
||||
output = new Data(output.speed, output.cadence, Distance.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Data getNoneValue() {
|
||||
return new Data(Speed.ZERO, Cadence.of(0f), Distance.ZERO);
|
||||
}
|
||||
|
||||
public record Data(Speed speed, Cadence cadence, @NonNull Distance distance) {
|
||||
public record Data(
|
||||
Speed speed,
|
||||
Cadence cadence,
|
||||
@NonNull Distance distance) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-13
@@ -12,21 +12,10 @@ public class AggregatorTemperature extends Aggregator<Temperature, Temperature>
|
||||
super(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
protected void computeValue(Raw<Temperature> current) {
|
||||
this.aggregatedValue = current.value();
|
||||
protected void computeValue(@NonNull Raw<Temperature> current) {
|
||||
this.output = current.value();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Temperature getNoneValue() {
|
||||
return Temperature.ofCentiCelsius(0);
|
||||
}
|
||||
|
||||
protected void resetImmediate() {
|
||||
aggregatedValue = getNoneValue();
|
||||
}
|
||||
|
||||
public void resetAggregated() {};
|
||||
|
||||
@Override
|
||||
protected Duration getMaxSensorAge() {
|
||||
return Duration.ofMinutes(1);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
|
||||
public record Raw<T>(
|
||||
@NonNull Instant time,
|
||||
@NonNull T value
|
||||
) {
|
||||
public Raw(@NonNull Clock clock, @NonNull T value) {
|
||||
this(clock.instant(), value);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Raw(@NonNull String time, @NonNull T value) {
|
||||
this(Instant.parse(time), value);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public record SensorData<T>(
|
||||
|
||||
|
||||
@Nullable
|
||||
T data,
|
||||
|
||||
String sensorNameOrAddress
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
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.data.models.Temperature;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class SensorDataAggregator {
|
||||
|
||||
private static final String TAG = SensorDataAggregator.class.getSimpleName();
|
||||
|
||||
@VisibleForTesting
|
||||
public AggregatorHeartRate heartRate;
|
||||
|
||||
@VisibleForTesting
|
||||
public AggregatorTemperature temperature;
|
||||
|
||||
@VisibleForTesting
|
||||
public AggregatorCyclingCadence cyclingCadence;
|
||||
|
||||
@VisibleForTesting
|
||||
public AggregatorCyclingDistanceSpeed cyclingDistanceSpeed;
|
||||
|
||||
@VisibleForTesting
|
||||
public AggregatorCyclingPower cyclingPower;
|
||||
|
||||
@VisibleForTesting
|
||||
public AggregatorRunning runningDistanceSpeedCadence;
|
||||
|
||||
@VisibleForTesting
|
||||
public AggregatorBarometer barometer;
|
||||
|
||||
public AggregatorGPS gps;
|
||||
|
||||
@VisibleForTesting
|
||||
public SensorData<HeartRate> getHeartRate(Instant now) {
|
||||
if (heartRate != null) {
|
||||
SensorData<HeartRate> value = heartRate.getAggregatedValueWithSensorName(now);
|
||||
if (value.data().isValid()) return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private SensorData<Cadence> getCadence(Instant now) {
|
||||
if (cyclingCadence != null) {
|
||||
return cyclingCadence.getAggregatedValueWithSensorName(now);
|
||||
}
|
||||
|
||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.getAggregatedValue(now).cadence() != null) {
|
||||
return new SensorData<>(runningDistanceSpeedCadence.aggregatedValue.cadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private SensorData<Distance> getDistance(Instant now) {
|
||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData()) {
|
||||
return new SensorData<>(cyclingDistanceSpeed.getAggregatedValue(now).distanceOverall(), cyclingDistanceSpeed.getSensorNameOrAddress());
|
||||
}
|
||||
|
||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData()) {
|
||||
return new SensorData<>(runningDistanceSpeedCadence.getAggregatedValue(now).distance(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//TOOD simplify?
|
||||
private SensorData<Speed> getSpeed(Instant now) {
|
||||
|
||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData() && cyclingDistanceSpeed.getAggregatedValue(now).speed() != null) {
|
||||
return new SensorData<>(cyclingDistanceSpeed.getAggregatedValue(now).speed(), cyclingDistanceSpeed.getSensorNameOrAddress());
|
||||
}
|
||||
|
||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.getAggregatedValue(now).speed() != null) {
|
||||
return new SensorData<>(runningDistanceSpeedCadence.aggregatedValue.speed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void add(@NonNull Aggregator<?, ?> data) {
|
||||
set(data, data);
|
||||
}
|
||||
|
||||
//TODO Refactor generic "fun" here: go explicit.
|
||||
public void update(@NonNull Raw<?> data) {
|
||||
Object value = data.value();
|
||||
|
||||
if (value instanceof HeartRate) {
|
||||
this.heartRate.add((Raw<HeartRate>) data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof Temperature) {
|
||||
this.temperature.add((Raw<Temperature>) data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof BluetoothHandlerCyclingCadence.CrankData) {
|
||||
this.cyclingCadence.add((Raw<BluetoothHandlerCyclingCadence.CrankData>) data);
|
||||
return;
|
||||
}
|
||||
if (value instanceof BluetoothHandlerCyclingDistanceSpeed.WheelData) {
|
||||
this.cyclingDistanceSpeed.setWheelCircumference(PreferencesUtils.getWheelCircumference()); //TODO Fetch once and then listen for changes.
|
||||
this.cyclingDistanceSpeed.add((Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData>) data);
|
||||
return;
|
||||
}
|
||||
if (value instanceof BluetoothHandlerRunningSpeedAndCadence.Data) {
|
||||
this.runningDistanceSpeedCadence.add((Raw<BluetoothHandlerRunningSpeedAndCadence.Data>) data);
|
||||
|
||||
return;
|
||||
}
|
||||
if (value instanceof BluetoothHandlerCyclingPower.Data) {
|
||||
this.cyclingPower.add((Raw<BluetoothHandlerCyclingPower.Data>) data);
|
||||
return;
|
||||
}
|
||||
if (value instanceof AtmosphericPressure) {
|
||||
this.barometer.add((Raw<AtmosphericPressure>) data);
|
||||
return;
|
||||
}
|
||||
if (value instanceof Position) {
|
||||
this.gps.add((Raw<Position>) data);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException(data.getClass().getCanonicalName() + " " + data.value().getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
public void remove(@NonNull Aggregator<?, ?> type) {
|
||||
set(type, null);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
Log.i(TAG, "Removing all aggregators");
|
||||
this.heartRate = null;
|
||||
this.temperature = null;
|
||||
this.cyclingCadence = null;
|
||||
this.cyclingDistanceSpeed = null;
|
||||
this.cyclingPower = null;
|
||||
this.runningDistanceSpeedCadence = null;
|
||||
this.barometer = null;
|
||||
this.gps = null;
|
||||
}
|
||||
|
||||
public SensorDataSet getSensorData(Instant now) {
|
||||
return new SensorDataSet(
|
||||
gps != null && gps.hasReceivedData() ? gps.getAggregatedValueWithSensorName(now) : new SensorData<>(Position.of(now), ""),
|
||||
getSpeed(now),
|
||||
getDistance(now),
|
||||
getHeartRate(now),
|
||||
temperature != null && temperature.hasReceivedData() ? temperature.getAggregatedValueWithSensorName(now) : null,
|
||||
getCadence(now),
|
||||
cyclingPower != null && cyclingPower.hasReceivedData() ? cyclingPower.getAggregatedValueWithSensorName(now) : null,
|
||||
barometer != null && barometer.hasReceivedData() ? barometer.getAggregatedValueWithSensorName(now) : null
|
||||
);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
Log.i(TAG, "Resetting data");
|
||||
|
||||
if (heartRate != null) heartRate.resetAggregated();
|
||||
if (temperature != null) temperature.resetAggregated();
|
||||
if (cyclingCadence != null) cyclingCadence.resetAggregated();
|
||||
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.resetAggregated();
|
||||
if (cyclingPower != null) cyclingPower.resetAggregated();
|
||||
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.resetAggregated();
|
||||
if (barometer != null) barometer.resetAggregated();
|
||||
if (gps != null) gps.resetAggregated();
|
||||
}
|
||||
|
||||
//TODO Refactor generic "fun" here: go explicit.
|
||||
private void set(@NonNull Aggregator<?, ?> type, @Nullable Aggregator<?, ?> sensorData) {
|
||||
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName() + " to " + sensorData);
|
||||
|
||||
if (type instanceof AggregatorHeartRate) {
|
||||
heartRate = (AggregatorHeartRate) sensorData;
|
||||
return;
|
||||
}
|
||||
if (type instanceof AggregatorTemperature) {
|
||||
temperature = (AggregatorTemperature) sensorData;
|
||||
return;
|
||||
}
|
||||
if (type instanceof AggregatorCyclingCadence) {
|
||||
cyclingCadence = (AggregatorCyclingCadence) sensorData;
|
||||
return;
|
||||
}
|
||||
if (type instanceof AggregatorCyclingDistanceSpeed) {
|
||||
cyclingDistanceSpeed = (AggregatorCyclingDistanceSpeed) sensorData;
|
||||
return;
|
||||
}
|
||||
if (type instanceof AggregatorCyclingPower) {
|
||||
cyclingPower = (AggregatorCyclingPower) sensorData;
|
||||
return;
|
||||
}
|
||||
if (type instanceof AggregatorRunning) {
|
||||
runningDistanceSpeedCadence = (AggregatorRunning) sensorData;
|
||||
return;
|
||||
}
|
||||
if (type instanceof AggregatorBarometer) {
|
||||
barometer = (AggregatorBarometer) sensorData;
|
||||
return;
|
||||
}
|
||||
if (type instanceof AggregatorGPS) {
|
||||
gps = (AggregatorGPS) sensorData;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
|
||||
public interface SensorHandlerInterface {
|
||||
|
||||
List<ServiceMeasurementUUID> getServices();
|
||||
|
||||
Aggregator<?, ?> createEmptySensorData(String address, String name);
|
||||
|
||||
void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic);
|
||||
}
|
||||
@@ -10,6 +10,10 @@ 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);
|
||||
|
||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, current.first);
|
||||
|
||||
ALTITUDE_CORRECTION_MANAGER.correctAltitude(context, current.first);
|
||||
TrackPoint TrackPointWithEGM2008 = ALTITUDE_CORRECTION_MANAGER.correctAltitude(context, trackPoint);
|
||||
|
||||
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(), current.first, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
|
||||
return new RecordingData(trackBuilder.getTrack(), TrackPointWithEGM2008, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
|
||||
@@ -48,7 +48,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
|
||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||
|
||||
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
||||
@VisibleForTesting
|
||||
public 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;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
sensorManager.stop(context);
|
||||
sensorManager.stop();
|
||||
this.context = null;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
||||
}
|
||||
|
||||
public synchronized TrackPoint createSegmentStartManual() {
|
||||
return TrackPoint.createSegmentStartManualWithTime(createNow());
|
||||
return TrackPoint.createSegmentStartManualWithTime(getNow());
|
||||
}
|
||||
|
||||
public synchronized TrackPoint createSegmentEnd() {
|
||||
@@ -89,7 +89,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
||||
}
|
||||
|
||||
public Pair<TrackPoint, SensorDataSet> createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) {
|
||||
Instant now = createNow();
|
||||
Instant now = getNow();
|
||||
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
|
||||
|
||||
Position position = sensorDataSet.position().data();
|
||||
@@ -102,10 +102,10 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
||||
}
|
||||
|
||||
if (lastTrackPointUISpeed != null)
|
||||
position.with(lastTrackPointUISpeed.position().speed());
|
||||
position = position.with(lastTrackPointUISpeed.position().speed());
|
||||
|
||||
if (lastTrackPointUIAltitude != null)
|
||||
position.with(lastTrackPointUIAltitude.position().altitude());
|
||||
position = position.with(lastTrackPointUIAltitude.position().altitude());
|
||||
|
||||
TrackPoint currentTrackPoint = new TrackPoint(
|
||||
null,
|
||||
@@ -123,7 +123,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
||||
}
|
||||
|
||||
private TrackPoint createTrackPoint(TrackPoint.Type type) {
|
||||
Instant now = createNow();
|
||||
Instant now = getNow();
|
||||
|
||||
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
|
||||
|
||||
@@ -146,7 +146,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
||||
);
|
||||
}
|
||||
|
||||
public Instant createNow() {
|
||||
public Instant getNow() {
|
||||
return Instant.now(clock);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.sensors.SensorType;
|
||||
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||
@@ -253,10 +253,6 @@ public class PreferencesUtils {
|
||||
return isReportSpeed(track.activityTypeLocalized());
|
||||
}
|
||||
|
||||
private static String getBluetoothSensorAddressNone() {
|
||||
return SensorType.NONE.getPreferenceValue();
|
||||
}
|
||||
|
||||
public static SensorType getSensorType(String address) {
|
||||
return Arrays.stream(SensorType.values())
|
||||
.filter(it -> it.getPreferenceValue().equals(address))
|
||||
@@ -264,39 +260,11 @@ public class PreferencesUtils {
|
||||
.orElse(SensorType.REMOTE);
|
||||
}
|
||||
|
||||
public static String getBarometerSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_pressure_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static String getBluetoothHeartRateSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static String getBluetoothTemperatureSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_temperature_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static String getBluetoothCyclingCadenceSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static String getBluetoothCyclingSpeedSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static Distance getWheelCircumference() {
|
||||
final int DEFAULT = Integer.parseInt(resources.getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
|
||||
return Distance.ofMM(getInt(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT));
|
||||
}
|
||||
|
||||
public static String getBluetoothCyclingPowerSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static String getBluetoothRunningSpeedAndCadenceAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static boolean getBluetoothFilterEnabled() {
|
||||
final boolean DEFAULT = resources.getBoolean(R.bool.settings_sensor_bluetooth_service_filter_enabled_default);
|
||||
return getBoolean(R.string.settings_sensor_bluetooth_service_filter_enabled_key, DEFAULT);
|
||||
@@ -579,6 +547,7 @@ public class PreferencesUtils {
|
||||
return entries;
|
||||
}
|
||||
|
||||
//TODO Rename to getGpsMinSamplingInterval
|
||||
public static Duration getMinSamplingInterval() {
|
||||
final Duration MIN_SAMPLING_INTERVAL = getMinSamplingIntervalDefault();
|
||||
return Duration.ofSeconds(getInt(R.string.min_sampling_interval_key, (int) MIN_SAMPLING_INTERVAL.getSeconds()));
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||
|
||||
public class BluetoothLeCyclingCadencePreference extends BluetoothLeSensorPreference {
|
||||
|
||||
@@ -27,6 +27,6 @@ public class BluetoothLeCyclingCadencePreference extends BluetoothLeSensorPrefer
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerCyclingCadence.CYCLING_CADENCE);
|
||||
return BluetoothLeSensorPreferenceDialog.newInstance(getKey(), CyclingCadenceBluetooth.CYCLING_CADENCE);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||
|
||||
public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference {
|
||||
|
||||
@@ -29,6 +29,6 @@ public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreferen
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerCyclingPower.CYCLING_POWER));
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(CyclingPowerBluetooth.CYCLING_POWER));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||
|
||||
public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreference {
|
||||
|
||||
@@ -29,6 +29,6 @@ public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreferen
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE));
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE));
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.settings.bluetooth;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.LocationManager;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.core.location.LocationManagerCompat;
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||
|
||||
public class BluetoothLeGpsPreference extends BluetoothLeSensorPreference {
|
||||
|
||||
public BluetoothLeGpsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public BluetoothLeGpsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public BluetoothLeGpsPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public BluetoothLeGpsPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
boolean hasInternalSensor = LocationManagerCompat.hasProvider((LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE), GpsInternal.LOCATION_PROVIDER);
|
||||
|
||||
return BluetoothLeSensorPreferenceDialog
|
||||
.newInstance(getKey(), List.of(), hasInternalSensor);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+2
-2
@@ -5,7 +5,7 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.driver.HeartRateBluetooth;
|
||||
|
||||
public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference {
|
||||
|
||||
@@ -27,6 +27,6 @@ public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerHeartRate.HEART_RATE_SUPPORTING_DEVICES);
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), HeartRateBluetooth.HEART_RATE_SUPPORTING_DEVICES);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -9,7 +9,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerBarometricPressure;
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerBluetooth;
|
||||
|
||||
public class BluetoothLePressurePreference extends BluetoothLeSensorPreference {
|
||||
|
||||
@@ -36,6 +36,6 @@ public class BluetoothLePressurePreference extends BluetoothLeSensorPreference {
|
||||
boolean hasInternalSensor = pressureSensor != null;
|
||||
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog
|
||||
.newInstance(getKey(), List.of(BluetoothHandlerBarometricPressure.BAROMETRIC_PRESSURE), hasInternalSensor);
|
||||
.newInstance(getKey(), List.of(BarometerBluetooth.BAROMETRIC_PRESSURE), hasInternalSensor);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||
|
||||
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
|
||||
|
||||
@@ -29,6 +29,6 @@ public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSens
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE));
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(RunningSpeedAndCadenceBluetooth.RUNNING_SPEED_CADENCE));
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -10,6 +10,7 @@ import android.bluetooth.le.ScanResult;
|
||||
import android.bluetooth.le.ScanSettings;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelUuid;
|
||||
import android.text.TextUtils;
|
||||
@@ -18,6 +19,7 @@ import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.DialogPreference;
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
@@ -28,8 +30,8 @@ import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||
import de.dennisguse.opentracks.sensors.SensorType;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@@ -80,6 +82,12 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Object onGetDefaultValue(@NonNull TypedArray a, int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(Object defaultValue) {
|
||||
setValue(getPersistedString((String) defaultValue));
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerTemperature;
|
||||
import de.dennisguse.opentracks.sensors.driver.TemperatureBluetooth;
|
||||
|
||||
public class BluetoothLeTemperaturePreference extends BluetoothLeSensorPreference {
|
||||
|
||||
@@ -30,6 +30,6 @@ public class BluetoothLeTemperaturePreference extends BluetoothLeSensorPreferenc
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreferenceDialog
|
||||
.newInstance(getKey(), List.of(BluetoothHandlerTemperature.TEMPERATURE));
|
||||
.newInstance(getKey(), List.of(TemperatureBluetooth.TEMPERATURE));
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -5,6 +5,7 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -22,7 +23,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))
|
||||
.toList();
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
public static RecordingLayout fromCsvLine(@NonNull String csvLine, @NonNull Resources resources) {
|
||||
|
||||
@@ -18,7 +18,7 @@ limitations under the License.
|
||||
<string name="activity_type_airplane">αεροπλάνο</string>
|
||||
<string name="activity_type_atv">ATV</string>
|
||||
<string name="activity_type_biking">ποδηλασία</string>
|
||||
<string name="activity_type_blimp">μικρό αερόστατο</string>
|
||||
<string name="activity_type_blimp">αερόπλοιο</string>
|
||||
<string name="activity_type_boat">πλοίο</string>
|
||||
<string name="activity_type_climbing">αναρρίχηση</string>
|
||||
<string name="activity_type_commercial_airplane">εμπορικό αεροπλάνο</string>
|
||||
@@ -29,8 +29,8 @@ limitations under the License.
|
||||
<string name="activity_type_driving">οδήγηση</string>
|
||||
<string name="activity_type_driving_bus">οδήγηση λεωφορείου</string>
|
||||
<string name="activity_type_driving_car">οδήγηση αυτοκινήτου</string>
|
||||
<string name="activity_type_ferry">Φέρι μποτ</string>
|
||||
<string name="activity_type_frisbee">φρισμπι</string>
|
||||
<string name="activity_type_ferry">φέρι μποτ</string>
|
||||
<string name="activity_type_frisbee">φρίσμπι</string>
|
||||
<string name="activity_type_gliding">ανεμόπτερο</string>
|
||||
<string name="activity_type_hang_gliding">αιωροπτερισμός</string>
|
||||
<string name="activity_type_helicopter">ελικόπτερο</string>
|
||||
@@ -44,15 +44,15 @@ limitations under the License.
|
||||
<string name="activity_type_land_sailing">ιστιοπλοΐα γης</string>
|
||||
<string name="activity_type_mixed_type">μικτού τύπου</string>
|
||||
<string name="activity_type_motor_bike">μηχανάκι</string>
|
||||
<string name="activity_type_motor_boating">Μηχανοκίνητα σκάφη</string>
|
||||
<string name="activity_type_mountain_biking">Ποδηλασία στο βουνό</string>
|
||||
<string name="activity_type_motor_boating">μηχανοκίνητο σκάφος</string>
|
||||
<string name="activity_type_mountain_biking">ποδηλασία στο βουνό</string>
|
||||
<string name="activity_type_off_trail_hiking">πεζοπορία σε ανώμαλο έδαφος</string>
|
||||
<string name="activity_type_other">Άλλη</string>
|
||||
<string name="activity_type_other">άλλη</string>
|
||||
<string name="activity_type_paddling">κωπηλασία</string>
|
||||
<string name="activity_type_para_gliding">παραπέντε</string>
|
||||
<string name="activity_type_rc_airplane">τηλεχειριζόμενο αεροπλάνο</string>
|
||||
<string name="activity_type_rc_boat">Τηλεχειριζόμενη βάρκα</string>
|
||||
<string name="activity_type_rc_helicopter">Τηλεχειριζόμενο ελικόπτερο</string>
|
||||
<string name="activity_type_rc_boat">τηλεχειριζόμενη βάρκα</string>
|
||||
<string name="activity_type_rc_helicopter">τηλεχειριζόμενο ελικόπτερο</string>
|
||||
<string name="activity_type_riding">ιππασία</string>
|
||||
<string name="activity_type_road_biking">ποδηλασία</string>
|
||||
<string name="activity_type_roller_skiing">ρόλερ σκι ρολό</string>
|
||||
@@ -302,4 +302,27 @@ limitations under the License.
|
||||
<string name="waypoint_type_waterfall">καταρράκτης</string>
|
||||
<string name="waypoint_type_water_fountain">συντριβάνι</string>
|
||||
<string name="waypoint_type_waypoint">σημείο διαδρομής</string>
|
||||
</resources>
|
||||
<string name="about_preference_title">Σχετικά</string>
|
||||
<string name="about_url">Ιστότοπους εγχειρήματος: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Όνομα έκδοσης: %1$s</string>
|
||||
<string name="about_version_code">Κωδικός έκδοσης: %1$d</string>
|
||||
<string name="about_third_party_data">Δεδομένα από τρίτους</string>
|
||||
<string name="about_libraries">Βιβλιοθήκες</string>
|
||||
<string name="help_feature_title">Το OpenTracks έχει τα ακόλουθα χαρακτηριστικά:</string>
|
||||
<string name="help_data_google_play_services_title">Το OpenTracks χρειάζεται/χρησιμοποιεί τις Υπηρεσίες Google Play;</string>
|
||||
<string name="help_data_google_play_services_content">Όχι</string>
|
||||
<string name="help_recording_title">Πώς το OpenTracks καταγράφει τα δεδομένα;</string>
|
||||
<string name="help_track_map_title">Πώς να εμφανιστεί ένα ίχνος στον χάρτη;</string>
|
||||
<string name="help_gps_troubleshooting_title">Το OpenTracks δεν καταγράφει τοποθεσίες;</string>
|
||||
<string name="help_support_title">Πώς να υποστηρίξω το OpenTracks;</string>
|
||||
<string name="open_track_as_trackfileformat">Ανοίγμα ως %1$s</string>
|
||||
<string name="activity_type_escooter">ηλεκτρικό πατίνι</string>
|
||||
<string name="activity_type_kickscooter">πατίνι</string>
|
||||
<string name="power_x_watt">Ισχύς {x, plural, =1 {1 watt} other {# watts} }</string>
|
||||
<string name="power">Ισχύς</string>
|
||||
<string name="report_bug">Αναφορά προβλήματος</string>
|
||||
<string name="always">Πάντα</string>
|
||||
<string name="settings_recording_min_sampling_interval_title">Χρονικό μεσοδιάστημα δειγματοληψίας</string>
|
||||
<string name="settings_recording_location_distance_summary">%1$s μεταξύ καταγεγραμμένων τοποθεσιών</string>
|
||||
<string name="field_remove_from_layout">Αφαίρεση</string>
|
||||
</resources>
|
||||
|
||||
@@ -304,4 +304,13 @@ limitations under the License.
|
||||
<string name="waypoint_type_waypoint">地点</string>
|
||||
<string name="help_feature_content">"* GPS トラックを作成します\n\n\n\n * 心拍センサーを使用します (Bluetooth LE のみ)\n\n\n\n * 音声アナウンス(時間または距離)\n\n\n\n * マーカーの作成 (写真を含む)\n\n\n\n * 完全にオフラインで動作します\n\n\n\n * 地図上にトラックを表示します (下記を参照)"</string>
|
||||
<string name="help_data_google_play_services_content">いいえ</string>
|
||||
</resources>
|
||||
<string name="about_history">OpenTracksは<a href="https://code.google.com/archive/p/mytracks/"><i>GoogleのMyTracks</i></a> (2016に開発停止)のフォークです。</string>
|
||||
<string name="help_feature_title">OpenTracks には以下の機能があります:</string>
|
||||
<string name="activity_type_escooter">電動スクーター</string>
|
||||
<string name="activity_type_kickscooter">スクーター</string>
|
||||
<string name="generic_overwrite">上書き</string>
|
||||
<string name="generic_skip">スキップ</string>
|
||||
<string name="import_progress_summary_ok_msg">インポート完了</string>
|
||||
<string name="import_progress_summary_exists_msg">既に存在しています</string>
|
||||
<string name="help_track_map_content">小さな地図のアイコンを使います.\n a) (live updates) <a href="https://codeberg.org/OpenTracksApp/OSMDashboard">OSMDashboard</a>をインストールして下さい。 <a href="https://www.openstreetmap.org">OpenStreetMap</a> を利用したオンラインまたはオフラインの地図を使用できるようになります。\n実際、OSMDashbordは (一時的に保存された) 表示するトラッキングデータにだけアクセスする独立したアプリケーションです。\n b) 一般的な地図アプリケーションを使用 (e.g., <a href="http://osmand.net/">OsmAnd</a> and <a href="https://maps.me/">MAPS.ME</a>) 。 KMZフォーマットをサポートしている必要があります (位置及びタイムスタンプのみを含む)。 加えて、 MIME <i>application/vnd.google-earth.kmz</i> の <i>ACTION_VIEW</i> をサポートしている必要もあります。</string>
|
||||
</resources>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<string name="settings_sensor_bluetooth_cycling_power_key" translatable="false">bluetoothCyclingPowerSensor</string>
|
||||
<string name="settings_sensor_bluetooth_running_speed_and_cadence_key" translatable="false">bluetoothRunningSpeedAndCadenceSensor</string>
|
||||
<string name="settings_sensor_bluetooth_pressure_key" translatable="false">bluetoothPressureSensor</string>
|
||||
<string name="settings_sensor_bluetooth_gps_key" translatable="false">bluetoothGPSSensor</string>
|
||||
<string name="sensor_type_value_none" translatable="false">NONE</string>
|
||||
<string name="sensor_type_value_internal" translatable="false">INTERNAL</string>
|
||||
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
android:title="@string/settings_sensors_title">
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_gps_sensor">
|
||||
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLeGpsPreference
|
||||
android:defaultValue="@string/sensor_type_value_internal"
|
||||
android:key="@string/settings_sensor_bluetooth_gps_key"
|
||||
android:title="@string/settings_gps_sensor" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/min_sampling_interval_default"
|
||||
android:entryValues="@array/min_sampling_interval_values"
|
||||
android:key="@string/min_sampling_interval_key"
|
||||
android:title="@string/settings_recording_min_sampling_interval_title" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/recording_gps_accuracy_default"
|
||||
android:entryValues="@array/recording_gps_accuracy_values"
|
||||
@@ -71,9 +74,9 @@
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/settings_sensor_bluetooth_service_filter_enabled_default"
|
||||
android:key="@string/settings_sensor_bluetooth_service_filter_enabled_key"
|
||||
android:title="@string/settings_sensor_bluetooth_service_filter_title"
|
||||
android:summaryOff="@string/settings_sensor_bluetooth_service_filter_off"
|
||||
android:summaryOn="@string/settings_sensor_bluetooth_service_filter_on"
|
||||
android:summaryOff="@string/settings_sensor_bluetooth_service_filter_off" />
|
||||
android:title="@string/settings_sensor_bluetooth_service_filter_title" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user