mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b30ecabb1 | |||
| 107cc14a2d | |||
| 6d3d8bb83c | |||
| 9c8e0335ef | |||
| 8c648c69e8 | |||
| 4422464f8d | |||
| d094531779 | |||
| a01341d235 | |||
| 83065dad39 | |||
| 68ba3e0c2a | |||
| 3f21edc44c | |||
| aefd1a6a35 | |||
| 74fa73e4a4 | |||
| 0a006d64f9 | |||
| 997ab6060e | |||
| 6d926ca7a8 | |||
| 3c1f1a9ba5 | |||
| ed5da55fba | |||
| d1e9649505 | |||
| 2431d60171 | |||
| e08f5e7273 | |||
| 7d2a6fff59 | |||
| a5158a55d9 | |||
| 15fbd90ca4 | |||
| eebc33f60c | |||
| ba2a755fb7 | |||
| 344c50a20c | |||
| 9a9a610321 | |||
| 8af427ea48 | |||
| 7b7d7c8061 | |||
| 7d6919d3f9 | |||
| 7eceb979c3 | |||
| bd48944ad5 | |||
| 503cba1315 | |||
| a313cb6244 | |||
| 7f0c6309c0 | |||
| 02c578327d | |||
| 7fec0dcff4 | |||
| 34eb7a833a | |||
| 5db3aa7ac1 | |||
| 47fd7c51a9 | |||
| 2f6f0b4e17 | |||
| 41b794f66b | |||
| e2c592367a | |||
| 7a24c56474 | |||
| 3f6bd6926a | |||
| 508af45449 | |||
| 0f89cd7875 | |||
| 29b522b8a2 | |||
| 14e63a1a54 | |||
| e559583d23 | |||
| 3b9711d82e | |||
| 114fc8255f | |||
| 26e655d58f | |||
| 815b6a3cf1 | |||
| 3dc8a724d8 | |||
| 70f571cd09 | |||
| 3f5c3a5c08 | |||
| a13465391f | |||
| 97454b5369 | |||
| f6a47c4594 | |||
| fce76230e1 | |||
| fdf4336a9b | |||
| 844c6e9609 | |||
| 784942ffd2 | |||
| edf4ccc941 | |||
| d90258294f | |||
| 01f4c13307 | |||
| 873956426d | |||
| 4128c54960 | |||
| 9978bddf21 | |||
| a9726b9a94 | |||
| 05ba52bab5 | |||
| ca95f40e77 | |||
| 5cb85c4383 | |||
| d0a477b112 | |||
| ec365a18ca | |||
| 0ff735e559 | |||
| 8a35835791 | |||
| e3569dab56 | |||
| 4040e3f45b | |||
| eac7efba64 | |||
| 21a3971a56 | |||
| 4e4a9abf6f | |||
| a9624f72e7 | |||
| bb38d274fb |
@@ -18,4 +18,6 @@ jobs:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
arch: x86_64
|
||||
profile: Nexus 6
|
||||
script: ./gradlew connectedCheck
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ However, often only one value is provided.
|
||||
### Speed
|
||||
|
||||
* Garmin Speed Sensor 2
|
||||
* Has updatable firmware that requires an account for garmin.com
|
||||
* This sensor reports speed data as cadence.
|
||||
A workaround is in place.
|
||||
* This sensor sometimes counts backwards (unknown software version).
|
||||
A workaround is in place.
|
||||
|
||||
* Wahoo Speed (Model: WFRPMSPD)
|
||||
|
||||
### Cadence
|
||||
|
||||
+6
-6
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.2'
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 3855
|
||||
versionName "v3.15.0"
|
||||
versionCode 3941
|
||||
versionName "v3.16.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -86,8 +86,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha04'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha05'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
@@ -99,5 +99,5 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.7.7'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.8.0'
|
||||
}
|
||||
|
||||
@@ -2,17 +2,7 @@
|
||||
interface TrackExporter {
|
||||
}
|
||||
class KmzTrackExporter implements TrackExporter
|
||||
KmzTrackExporter o-- FileTrackExporter
|
||||
class FileTrackExporter implements TrackExporter {
|
||||
/' One track only '/
|
||||
}
|
||||
FileTrackExporter o-- TrackWriter
|
||||
|
||||
interface TrackWriter {
|
||||
}
|
||||
class GpxTrackWriter implements TrackWriter
|
||||
class KmlTrackWriter implements TrackWriter
|
||||
|
||||
class KmlTrackExporter implements TrackExporter
|
||||
|
||||
interface TrackImporter {
|
||||
}
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
<?xml version="1.0"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
|
||||
|
||||
<xs:element name="trackuuid"
|
||||
type="xs:string" />
|
||||
<xsd:element name="trackuuid"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
A unique track identifier stored as UUID.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
|
||||
</xs:schema>
|
||||
<xsd:element name="loss"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The lost elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="gain"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The gained elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.15.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- After recording show sensor statistics (e.g., avg HR)
|
||||
|
||||
Bugfix:
|
||||
- Crash on recording start with show latitude/longitude enabled
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.15.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Updated translations
|
||||
|
||||
Bugfix:
|
||||
- Crash on GPX export
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.15.3: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Computation avg/max: heart rate, cadence, and power
|
||||
|
||||
Bugfix:
|
||||
- KML/KMZ do not export #start/#end placemarks anymore
|
||||
- While recording: Speed/location was sometimes falsly shown as 0.
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.15.4: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Voice annoucements interval should be in minutes (introduced in v3.15.0)
|
||||
@@ -0,0 +1,13 @@
|
||||
v3.16.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Cycling: get distance from BLE speed sensor
|
||||
- Reworked UI presented while recording
|
||||
- UI performance optimizations
|
||||
- Exported filenames: <First 32bits UUID>_<track's name>.<extension>
|
||||
- Import multiple files via Intent (e.g., from Gadgetbridge)
|
||||
- GPX: export power data
|
||||
- Updated translations
|
||||
|
||||
Bugfix:
|
||||
- Voice announcement use minutes correctly
|
||||
@@ -11,6 +11,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class TestDataUtil {
|
||||
@@ -150,4 +151,13 @@ public class TestDataUtil {
|
||||
|
||||
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, 0.0, 0, trackPoint, photoUrl);
|
||||
}
|
||||
|
||||
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null);
|
||||
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||
while (trackPointIterator.hasNext()) {
|
||||
trackPoints.add(trackPointIterator.next());
|
||||
}
|
||||
return trackPoints;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TestSensorDataUtil {
|
||||
|
||||
private final List<TrackPoint> trackPointList = new ArrayList<>();
|
||||
private final List<SensorData> sensorDataList = new ArrayList<>();
|
||||
|
||||
public void add(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
|
||||
sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type));
|
||||
TrackPoint tp = new TrackPoint(type);
|
||||
int i = trackPointList.size() + 1;
|
||||
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
tp.setHeartRate_bpm(hr);
|
||||
tp.setCyclingCadence_rpm(cadence);
|
||||
tp.setPower(power);
|
||||
tp.setAccuracy(1f);
|
||||
tp.setAltitude(1f);
|
||||
tp.setTime(time);
|
||||
tp.setSpeed(5f + (i / 10f));
|
||||
tp.setElevationGain(3f);
|
||||
tp.setElevationLoss(3f);
|
||||
trackPointList.add(tp);
|
||||
}
|
||||
|
||||
public List<TrackPoint> getTrackPointList() {
|
||||
return this.trackPointList;
|
||||
}
|
||||
|
||||
public SensorDataStats computeStats() {
|
||||
if (sensorDataList.size() <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SensorDataStats stats = new SensorDataStats();
|
||||
long timeElapsed;
|
||||
long movingTime = 0;
|
||||
SensorData dataPrev = sensorDataList.get(0);
|
||||
stats.maxHr = dataPrev.hr;
|
||||
stats.maxCadence = dataPrev.cadence;
|
||||
SensorData dataCurrent;
|
||||
for (int i = 1; i < sensorDataList.size(); i++) {
|
||||
dataCurrent = sensorDataList.get(i);
|
||||
if (dataPrev.type != TrackPoint.Type.SEGMENT_START_MANUAL) {
|
||||
timeElapsed = dataCurrent.type != TrackPoint.Type.SEGMENT_START_MANUAL ? dataCurrent.time.getEpochSecond() - dataPrev.time.getEpochSecond() : 0;
|
||||
stats.avgHr += (dataPrev.hr * timeElapsed);
|
||||
stats.maxHr = Math.max(dataPrev.hr, stats.maxHr);
|
||||
stats.avgCadence += (dataPrev.cadence * timeElapsed);
|
||||
stats.maxCadence = Math.max(dataPrev.cadence, stats.maxCadence);
|
||||
stats.avgPower += (dataPrev.power * timeElapsed);
|
||||
|
||||
movingTime += timeElapsed;
|
||||
}
|
||||
dataPrev = dataCurrent;
|
||||
}
|
||||
|
||||
stats.avgHr /= movingTime;
|
||||
stats.avgCadence /= movingTime;
|
||||
stats.avgPower /= movingTime;
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private static class SensorData {
|
||||
final Instant time;
|
||||
final float hr;
|
||||
final float cadence;
|
||||
final float power;
|
||||
final TrackPoint.Type type;
|
||||
|
||||
public SensorData(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
|
||||
this.time = time;
|
||||
this.hr = hr == null ? 0 : hr;
|
||||
this.cadence = cadence == null ? 0 : cadence;
|
||||
this.power = power == null ? 0 : power;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorDataStats {
|
||||
public float avgHr = 0f;
|
||||
public float maxHr = 0f;
|
||||
public float avgCadence = 0f;
|
||||
public float maxCadence = 0f;
|
||||
public float avgPower = 0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TrackPointTest {
|
||||
|
||||
@Test
|
||||
public void isRecent_true() {
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
tp.setTime(Instant.now());
|
||||
|
||||
assertTrue(tp.isRecent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isRecent_false() {
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
tp.setTime(Instant.now().minus(2, ChronoUnit.MINUTES));
|
||||
|
||||
assertFalse(tp.isRecent());
|
||||
}
|
||||
}
|
||||
+354
-24
@@ -35,18 +35,22 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.TestSensorDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
@@ -90,29 +94,22 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noPoints() {
|
||||
testIterator(new Track.Id(1), 0, 1);
|
||||
testIterator(new Track.Id(1), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchAscending() {
|
||||
testIterator(new Track.Id(1), 50, 100);
|
||||
testIterator(new Track.Id(2), 50, 50);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchAscending() {
|
||||
testIterator(new Track.Id(1), 50, 11);
|
||||
testIterator(new Track.Id(2), 50, 25);
|
||||
public void testLocationIterator_noAscending() {
|
||||
testIterator(new Track.Id(1), 50);
|
||||
testIterator(new Track.Id(2), 50);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
testIterator(new Track.Id(1), 20000, 2000);
|
||||
testIterator(new Track.Id(1), 20000);
|
||||
}
|
||||
|
||||
private void testIterator(Track.Id trackId, int numPoints, int batchSize) {
|
||||
long lastPointId = initializeTrack(trackId, numPoints);
|
||||
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
||||
private void testIterator(Track.Id trackId, int numPoints) {
|
||||
TrackPoint.Id lastPointId = initializeTrack(trackId, numPoints);
|
||||
List<TrackPoint> locations = new ArrayList<>(numPoints);
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||
while (it.hasNext()) {
|
||||
@@ -120,13 +117,13 @@ public class CustomContentProviderUtilsTest {
|
||||
assertNotNull(trackPoint);
|
||||
locations.add(trackPoint);
|
||||
// Make sure the IDs are returned in the right order.
|
||||
assertEquals(lastPointId - numPoints + locations.size(), trackPoint.getId().getId());
|
||||
assertEquals(lastPointId.getId() - numPoints + locations.size(), trackPoint.getId().getId());
|
||||
}
|
||||
assertEquals(numPoints, locations.size());
|
||||
}
|
||||
}
|
||||
|
||||
private long initializeTrack(Track.Id id, int numPoints) {
|
||||
private TrackPoint.Id initializeTrack(Track.Id id, int numPoints) {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id.getId());
|
||||
@@ -146,17 +143,17 @@ public class CustomContentProviderUtilsTest {
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
|
||||
|
||||
// Load all inserted trackPoints.
|
||||
long lastPointId = -1;
|
||||
TrackPoint.Id lastPointId = null;
|
||||
int counter = 0;
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) {
|
||||
while (it.hasNext()) {
|
||||
TrackPoint trackPoint = it.next();
|
||||
lastPointId = trackPoint.getId().getId();
|
||||
lastPointId = trackPoint.getId();
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(numPoints == 0 || lastPointId > 0);
|
||||
assertTrue(numPoints == 0 || lastPointId.getId() > 0);
|
||||
assertEquals(numPoints, counter);
|
||||
|
||||
return lastPointId;
|
||||
@@ -766,9 +763,9 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
// when / then
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
|
||||
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
|
||||
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId);
|
||||
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
|
||||
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -825,7 +822,7 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
|
||||
assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -843,7 +840,7 @@ public class CustomContentProviderUtilsTest {
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertTrue(trackPoints.get(10).hasHeartRate());
|
||||
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
|
||||
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
|
||||
@@ -875,7 +872,7 @@ public class CustomContentProviderUtilsTest {
|
||||
.map(TrackPoint.Id::new).collect(Collectors.toList());
|
||||
|
||||
// when
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8), 5);
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8));
|
||||
|
||||
// then
|
||||
assertEquals(2, cursor.getCount());
|
||||
@@ -927,4 +924,337 @@ public class CustomContentProviderUtilsTest {
|
||||
assertEquals("12", ContentProviderUtils.formatIdListForUri(new Track.Id(12)));
|
||||
assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new Track.Id(42), new Track.Id(43), new Track.Id(44)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_noSensorData() {
|
||||
// given
|
||||
List<TrackPoint> trackPointList = new ArrayList<>();
|
||||
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
|
||||
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
|
||||
trackPoint.setPower(null);
|
||||
trackPoint.setCyclingCadence_rpm(null);
|
||||
trackPoint.setHeartRate_bpm(null);
|
||||
trackPointList.add(trackPoint);
|
||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, trackPointList);
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_needAtLeastTwoTrackPointsFalse() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 90 300 0
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 90f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_needAtLeastTwoTrackPointsTrue() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 90 300 -1
|
||||
* 1 140 90 300 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 90f, 300f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), 140f, 90f, 300f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertTrue(sensorStatistics.hasHeartRate());
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertTrue(sensorStatistics.hasCadence());
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertTrue(sensorStatistics.hasPower());
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_onlyHr() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 NULL NULL -1
|
||||
* 1 140 NULL NULL 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, null, null, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), 140f, null, null, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertTrue(sensorStatistics.hasHeartRate());
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_onlyCadence() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 NULL 90 NULL -1
|
||||
* 1 NULL 90 NULL 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, null, 90f, null, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), null, 90f, null, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertTrue(sensorStatistics.hasCadence());
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_onlyPower() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 NULL NULL 300 -1
|
||||
* 1 NULL NULL 300 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, null, null, 300f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), null, null, 300f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertTrue(sensorStatistics.hasPower());
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 75 250 -1
|
||||
* 2 148 80 300 0
|
||||
* 1 150 82 325 0
|
||||
* 7 160 90 275 0
|
||||
* 4 155 85 280 0
|
||||
* 1 155 84 295 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 75f, 250f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(2, ChronoUnit.SECONDS), 148f, 80f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(3, ChronoUnit.SECONDS), 150f, 82f, 325f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(10, ChronoUnit.SECONDS), 160f, 90f, 275f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(14, ChronoUnit.SECONDS), 155f, 85f, 280f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(15, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_withManualResume() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 75 250 -1
|
||||
* 2 148 80 300 0
|
||||
* 1 150 82 325 0
|
||||
* 3 174 88 400 0
|
||||
* 20 127 54 175 -2
|
||||
* 3 160 90 275 0
|
||||
* 7 155 85 280 0
|
||||
* 3 150 90 267 0
|
||||
* 3 170 90 240 0
|
||||
* 2 155 84 295 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 75f, 250f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(2, ChronoUnit.SECONDS), 148f, 80f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(3, ChronoUnit.SECONDS), 150f, 82f, 325f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(6, ChronoUnit.SECONDS), 174f, 88f, 400f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(26, ChronoUnit.SECONDS), 127f, 54f, 175f, TrackPoint.Type.SEGMENT_START_MANUAL);
|
||||
sensorDataUtil.add(start.plus(29, ChronoUnit.SECONDS), 160f, 90f, 275f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(36, ChronoUnit.SECONDS), 155f, 85f, 280f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(39, ChronoUnit.SECONDS), 150f, 90f, 267f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(42, ChronoUnit.SECONDS), 170f, 90f, 240f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(44, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_withStartAutomatic() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 75 250 -1
|
||||
* 2 148 80 300 0
|
||||
* 1 150 82 325 0
|
||||
* 3 174 88 400 0
|
||||
* 20 127 54 175 -1
|
||||
* 3 160 90 275 0
|
||||
* 7 155 85 280 0
|
||||
* 3 150 90 267 0
|
||||
* 3 170 90 240 0
|
||||
* 2 155 84 295 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 75f, 250f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(2, ChronoUnit.SECONDS), 148f, 80f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(3, ChronoUnit.SECONDS), 150f, 82f, 325f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(6, ChronoUnit.SECONDS), 174f, 88f, 400f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(26, ChronoUnit.SECONDS), 127f, 54f, 175f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(29, ChronoUnit.SECONDS), 160f, 90f, 275f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(36, ChronoUnit.SECONDS), 155f, 85f, 280f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(39, ChronoUnit.SECONDS), 150f, 90f, 267f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(42, ChronoUnit.SECONDS), 170f, 90f, 240f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(44, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
private void testGetSensorStats_randomData(int totalPoints, boolean withStartSegments) {
|
||||
// given
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < totalPoints; i++) {
|
||||
int randomNum = withStartSegments ? random.nextInt(50) - 2 : 0;
|
||||
TrackPoint.Type type = randomNum >= 0 ? TrackPoint.Type.TRACKPOINT : TrackPoint.Type.getById(randomNum);
|
||||
float randomHr = random.nextFloat() * (200f - 90f) + 90f;
|
||||
float randomCadence = random.nextFloat() * (110f - 40f) + 40f;
|
||||
float randomPower = random.nextFloat() * (500f - 100f) + 100f;
|
||||
sensorDataUtil.add(start.plus(i, ChronoUnit.SECONDS), randomHr, randomCadence, randomPower, type);
|
||||
}
|
||||
sensorDataUtil.add(start.plus(totalPoints, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0.01f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0.01f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0.01f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0.01f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_veryLongActivity12h() {
|
||||
testGetSensorStats_randomData(43200, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_withSeveralRandomStartSegments() {
|
||||
testGetSensorStats_randomData(5000, true);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-14
@@ -24,7 +24,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(60, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,7 +37,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(33.53, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(33.53, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,7 +50,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(0, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(0, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertFalse(current.hasCadence_rpm());
|
||||
assertFalse(current.hasValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,7 +77,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(60, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,40 +90,42 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(60, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2150);
|
||||
|
||||
// then
|
||||
assertEquals(1.20, current.getSpeed_mps(), 0.01);
|
||||
assertEquals(1.20, current.getValue().distance_m, 2150);
|
||||
assertEquals(1.20, current.getValue().speed_mps, 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed_rollOverCount() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2000);
|
||||
|
||||
// then
|
||||
assertEquals(2, current.getSpeed_mps(), 0.01);
|
||||
assertEquals(1.20, current.getValue().distance_m, 2000);
|
||||
assertEquals(2, current.getValue().speed_mps, 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equals_speed_with_no_data() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress");
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
previous.toString();
|
||||
|
||||
+41
-71
@@ -2,7 +2,7 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.junit.runners.JUnit4;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -31,6 +32,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -69,6 +71,7 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
@@ -89,17 +92,17 @@ public class ExportImportTest {
|
||||
|
||||
trackId = service.startNewTrack();
|
||||
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
service.resumeCurrentTrack();
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.endCurrentTrack();
|
||||
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -109,7 +112,7 @@ public class ExportImportTest {
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
markers = contentProviderUtils.getMarkers(trackId);
|
||||
assertEquals(10, trackPoints.size());
|
||||
assertEquals(2, markers.size());
|
||||
@@ -148,7 +151,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -161,10 +164,10 @@ public class ExportImportTest {
|
||||
assertEquals(track.getUuid(), importedTrack.getUuid());
|
||||
|
||||
// 2. trackpoints
|
||||
assertTrackpoints(trackPoints, false, false, false, false, false);
|
||||
assertTrackpoints(trackPoints, false, false, false, false, false, false);
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(false, false);
|
||||
assertTrackStatistics(false, false, false);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -187,7 +190,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -199,10 +202,10 @@ public class ExportImportTest {
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. trackpoints
|
||||
assertTrackpoints(trackPoints, true, true, true, true, true);
|
||||
assertTrackpoints(trackPoints, true, true, true, true, true, true);
|
||||
|
||||
// 2. trackstatistics
|
||||
assertTrackStatistics(false, true);
|
||||
assertTrackStatistics(false, true, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -212,7 +215,7 @@ public class ExportImportTest {
|
||||
@Test(expected = ImportAlreadyExistsException.class)
|
||||
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
|
||||
// given
|
||||
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||
@@ -225,45 +228,13 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNull(importedTrack);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_only_track() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata_and_pictures() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx() {
|
||||
@@ -281,7 +252,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -301,10 +272,10 @@ public class ExportImportTest {
|
||||
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
|
||||
assertTrackpoints(trackPointsWithCoordinates, false, true, true, true, true);
|
||||
assertTrackpoints(trackPointsWithCoordinates, true, true, true, true, true, false);
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(true, true);
|
||||
assertTrackStatistics(true, true, false);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -314,7 +285,7 @@ public class ExportImportTest {
|
||||
@Test(expected = ImportAlreadyExistsException.class)
|
||||
public void gpx_duplicate_trackUUID() {
|
||||
// given
|
||||
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
@@ -327,7 +298,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -354,9 +325,8 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss) {
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
|
||||
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss, boolean verifyDistance) {
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
||||
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
@@ -391,13 +361,13 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
if (verifyHeartrate) {
|
||||
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
assertEquals("" + trackPoint, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
}
|
||||
if (verifyCadence) {
|
||||
assertEquals(trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
assertEquals("" + trackPoint, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
}
|
||||
if (verifyPower) {
|
||||
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
assertEquals("" + trackPoint, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
}
|
||||
if (verifyElevationGain) {
|
||||
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
|
||||
@@ -405,10 +375,13 @@ public class ExportImportTest {
|
||||
if (verifyElevationLoss) {
|
||||
assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01);
|
||||
}
|
||||
if (verifyDistance) {
|
||||
assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance(), 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss) {
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss, boolean verifyDistance) {
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
|
||||
assertNotNull(importedTrack.getTrackStatistics());
|
||||
@@ -426,7 +399,9 @@ public class ExportImportTest {
|
||||
assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
|
||||
|
||||
// Distance
|
||||
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
|
||||
if (verifyDistance) {
|
||||
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
// Speed
|
||||
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed(), 0.01);
|
||||
@@ -446,20 +421,15 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, long speed, long altitude, float elevationGain, float heartRate, float cyclingCadence, float power) {
|
||||
Location location = new Location("");
|
||||
location.setTime(time);
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
location.setAccuracy(accuracy);
|
||||
location.setAltitude(altitude);
|
||||
location.setSpeed(speed);
|
||||
|
||||
TrackPoint tp = new TrackPoint(location);
|
||||
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float elevationGain, float heartRate, float cyclingCadence, float power, float distance) {
|
||||
TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time));
|
||||
tp.setAccuracy(accuracy);
|
||||
tp.setSpeed(speed);
|
||||
tp.setHeartRate_bpm(heartRate);
|
||||
tp.setCyclingCadence_rpm(cyclingCadence);
|
||||
tp.setPower(power);
|
||||
tp.setElevationGain(elevationGain);
|
||||
tp.setSensorDistance(distance);
|
||||
return tp;
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -14,6 +14,7 @@ import org.junit.runners.JUnit4;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -54,7 +55,7 @@ public class LegacyImportTest {
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -69,7 +70,7 @@ public class LegacyImportTest {
|
||||
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(6, importedTrackPoints.size());
|
||||
|
||||
// first 3 trackpoints
|
||||
@@ -95,7 +96,7 @@ public class LegacyImportTest {
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
|
||||
|
||||
// when
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +111,7 @@ public class LegacyImportTest {
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -122,7 +123,7 @@ public class LegacyImportTest {
|
||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(6, importedTrackPoints.size());
|
||||
|
||||
// first segment
|
||||
|
||||
+13
-7
@@ -12,6 +12,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -25,7 +26,10 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private TrackPoint locationMock;
|
||||
private TrackPoint trackPointMock;
|
||||
|
||||
@Mock
|
||||
private TrackStatistics trackStatisticsMock;
|
||||
|
||||
@Mock
|
||||
private NotificationCompat.Builder notificationCompatBuilder;
|
||||
@@ -35,18 +39,20 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
|
||||
@Test
|
||||
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||
when(locationMock.hasAccuracy()).thenReturn(true);
|
||||
when(locationMock.getAccuracy()).thenReturn(999f);
|
||||
when(trackPointMock.hasAccuracy()).thenReturn(true);
|
||||
when(trackPointMock.getAccuracy()).thenReturn(999f);
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(0d);
|
||||
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
|
||||
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
|
||||
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
subject.setMetricUnits(true);
|
||||
|
||||
// when
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 1000);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 1000);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
|
||||
// then
|
||||
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||
|
||||
+16
-16
@@ -45,6 +45,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -84,6 +85,7 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
|
||||
@@ -102,7 +104,6 @@ public class TrackRecordingServiceTest {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
@@ -190,7 +191,7 @@ public class TrackRecordingServiceTest {
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
|
||||
assertEquals(1, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
@@ -207,7 +208,7 @@ public class TrackRecordingServiceTest {
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
@@ -225,7 +226,7 @@ public class TrackRecordingServiceTest {
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
// then
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
//when
|
||||
service.resumeTrack(trackId);
|
||||
@@ -234,7 +235,7 @@ public class TrackRecordingServiceTest {
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(3, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
@@ -250,7 +251,7 @@ public class TrackRecordingServiceTest {
|
||||
assertTrue(service.isRecording());
|
||||
service.endCurrentTrack();
|
||||
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
@@ -260,7 +261,7 @@ public class TrackRecordingServiceTest {
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(4, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
@@ -277,7 +278,7 @@ public class TrackRecordingServiceTest {
|
||||
assertTrue(service.isRecording());
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
@@ -286,7 +287,7 @@ public class TrackRecordingServiceTest {
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
@@ -302,7 +303,7 @@ public class TrackRecordingServiceTest {
|
||||
// when
|
||||
// Just set recording track to a bogus value.
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, 123L);
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
@@ -325,7 +326,7 @@ public class TrackRecordingServiceTest {
|
||||
// then
|
||||
assertNull(newTrackId);
|
||||
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@@ -341,7 +342,7 @@ public class TrackRecordingServiceTest {
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(PreferencesUtils.isRecording(context));
|
||||
assertFalse(PreferencesUtils.isRecording(sharedPreferences, context));
|
||||
assertNull(service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@@ -388,8 +389,8 @@ public class TrackRecordingServiceTest {
|
||||
assertNotNull(track.getId());
|
||||
contentProviderUtils.insertTrack(track);
|
||||
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
||||
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, !isRecording);
|
||||
}
|
||||
|
||||
// NOTE: Do not use to create a track that is currently recording.
|
||||
@@ -422,7 +423,6 @@ public class TrackRecordingServiceTest {
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
|
||||
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
|
||||
trackRecordingService.newTrackPoint(trackPoint, 50);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -104,7 +105,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(8, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -138,7 +139,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(4, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -185,7 +186,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
@@ -214,7 +215,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(6, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -260,7 +261,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(8, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -292,7 +293,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(7, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
|
||||
+16
-16
@@ -53,6 +53,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@@ -77,7 +78,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().apply();
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
@@ -99,28 +99,28 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, 500);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, -1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, 1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -128,14 +128,14 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, 50);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -143,42 +143,42 @@ public class TrackRecordingServiceTestLooper {
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
||||
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, 2);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingInterval() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, 2);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
||||
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
assertNotNull(track);
|
||||
assertEquals(trackId, track.getId());
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
@@ -223,7 +223,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setTime(startTime + i * 10000);
|
||||
location.setBearing(3.0f);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
service.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
if (i % 7 == 0) {
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -25,6 +26,9 @@ public class HandlerServerTest {
|
||||
@Mock
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
@Mock
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private HandlerServer subject;
|
||||
|
||||
@Before
|
||||
@@ -41,10 +45,10 @@ public class HandlerServerTest {
|
||||
@Test
|
||||
public void onSharedPreferenceChanged() {
|
||||
// when
|
||||
subject.onSharedPreferenceChanged(null, null, null);
|
||||
subject.onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
|
||||
// then
|
||||
verify(locationHandler).onSharedPreferenceChanged(null, null, null);
|
||||
verify(locationHandler).onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+4
-4
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
|
||||
public class LocationHandlerTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
@Mock
|
||||
private HandlerServer handlerServer;
|
||||
@@ -43,11 +44,10 @@ public class LocationHandlerTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onStart(context);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class LocationHandlerTest {
|
||||
@Test
|
||||
public void testOnLocationChanged_poorAccuracy() {
|
||||
// given
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
|
||||
// when
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
|
||||
|
||||
+131
-4
@@ -1,27 +1,36 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackStatisticsUpdaterTest {
|
||||
|
||||
private static final int GPS_DISTANCE = 50;
|
||||
|
||||
@Test
|
||||
public void addTrackPoint() {
|
||||
public void addTrackPoint_TestingTrack() {
|
||||
// given
|
||||
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
|
||||
|
||||
// when
|
||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(it -> updater.addTrackPoint(it, 50));
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE));
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = updater.getTrackStatistics();
|
||||
TrackStatistics statistics = subject.getTrackStatistics();
|
||||
assertEquals(85.35, statistics.getTotalDistance(), 0.01);
|
||||
assertEquals(Duration.ofMillis(13999), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(6), statistics.getMovingTime());
|
||||
@@ -35,4 +44,122 @@ public class TrackStatisticsUpdaterTest {
|
||||
assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01);
|
||||
assertEquals(6.566, statistics.getAverageSpeed(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_not_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
tp3.setSpeed(5f);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
tp2.setSpeed(5f);
|
||||
TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
tp2.setSpeed(5f);
|
||||
TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000));
|
||||
tp2.setSpeed(5f);
|
||||
tp4.setSensorDistance(5f);
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
tp5.setSensorDistance(10f);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(110.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(125.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_not_moving_and_sensor_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
TrackPoint tp4 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(4000));
|
||||
tp4.setSensorDistance(5f);
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
tp5.setSensorDistance(10f);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(15, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_not_moving() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_moving() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_not_moving_and_sensor_speed() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() {
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.junit.runner.RunWith;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -26,27 +25,27 @@ public class AnnouncementUtilsTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement() {
|
||||
public void getAnnouncement_metric() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null);
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval() {
|
||||
public void getAnnouncement_withInterval_metric() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
|
||||
@@ -54,30 +53,10 @@ public class AnnouncementUtilsTest {
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000);
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
int speedId = R.plurals.voiceSpeedKilometersPerHour;
|
||||
double kmPerHour = lastInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH;
|
||||
|
||||
String firstPartMsg = "OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour";
|
||||
String rateMsg = " Lap speed of " + context.getResources().getQuantityString(speedId, getQuantityCount(kmPerHour), kmPerHour);
|
||||
String msg = firstPartMsg + rateMsg;
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", lastInterval);
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals(msg, announcement);
|
||||
}
|
||||
|
||||
private int getQuantityCount(double d) {
|
||||
if (d == 0) {
|
||||
return 0;
|
||||
} else if (d == 1) {
|
||||
return 1;
|
||||
} else if (d == 2) {
|
||||
return 2;
|
||||
} else {
|
||||
int count = (int) d;
|
||||
return Math.max(count, 3);
|
||||
}
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour Lap speed of 51.2 kilometers per hour", announcement);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertNull(sensor.getSpeed());
|
||||
assertNull(sensor.getDistanceSpeed());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,7 +60,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertNull(sensor.getCadence());
|
||||
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,7 +73,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ExportUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_exists() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_other name name.kmz");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertTrue(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists1() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "gpx";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists2() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,14 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_ok() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
|
||||
@@ -36,12 +38,13 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_invalid() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
@@ -50,12 +53,13 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_noValue() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
|
||||
@@ -94,18 +94,12 @@ public class StringUtilsTest {
|
||||
assertEquals("<![CDATA[hello]]]]><![CDATA[>there]]>", StringUtils.formatCData("hello]]>there"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with fractional seconds.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_fractional() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with time zone.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_timezone() {
|
||||
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
|
||||
@@ -118,9 +112,6 @@ public class StringUtilsTest {
|
||||
assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with fractional seconds and time zone.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_fractionalAndTimezone() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -46,12 +47,14 @@ public class TrackNameUtilsTest {
|
||||
|
||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
|
||||
|
||||
/**
|
||||
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_date_local() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
|
||||
@@ -60,7 +63,7 @@ public class TrackNameUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_date_iso_8601() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
|
||||
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
@@ -70,7 +73,7 @@ public class TrackNameUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_number() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
||||
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,11 +73,13 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/gpx+xml" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/gpx" />
|
||||
</intent-filter>
|
||||
@@ -135,6 +137,7 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/vnd.google-earth.kmz" />
|
||||
</intent-filter>
|
||||
@@ -192,6 +195,8 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/vnd.google-earth.kml+xml" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -66,7 +66,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
||||
public void onConfirmDeleteDone(Track.Id... trackIds) {
|
||||
boolean stopRecording = false;
|
||||
|
||||
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(PreferencesUtils.getSharedPreferences(this), this);
|
||||
for (Track.Id trackId : trackIds) {
|
||||
if (trackId.equals(recordingTrackId)) {
|
||||
stopRecording = true;
|
||||
|
||||
@@ -84,11 +84,9 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
// Setup UI elements
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.marker_types, android.R.layout.simple_dropdown_item_1line);
|
||||
viewBinding.markerEditMarkerType.setAdapter(adapter);
|
||||
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> {
|
||||
viewModel.onPhotoDelete(viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString());
|
||||
});
|
||||
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> viewModel.onPhotoDelete(viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString()));
|
||||
|
||||
viewBinding.markerEditCancel.setOnClickListener(v -> {
|
||||
viewModel.onCancel();
|
||||
|
||||
@@ -66,12 +66,12 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, MarkerListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, MarkerListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
|
||||
@@ -138,7 +138,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -27,31 +28,24 @@ public class Startup extends Application {
|
||||
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
|
||||
|
||||
//Set default values of preferences on first start.
|
||||
PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) {
|
||||
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
|
||||
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
|
||||
PreferencesUtils.setString(sharedPreferences, this, R.string.stats_units_key, statsUnits);
|
||||
}
|
||||
|
||||
ActivityUtils.applyNightMode(this);
|
||||
ActivityUtils.applyNightMode(sharedPreferences, this);
|
||||
|
||||
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
|
||||
if (PreferencesUtils.isRecording(this)) {
|
||||
if (PreferencesUtils.isRecording(sharedPreferences, this)) {
|
||||
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
|
||||
PreferencesUtils.setLong(this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
|
||||
//In debug builds: show thread and VM warnings.
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Enabling strict mode");
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.enableDefaults();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -29,6 +30,7 @@ import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -107,18 +109,18 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackListActivity.this);
|
||||
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(() -> {
|
||||
@@ -195,17 +197,20 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||
Intent newIntent;
|
||||
if (trackId == recordingTrackId.getId()) {
|
||||
// Is recording -> open record activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
startActivity(newIntent);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
this,
|
||||
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||
startActivity(newIntent, activityOptions.toBundle());
|
||||
}
|
||||
startActivity(newIntent);
|
||||
});
|
||||
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@@ -257,7 +262,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
super.onStart();
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@@ -55,6 +56,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
|
||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||
|
||||
public static final String VIEW_TRACK_ICON = "track_icon";
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
@@ -84,6 +87,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
if (savedInstanceState != null) {
|
||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||
}
|
||||
|
||||
postponeEnterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,7 +143,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(this));
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(PreferencesUtils.getSharedPreferences(this), this));
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
setTitle(track != null ? track.getName() : "");
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
@@ -275,4 +280,9 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startPostponedEnterTransitionWith(View viewIcon, View viewName) {
|
||||
ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON);
|
||||
startPostponedEnterTransition();
|
||||
}
|
||||
}
|
||||
@@ -97,9 +97,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackRecordingActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this);
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
@@ -182,7 +182,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
@@ -192,7 +192,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setFullscreenPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
@@ -206,7 +206,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
super.onStart();
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
trackDataHub.start();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -134,11 +135,13 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO Check preference handling.
|
||||
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
String category = aggregatedStatistic.getCategory();
|
||||
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context, category);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
||||
|
||||
sportIcon.setImageResource(getIcon(aggregatedStatistic));
|
||||
typeLabel.setText(category);
|
||||
|
||||
@@ -14,8 +14,8 @@ import java.util.concurrent.Future;
|
||||
*/
|
||||
class ExecutorListViewService<T> {
|
||||
|
||||
private List<T> enqueueObjects = new ArrayList<>();
|
||||
private ExecutorService executorService;
|
||||
private final List<T> enqueueObjects = new ArrayList<>();
|
||||
private final ExecutorService executorService;
|
||||
|
||||
public ExecutorListViewService(int numThreads) {
|
||||
executorService = Executors.newFixedThreadPool(numThreads);
|
||||
@@ -23,7 +23,6 @@ class ExecutorListViewService<T> {
|
||||
|
||||
public void shutdown() {
|
||||
enqueueObjects.clear();
|
||||
enqueueObjects = null;
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,12 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
||||
|
||||
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) {
|
||||
super(context, R.layout.interval_stats_list_item, intervalList);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
|
||||
this.category = category;
|
||||
this.stackMode = stackMode;
|
||||
}
|
||||
|
||||
//TODO Check preference handling! Should not be accessed in getView()
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View intervalView, @NonNull ViewGroup parent) {
|
||||
@@ -60,7 +61,7 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
||||
}
|
||||
viewHolder.distance.setText(StringUtils.formatDistance(getContext(), sumDistance_m, metricUnits));
|
||||
|
||||
if (PreferencesUtils.isReportSpeed(getContext(), category)) {
|
||||
if (PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category)) {
|
||||
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, true));
|
||||
} else {
|
||||
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, false));
|
||||
|
||||
@@ -33,7 +33,8 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
|
||||
|
||||
private final Activity activity;
|
||||
|
||||
private ExecutorListViewService executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||
//TODO Should be Marker.Id
|
||||
private final ExecutorListViewService<Long> executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||
|
||||
private boolean scroll = false;
|
||||
|
||||
@@ -111,8 +112,8 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
|
||||
return;
|
||||
}
|
||||
|
||||
long id = cursor.getLong(cursor.getColumnIndex(MarkerColumns._ID));
|
||||
String photoUrl = cursor.getString(cursor.getColumnIndex(MarkerColumns.PHOTOURL));
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MarkerColumns._ID));
|
||||
String photoUrl = cursor.getString(cursor.getColumnIndexOrThrow(MarkerColumns.PHOTOURL));
|
||||
|
||||
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
|
||||
if (hasPhoto) {
|
||||
|
||||
@@ -12,7 +12,7 @@ public class ScrollVisibleViews implements AbsListView.OnScrollListener {
|
||||
private int from = -1;
|
||||
private int to = -1;
|
||||
|
||||
private VisibleViewsListener visibleViewsListener;
|
||||
private final VisibleViewsListener visibleViewsListener;
|
||||
|
||||
public ScrollVisibleViews(@NonNull VisibleViewsListener visibleViewsListener) {
|
||||
this.visibleViewsListener = visibleViewsListener;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -13,19 +12,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorData;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
|
||||
public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
public static final int HEART_RATE_TYPE = 0;
|
||||
public static final int CADENCE_TYPE = 1;
|
||||
public static final int POWER_TYPE = 2;
|
||||
|
||||
private List<Pair<Integer, SensorData>> sensorDataList;
|
||||
private List<SensorDataModel> sensorDataList;
|
||||
private final Context context;
|
||||
|
||||
public SensorsAdapter(Context context) {
|
||||
@@ -42,9 +33,8 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SensorsAdapter.ViewHolder viewHolder = (SensorsAdapter.ViewHolder) holder;
|
||||
int type = sensorDataList.get(position).first;
|
||||
SensorData sensorData = sensorDataList.get(position).second;
|
||||
viewHolder.setData(sensorData, type);
|
||||
SensorDataModel sensorDataModel = sensorDataList.get(position);
|
||||
viewHolder.setData(sensorDataModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,12 +46,7 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return sensorDataList.get(position).first;
|
||||
}
|
||||
|
||||
public List<Pair<Integer, SensorData>> swapData(List<Pair<Integer, SensorData>> data) {
|
||||
public List<SensorDataModel> swapData(List<SensorDataModel> data) {
|
||||
if (sensorDataList == data) {
|
||||
return null;
|
||||
}
|
||||
@@ -89,70 +74,18 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
unit = itemView.findViewById(R.id.stats_sensor_unit);
|
||||
}
|
||||
|
||||
public void setData(SensorData sensorData, int type) {
|
||||
switch (type) {
|
||||
case HEART_RATE_TYPE:
|
||||
setHeartRateSensorData((SensorDataHeartRate) sensorData);
|
||||
break;
|
||||
case CADENCE_TYPE:
|
||||
setCadenceSensorData((SensorDataCycling.Cadence) sensorData);
|
||||
break;
|
||||
case POWER_TYPE:
|
||||
setPowerSensorData((SensorDataCyclingPower) sensorData);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unknown sensor type");
|
||||
}
|
||||
}
|
||||
public void setData(SensorDataModel sensorDataModel) {
|
||||
String sensorName = sensorDataModel.getSensorName();
|
||||
String sensorValue = sensorDataModel.hasValue() ? sensorDataModel.getSensorValue() : context.getString(R.string.value_unknown);
|
||||
|
||||
private void setHeartRateSensorData(SensorDataHeartRate data) {
|
||||
String sensorValue = context.getString(R.string.value_unknown);
|
||||
String sensorName = context.getString(R.string.value_unknown);
|
||||
if (data != null) {
|
||||
sensorName = data.getSensorNameOrAddress();
|
||||
if (data.hasHeartRate_bpm() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getHeartRate_bpm(), 0);
|
||||
}
|
||||
this.label.setText(context.getString(sensorDataModel.getLabelId()));
|
||||
if (sensorName == null) {
|
||||
this.sensorValue.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.sensorValue.setText(sensorName);
|
||||
}
|
||||
|
||||
this.label.setText(context.getString(R.string.sensor_state_heart_rate));
|
||||
this.sensorValue.setText(sensorName);
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(R.string.sensor_unit_beats_per_minute);
|
||||
}
|
||||
|
||||
private void setCadenceSensorData(SensorDataCycling.Cadence data) {
|
||||
String sensorValue = context.getString(R.string.value_unknown);
|
||||
String sensorName = context.getString(R.string.value_unknown);
|
||||
if (data != null) {
|
||||
sensorName = data.getSensorNameOrAddress();
|
||||
|
||||
if (data.hasCadence_rpm() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getCadence_rpm(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
this.label.setText(context.getString(R.string.sensor_state_cadence));
|
||||
this.sensorValue.setText(sensorName);
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(R.string.sensor_unit_rounds_per_minute);
|
||||
}
|
||||
|
||||
private void setPowerSensorData(SensorDataCyclingPower data) {
|
||||
String sensorValue = context.getString(R.string.value_unknown);
|
||||
String sensorName = context.getString(R.string.value_unknown);
|
||||
if (data != null) {
|
||||
sensorName = data.getSensorNameOrAddress();
|
||||
|
||||
if (data.hasPower_w() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getPower_w(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
this.label.setText(context.getString(R.string.sensor_state_power));
|
||||
this.sensorValue.setText(sensorName);
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(R.string.sensor_unit_power);
|
||||
this.unit.setText(context.getString(sensorDataModel.getUnitId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int targetNumPoints;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private boolean started;
|
||||
private HandlerThread handlerThread;
|
||||
private Handler handler;
|
||||
@@ -140,8 +142,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
||||
|
||||
|
||||
PreferencesUtils.register(context, this);
|
||||
onSharedPreferenceChanged(null, null);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
handler.post(() -> {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
@@ -155,7 +159,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
return;
|
||||
}
|
||||
|
||||
PreferencesUtils.unregister(context, this);
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||
sharedPreferences = null;
|
||||
|
||||
started = false;
|
||||
|
||||
@@ -224,10 +229,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||
handler.post(() -> {
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -365,10 +370,11 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = null;
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
||||
|
||||
while (trackPointIterator.hasNext()) {
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
trackPoint = trackPointIterator.next();
|
||||
TrackPoint.Id trackPointId = trackPoint.getId();
|
||||
|
||||
// Stop if past the last wanted point
|
||||
@@ -398,18 +404,23 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
}
|
||||
|
||||
localNumLoadedTrackPoints++;
|
||||
localLastSeenTrackPointIdId = trackPointId;
|
||||
}
|
||||
}
|
||||
|
||||
if (trackPoint != null) {
|
||||
localLastSeenTrackPointIdId = trackPoint.getId();
|
||||
}
|
||||
|
||||
if (updateSamplingState) {
|
||||
numLoadedPoints = localNumLoadedTrackPoints;
|
||||
firstSeenTrackPointId = localFirstSeenTrackPointId;
|
||||
lastSeenTrackPointId = localLastSeenTrackPointIdId;
|
||||
}
|
||||
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
listener.onNewTrackPointsDone();
|
||||
if (trackPoint != null) {
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
listener.onNewTrackPointsDone(trackPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -32,6 +34,7 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
//TODO Could be @NonNull
|
||||
void onTrackUpdated(Track track);
|
||||
|
||||
/**
|
||||
@@ -44,20 +47,20 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledInTrackPoint(TrackPoint trackPoint);
|
||||
void onSampledInTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
|
||||
/**
|
||||
* Called when a sampled out track point is read.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledOutTrackPoint(TrackPoint trackPoint);
|
||||
void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
|
||||
/**
|
||||
* Called when finish sending new track points.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint)} and {@link #onSampledOutTrackPoint(TrackPoint)}.
|
||||
*/
|
||||
void onNewTrackPointsDone();
|
||||
void onNewTrackPointsDone(@NonNull TrackPoint lastTrackPoint);
|
||||
|
||||
/**
|
||||
* Called to clear previously sent markers.
|
||||
|
||||
@@ -109,6 +109,19 @@ public class Track {
|
||||
this.trackStatistics = trackStatistics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Track track = (Track) o;
|
||||
return id.equals(track.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
public static class Id implements Parcelable {
|
||||
|
||||
private final long id;
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.os.Parcel;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -42,6 +43,11 @@ import java.util.Objects;
|
||||
*/
|
||||
public class TrackPoint {
|
||||
|
||||
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
||||
|
||||
// Anything faster than that (in meters per second) will be considered moving.
|
||||
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||
|
||||
private TrackPoint.Id id;
|
||||
|
||||
private Instant time;
|
||||
@@ -51,6 +57,7 @@ public class TrackPoint {
|
||||
private Double altitude_m;
|
||||
private Float speed_mps;
|
||||
private Float bearing;
|
||||
private Float sensorDistance_m;
|
||||
|
||||
public enum Type {
|
||||
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
|
||||
@@ -174,7 +181,23 @@ public class TrackPoint {
|
||||
}
|
||||
|
||||
public boolean hasLocation() {
|
||||
return latitude != null || longitude != null;
|
||||
return latitude != null && longitude != null;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -202,7 +225,7 @@ public class TrackPoint {
|
||||
return elevationGain != null;
|
||||
}
|
||||
|
||||
public Float getElevationGain() {
|
||||
public float getElevationGain() {
|
||||
return elevationGain;
|
||||
}
|
||||
|
||||
@@ -222,22 +245,6 @@ public class TrackPoint {
|
||||
this.elevationLoss = elevationLoss;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public Instant getTime() {
|
||||
return time;
|
||||
}
|
||||
@@ -246,6 +253,12 @@ public class TrackPoint {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public boolean isRecent() {
|
||||
return Instant.now()
|
||||
.isBefore(time.plus(MAX_LOCATION_AGE));
|
||||
}
|
||||
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return altitude_m != null;
|
||||
}
|
||||
@@ -270,6 +283,10 @@ public class TrackPoint {
|
||||
this.speed_mps = speed;
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return hasSpeed() && getSpeed() >= MAX_NO_MOVEMENT_SPEED;
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
return bearing != null;
|
||||
}
|
||||
@@ -294,8 +311,16 @@ public class TrackPoint {
|
||||
this.accuracy = horizontalAccuracy;
|
||||
}
|
||||
|
||||
public float distanceTo(@NonNull TrackPoint dest) {
|
||||
return getLocation().distanceTo(dest.getLocation());
|
||||
public float distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
if (hasSensorDistance()) {
|
||||
return getSensorDistance();
|
||||
}
|
||||
|
||||
return getLocation().distanceTo(previous.getLocation());
|
||||
}
|
||||
|
||||
public boolean fulfillsAccuracy(int poorAccuracy) {
|
||||
return hasAccuracy() && accuracy < poorAccuracy;
|
||||
}
|
||||
|
||||
public float bearingTo(@NonNull TrackPoint dest) {
|
||||
@@ -307,6 +332,18 @@ public class TrackPoint {
|
||||
}
|
||||
|
||||
// Sensor data
|
||||
public boolean hasSensorDistance() {
|
||||
return sensorDistance_m != null;
|
||||
}
|
||||
|
||||
public Float getSensorDistance() {
|
||||
return sensorDistance_m;
|
||||
}
|
||||
|
||||
public void setSensorDistance(Float distance_m) {
|
||||
this.sensorDistance_m = distance_m;
|
||||
}
|
||||
|
||||
public boolean hasSensorData() {
|
||||
return hasHeartRate() || hasCyclingCadence() || hasPower();
|
||||
}
|
||||
@@ -327,7 +364,7 @@ public class TrackPoint {
|
||||
return cyclingCadence_rpm != null;
|
||||
}
|
||||
|
||||
public Float getCyclingCadence_rpm() {
|
||||
public float getCyclingCadence_rpm() {
|
||||
return cyclingCadence_rpm;
|
||||
}
|
||||
|
||||
@@ -339,7 +376,7 @@ public class TrackPoint {
|
||||
return power != null;
|
||||
}
|
||||
|
||||
public Float getPower() {
|
||||
public float getPower() {
|
||||
return power;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,10 +50,18 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
String BEARING = "bearing";
|
||||
String SENSOR_HEARTRATE = "sensor_heartrate";
|
||||
String SENSOR_CADENCE = "sensor_cadence";
|
||||
String SENSOR_DISTANCE = "sensor_distance"; //DISTANCE from previous TrackPoint
|
||||
String SENSOR_POWER = "sensor_power";
|
||||
String ELEVATION_GAIN = "elevation_gain";
|
||||
String ELEVATION_LOSS = "elevation_loss";
|
||||
|
||||
// Alias for sensor statistics
|
||||
String ALIAS_AVG_HR = "avg_hr";
|
||||
String ALIAS_MAX_HR = "max_hr";
|
||||
String ALIAS_AVG_CADENCE = "avg_cadence";
|
||||
String ALIAS_MAX_CADENCE = "max_cadence";
|
||||
String ALIAS_AVG_POWER = "avg_power";
|
||||
|
||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ TRACKID + " INTEGER NOT NULL, "
|
||||
@@ -70,6 +78,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
+ ELEVATION_GAIN + " FLOAT, "
|
||||
+ ELEVATION_LOSS + " FLOAT, "
|
||||
+ TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), "
|
||||
+ SENSOR_DISTANCE + " FLOAT, "
|
||||
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public interface TracksColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "tracks";
|
||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_SENSOR_STATS = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/sensorstats");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
|
||||
String DEFAULT_SORT_ORDER = _ID;
|
||||
|
||||
@@ -19,6 +19,7 @@ class CachedTrackPointsIndexes {
|
||||
final int bearingIndex;
|
||||
final int sensorHeartRateIndex;
|
||||
final int sensorCadenceIndex;
|
||||
final int sensorDistanceIndex;
|
||||
final int sensorPowerIndex;
|
||||
final int elevationGainIndex;
|
||||
final int elevationLossIndex;
|
||||
@@ -35,6 +36,7 @@ class CachedTrackPointsIndexes {
|
||||
bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
|
||||
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
|
||||
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
|
||||
sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE);
|
||||
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
|
||||
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
|
||||
elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
@@ -44,6 +45,7 @@ import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
@@ -68,7 +70,6 @@ public class ContentProviderUtils {
|
||||
private static final String ID_SEPARATOR = ",";
|
||||
|
||||
private final ContentResolver contentResolver;
|
||||
private int defaultCursorBatchSize = 2000;
|
||||
|
||||
public ContentProviderUtils(Context context) {
|
||||
contentResolver = context.getContentResolver();
|
||||
@@ -174,7 +175,7 @@ public class ContentProviderUtils {
|
||||
|
||||
// Delete track last since it triggers a database vacuum call
|
||||
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id->Long.toString(id.getId())).toArray(String[]::new));
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new));
|
||||
}
|
||||
|
||||
public void deleteTrack(Context context, @NonNull Track.Id trackId) {
|
||||
@@ -554,6 +555,9 @@ public class ContentProviderUtils {
|
||||
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
|
||||
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
|
||||
trackPoint.setSensorDistance(cursor.getFloat(indexes.sensorDistanceIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.sensorPowerIndex)) {
|
||||
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
|
||||
}
|
||||
@@ -577,24 +581,6 @@ public class ContentProviderUtils {
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first location id for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
@Deprecated
|
||||
public Track.Id getFirstTrackPointId(Track.Id trackId) {
|
||||
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return new Track.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last location id for a track.
|
||||
* Returns -1L if it doesn't exist.
|
||||
@@ -645,9 +631,9 @@ public class ContentProviderUtils {
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting trackPoint id. `null` to ignore
|
||||
* @param maxLocations maximum number of locations to return. `null` for no limit
|
||||
*/
|
||||
public Cursor getTrackPointCursor(Track.Id trackId, TrackPoint.Id startTrackPointId, Integer maxLocations) {
|
||||
@NonNull
|
||||
public Cursor getTrackPointCursor(@NonNull Track.Id trackId, TrackPoint.Id startTrackPointId) {
|
||||
String selection;
|
||||
String[] selectionArgs;
|
||||
if (startTrackPointId != null) {
|
||||
@@ -658,11 +644,7 @@ public class ContentProviderUtils {
|
||||
selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||
}
|
||||
|
||||
String sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER;
|
||||
if (maxLocations != null) {
|
||||
sortOrder += " LIMIT " + maxLocations;
|
||||
}
|
||||
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
|
||||
return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -724,6 +706,9 @@ public class ContentProviderUtils {
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
|
||||
}
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance());
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
|
||||
}
|
||||
@@ -773,39 +758,6 @@ public class ContentProviderUtils {
|
||||
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
|
||||
List<TrackPoint> trackPoints = null;
|
||||
|
||||
try (Cursor trackPointCursor = getTrackPointCursor(trackId, null, null)) {
|
||||
if (trackPointCursor != null) {
|
||||
trackPointCursor.moveToFirst();
|
||||
trackPoints = new ArrayList<>(trackPointCursor.getCount());
|
||||
for (int i = 0; i < trackPointCursor.getCount(); i++) {
|
||||
trackPoints.add(createTrackPoint(trackPointCursor));
|
||||
trackPointCursor.moveToNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trackPoints;
|
||||
}
|
||||
|
||||
int getDefaultCursorBatchSize() {
|
||||
return defaultCursorBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default cursor batch size. For testing purpose.
|
||||
*
|
||||
* @param defaultCursorBatchSize the default cursor batch size
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
|
||||
this.defaultCursorBatchSize = defaultCursorBatchSize;
|
||||
}
|
||||
|
||||
|
||||
public static String formatIdListForUri(Track.Id... trackIds) {
|
||||
long[] ids = new long[trackIds.length];
|
||||
for (int i = 0; i < trackIds.length; i++) {
|
||||
@@ -835,4 +787,21 @@ public class ContentProviderUtils {
|
||||
public static String[] parseTrackIdsFromUri(Uri url) {
|
||||
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
||||
}
|
||||
|
||||
public SensorStatistics getSensorStats(@NonNull Track.Id trackId) {
|
||||
SensorStatistics sensorStatistics = null;
|
||||
try (Cursor cursor = contentResolver.query(ContentUris.withAppendedId(TracksColumns.CONTENT_URI_SENSOR_STATS, trackId.getId()), null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
sensorStatistics = new SensorStatistics(
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("max_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_HR)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_HR)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("max_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_CADENCE)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_CADENCE)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_power")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_POWER)) : null
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
return sensorStatistics;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
|
||||
@@ -56,6 +57,38 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
private SQLiteDatabase db;
|
||||
|
||||
/**
|
||||
* The string representing the query that compute sensor stats from trackpoints table.
|
||||
* It computes the average for heart rate, cadence, and power (duration-based average) and the maximum for heart rate and cadence.
|
||||
* Finally, it ignores manual pause (SEGMENT_START_MANUAL).
|
||||
*/
|
||||
private final String SENSOR_STATS_QUERY =
|
||||
"WITH time_select as " +
|
||||
"(SELECT t1." + TrackPointsColumns.TIME + " * (t1." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")) time_value " +
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t1 " +
|
||||
"WHERE t1." + TrackPointsColumns._ID + " > t." + TrackPointsColumns._ID + " AND t1." + TrackPointsColumns.TRACKID + " = ? ORDER BY _id LIMIT 1) " +
|
||||
|
||||
"SELECT " +
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_HEARTRATE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_HR + ", " +
|
||||
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_HEARTRATE + ") " + TrackPointsColumns.ALIAS_MAX_HR + ", " +
|
||||
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_CADENCE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_CADENCE + ", " +
|
||||
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_CADENCE + ") " + TrackPointsColumns.ALIAS_MAX_CADENCE + ", " +
|
||||
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_POWER + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_POWER + " " +
|
||||
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t " +
|
||||
"WHERE t." + TrackPointsColumns.TRACKID + " = ? " +
|
||||
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
||||
|
||||
public CustomContentProvider() {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||
@@ -63,6 +96,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.TRACKPOINTS_BY_TRACKID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_SENSOR_STATS.getPath() + "/#", UrlType.TRACKS_SENSOR_STATS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||
@@ -223,6 +257,9 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
break;
|
||||
case TRACKS_SENSOR_STATS:
|
||||
long trackId = ContentUris.parseId(url);
|
||||
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
||||
case MARKERS:
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
||||
@@ -363,6 +400,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
TRACKPOINTS_BY_TRACKID,
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
TRACKS_SENSOR_STATS,
|
||||
MARKERS,
|
||||
MARKERS_BY_ID,
|
||||
MARKERS_BY_TRACKID
|
||||
|
||||
+35
-2
@@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
private static final int DATABASE_VERSION = 30;
|
||||
private static final int DATABASE_VERSION = 31;
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String DATABASE_NAME = "database.db";
|
||||
@@ -82,6 +82,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 30:
|
||||
upgradeFrom29to30(db);
|
||||
break;
|
||||
case 31:
|
||||
upgradeFrom30to31(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||
@@ -115,7 +118,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 29:
|
||||
downgradeFrom30to29(db);
|
||||
break;
|
||||
|
||||
case 30:
|
||||
downgradeFrom31to30(db);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||
}
|
||||
@@ -394,4 +399,32 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add distance column to TrackPoint.
|
||||
*/
|
||||
private void upgradeFrom30to31(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
// TrackPoints
|
||||
db.execSQL("ALTER TABLE trackpoints ADD COLUMN sensor_distance FLOAT");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void downgradeFrom31to30(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
// TrackPoints; identical to upgradeFrom27to28()
|
||||
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
|
||||
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1)), FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
|
||||
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type FROM trackpoints_old");
|
||||
db.execSQL("DROP TABLE trackpoints_old");
|
||||
|
||||
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
@@ -10,9 +12,8 @@ import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
|
||||
* A lightweight wrapper around the original {@link Cursor}.
|
||||
*/
|
||||
//TODO Remove batching; that should be handled by the database/contentprovider (i.e., already in place as we use a cursor)!
|
||||
public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
|
||||
private static final String TAG = TrackPointIterator.class.getSimpleName();
|
||||
@@ -20,7 +21,6 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final Track.Id trackId;
|
||||
private final CachedTrackPointsIndexes indexes;
|
||||
private TrackPoint.Id lastTrackPointId = null;
|
||||
private Cursor cursor;
|
||||
|
||||
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
|
||||
@@ -28,28 +28,11 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
this.trackId = trackId;
|
||||
|
||||
cursor = getCursor(startTrackPointId);
|
||||
indexes = cursor != null ? new CachedTrackPointsIndexes(cursor)
|
||||
: null;
|
||||
indexes = new CachedTrackPointsIndexes(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track point cursor.
|
||||
*
|
||||
* @param trackPointId the starting track point id
|
||||
*/
|
||||
private Cursor getCursor(TrackPoint.Id trackPointId) {
|
||||
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the cursor to the next batch. Returns true if successful.
|
||||
*/
|
||||
private boolean advanceCursorToNextBatch() {
|
||||
TrackPoint.Id trackPointId = lastTrackPointId == null ? null : new TrackPoint.Id(lastTrackPointId.getId() + 1);
|
||||
Log.d(TAG, "Advancing track point id: " + trackPointId);
|
||||
cursor.close();
|
||||
cursor = getCursor(trackPointId);
|
||||
return cursor != null;
|
||||
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,32 +40,23 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
if (cursor == null) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.isAfterLast()) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.isLast()) {
|
||||
if (cursor.getCount() != contentProviderUtils.getDefaultCursorBatchSize()) {
|
||||
return false;
|
||||
}
|
||||
return advanceCursorToNextBatch() && !cursor.isAfterLast();
|
||||
}
|
||||
return true;
|
||||
return !cursor.isLast() && !cursor.isAfterLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public TrackPoint next() {
|
||||
if (cursor == null) {
|
||||
if (cursor == null || !cursor.moveToNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
if (!cursor.moveToNext()) {
|
||||
if (!advanceCursorToNextBatch() || !cursor.moveToNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
lastTrackPointId = new TrackPoint.Id(cursor.getLong(indexes.idIndex));
|
||||
return ContentProviderUtils.fillTrackPoint(cursor, indexes);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getCount() {
|
||||
return cursor.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cursor != null) {
|
||||
|
||||
@@ -8,12 +8,14 @@ import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
|
||||
public class SensorData {
|
||||
public abstract class SensorData<T> {
|
||||
|
||||
protected T value;
|
||||
|
||||
private final String sensorAddress;
|
||||
private final String sensorName;
|
||||
|
||||
private final Instant timestamp_ms;
|
||||
private final Instant time;
|
||||
|
||||
SensorData(String sensorAddress) {
|
||||
this(sensorAddress, null);
|
||||
@@ -24,10 +26,10 @@ public class SensorData {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SensorData(String sensorAddress, String sensorName, Instant timestamp_ms) {
|
||||
SensorData(String sensorAddress, String sensorName, Instant time) {
|
||||
this.sensorAddress = sensorAddress;
|
||||
this.sensorName = sensorName;
|
||||
this.timestamp_ms = timestamp_ms;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -44,7 +46,31 @@ public class SensorData {
|
||||
return sensorName != null ? sensorName : sensorAddress;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance.
|
||||
*/
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the data recent considering the current time.
|
||||
*/
|
||||
public boolean isRecent() {
|
||||
return Instant.now().isBefore(timestamp_ms.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
|
||||
return Instant.now()
|
||||
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "sensorAddress='" + sensorAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import de.dennisguse.opentracks.util.UintUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
@@ -20,11 +21,10 @@ public final class SensorDataCycling {
|
||||
private SensorDataCycling() {
|
||||
}
|
||||
|
||||
public static class Cadence extends SensorData {
|
||||
public static class Cadence extends SensorData<Float> {
|
||||
|
||||
private final Long crankRevolutionsCount; // UINT32
|
||||
private final Integer crankRevolutionsTime; // UINT16; 1/1024s
|
||||
private Float cadence_rpm;
|
||||
|
||||
public Cadence(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
@@ -41,7 +41,7 @@ public final class SensorDataCycling {
|
||||
/**
|
||||
* Workaround for Wahoo CADENCE: provides speed instead of cadence
|
||||
*/
|
||||
public Cadence(@NonNull SensorDataCycling.Speed speed) {
|
||||
public Cadence(@NonNull DistanceSpeed speed) {
|
||||
this(speed.getSensorAddress(), speed.getSensorName(), speed.wheelRevolutionsCount, speed.wheelRevolutionsTime);
|
||||
}
|
||||
|
||||
@@ -57,24 +57,16 @@ public final class SensorDataCycling {
|
||||
return crankRevolutionsTime;
|
||||
}
|
||||
|
||||
public boolean hasCadence_rpm() {
|
||||
return cadence_rpm != null;
|
||||
}
|
||||
|
||||
public float getCadence_rpm() {
|
||||
return cadence_rpm;
|
||||
}
|
||||
|
||||
public void compute(Cadence previous) {
|
||||
if (hasData() && previous != null && previous.hasData()) {
|
||||
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||
if (timeDiff_ms <= 0) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
cadence_rpm = null;
|
||||
value = null;
|
||||
} else {
|
||||
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
||||
float cadence_ms = crankDiff / timeDiff_ms;
|
||||
cadence_rpm = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
||||
value = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +74,7 @@ public final class SensorDataCycling {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "cadence=" + cadence_rpm + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
|
||||
return super.toString() + " cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,19 +90,18 @@ public final class SensorDataCycling {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Speed extends SensorData {
|
||||
public static class DistanceSpeed extends SensorData<DistanceSpeed.Data> {
|
||||
|
||||
private final Integer wheelRevolutionsCount; // UINT16
|
||||
private final Integer wheelRevolutionsTime; // UINT16; 1/1024s
|
||||
private Float speed_mps;
|
||||
|
||||
public Speed(String sensorAddress) {
|
||||
public DistanceSpeed(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
this.wheelRevolutionsCount = null;
|
||||
this.wheelRevolutionsTime = null;
|
||||
}
|
||||
|
||||
public Speed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
|
||||
public DistanceSpeed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
|
||||
super(sensorAddress, sensorName);
|
||||
this.wheelRevolutionsCount = wheelRevolutionsCount;
|
||||
this.wheelRevolutionsTime = wheelRevolutionsTime;
|
||||
@@ -128,64 +119,88 @@ public final class SensorDataCycling {
|
||||
return wheelRevolutionsTime;
|
||||
}
|
||||
|
||||
public boolean hasSpeed_mps() {
|
||||
return speed_mps != null;
|
||||
}
|
||||
|
||||
public float getSpeed_mps() {
|
||||
return speed_mps;
|
||||
}
|
||||
|
||||
public void compute(Speed previous, int wheel_circumference_mm) {
|
||||
public void compute(DistanceSpeed previous, int wheel_circumference_mm) {
|
||||
if (hasData() && previous != null && previous.hasData()) {
|
||||
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||
if (timeDiff_ms <= 0) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
speed_mps = null;
|
||||
value = null;
|
||||
} else {
|
||||
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
|
||||
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as it some of those seem to count backwards
|
||||
|
||||
double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S;
|
||||
speed_mps = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M / timeDiff_s);
|
||||
float distance_m = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M);
|
||||
float distance_overall_m = distance_m;
|
||||
if (previous.hasValue()) {
|
||||
distance_overall_m += previous.getValue().distance_overall_m;
|
||||
}
|
||||
float speed_mps = (float) (distance_m / timeDiff_s);
|
||||
value = new Data(distance_m, distance_overall_m, speed_mps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (value != null) {
|
||||
value = new Data(value.distance_m, 0, value.speed_mps);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "speed=" + speed_mps + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
|
||||
return super.toString() + " data=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (!(obj instanceof Speed)) return false;
|
||||
if (!(obj instanceof DistanceSpeed)) return false;
|
||||
|
||||
Speed comp = (Speed) obj;
|
||||
DistanceSpeed comp = (DistanceSpeed) obj;
|
||||
if (hasData() && comp.hasData() == hasData()) {
|
||||
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public final float distance_m;
|
||||
public final float distance_overall_m;
|
||||
public final float speed_mps;
|
||||
|
||||
private Data(float distance_m, float distance_overall_m, float speed_mps) {
|
||||
this.distance_m = distance_m;
|
||||
this.distance_overall_m = distance_overall_m;
|
||||
this.speed_mps = speed_mps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"distance_m=" + distance_m +
|
||||
", distance_overall_m=" + distance_overall_m +
|
||||
", speed_mps=" + speed_mps +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CadenceAndSpeed extends SensorData {
|
||||
public static class CadenceAndSpeed extends SensorData<Pair<Cadence, DistanceSpeed>> {
|
||||
|
||||
private final Cadence cadence;
|
||||
private final Speed speed;
|
||||
|
||||
public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) {
|
||||
public CadenceAndSpeed(String sensorAddress, String sensorName, @Nullable Cadence cadence, @Nullable DistanceSpeed distanceSpeed) {
|
||||
super(sensorAddress, sensorName);
|
||||
this.cadence = cadence;
|
||||
this.speed = speed;
|
||||
this.value = new Pair<>(cadence, distanceSpeed);
|
||||
}
|
||||
|
||||
public Cadence getCadence() {
|
||||
return cadence;
|
||||
return this.value != null ? this.value.first : null;
|
||||
}
|
||||
|
||||
public Speed getSpeed() {
|
||||
return speed;
|
||||
public DistanceSpeed getDistanceSpeed() {
|
||||
return this.value != null ? this.value.second : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,20 @@ package de.dennisguse.opentracks.content.sensor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class SensorDataCyclingPower extends SensorData {
|
||||
|
||||
private final Float power_w;
|
||||
public class SensorDataCyclingPower extends SensorData<Float> {
|
||||
|
||||
public SensorDataCyclingPower(String address) {
|
||||
super(address);
|
||||
this.power_w = null;
|
||||
}
|
||||
|
||||
public SensorDataCyclingPower(String name, String address, float power_w) {
|
||||
super(name, address);
|
||||
this.power_w = power_w;
|
||||
}
|
||||
|
||||
public boolean hasPower_w() {
|
||||
return power_w != null;
|
||||
}
|
||||
|
||||
public float getPower_w() {
|
||||
return power_w;
|
||||
this.value = power_w;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "power=" + power_w;
|
||||
return super.toString() + " power=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,20 @@ package de.dennisguse.opentracks.content.sensor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class SensorDataHeartRate extends SensorData {
|
||||
|
||||
private final Float heartRate_bpm;
|
||||
public class SensorDataHeartRate extends SensorData<Float> {
|
||||
|
||||
public SensorDataHeartRate(String address) {
|
||||
super(address);
|
||||
heartRate_bpm = null;
|
||||
}
|
||||
|
||||
public SensorDataHeartRate(String name, String address, float heartRate_bpm) {
|
||||
super(name, address);
|
||||
this.heartRate_bpm = heartRate_bpm;
|
||||
}
|
||||
|
||||
public boolean hasHeartRate_bpm() {
|
||||
return heartRate_bpm != null;
|
||||
}
|
||||
|
||||
public Float getHeartRate_bpm() {
|
||||
return heartRate_bpm;
|
||||
this.value = heartRate_bpm;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "heart=" + heartRate_bpm;
|
||||
return super.toString() + " heart=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public final class SensorDataSet {
|
||||
|
||||
private SensorDataCycling.Cadence cyclingCadence;
|
||||
|
||||
private SensorDataCycling.Speed cyclingSpeed;
|
||||
private SensorDataCycling.DistanceSpeed cyclingDistanceSpeed;
|
||||
|
||||
private SensorDataCyclingPower cyclingPower;
|
||||
|
||||
@@ -25,57 +25,65 @@ public final class SensorDataSet {
|
||||
return cyclingCadence;
|
||||
}
|
||||
|
||||
public SensorDataCycling.Speed getCyclingSpeed() {
|
||||
return cyclingSpeed;
|
||||
public SensorDataCycling.DistanceSpeed getCyclingDistanceSpeed() {
|
||||
return cyclingDistanceSpeed;
|
||||
}
|
||||
|
||||
public SensorDataCyclingPower getCyclingPower() {
|
||||
return cyclingPower;
|
||||
}
|
||||
|
||||
public void set(SensorData data) {
|
||||
public void set(SensorData<?> data) {
|
||||
set(data, data);
|
||||
}
|
||||
|
||||
public void remove(SensorData type) {
|
||||
public void remove(SensorData<?> type) {
|
||||
set(type, null);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.heartRate = null;
|
||||
this.cyclingCadence = null;
|
||||
this.cyclingSpeed = null;
|
||||
this.cyclingDistanceSpeed = null;
|
||||
this.cyclingPower = null;
|
||||
}
|
||||
|
||||
public void fillTrackPoint(TrackPoint trackPoint) {
|
||||
if (heartRate != null) {
|
||||
trackPoint.setHeartRate_bpm(heartRate.getHeartRate_bpm());
|
||||
trackPoint.setHeartRate_bpm(heartRate.getValue());
|
||||
}
|
||||
|
||||
if (cyclingCadence != null && cyclingCadence.hasCadence_rpm()) {
|
||||
trackPoint.setCyclingCadence_rpm(cyclingCadence.getCadence_rpm());
|
||||
if (cyclingCadence != null && cyclingCadence.hasValue()) {
|
||||
trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue());
|
||||
}
|
||||
|
||||
if (cyclingSpeed != null && cyclingSpeed.hasSpeed_mps()) {
|
||||
trackPoint.setSpeed(cyclingSpeed.getSpeed_mps());
|
||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
|
||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m);
|
||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps);
|
||||
}
|
||||
|
||||
if (cyclingPower != null && cyclingPower.hasPower_w()) {
|
||||
trackPoint.setPower(cyclingPower.getPower_w());
|
||||
if (cyclingPower != null && cyclingPower.hasValue()) {
|
||||
trackPoint.setPower(cyclingPower.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (heartRate != null) heartRate.reset();
|
||||
if (cyclingCadence != null) cyclingCadence.reset();
|
||||
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
|
||||
if (cyclingPower != null) cyclingPower.reset();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return (getHeartRate() != null ? "" + getHeartRate() : "")
|
||||
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
|
||||
+ (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "")
|
||||
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
|
||||
+ (getCyclingPower() != null ? " " + getCyclingPower() : "");
|
||||
}
|
||||
|
||||
private void set(@NonNull SensorData type, SensorData data) {
|
||||
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
|
||||
if (type instanceof SensorDataHeartRate) {
|
||||
this.heartRate = (SensorDataHeartRate) data;
|
||||
return;
|
||||
@@ -85,8 +93,8 @@ public final class SensorDataSet {
|
||||
this.cyclingCadence = (SensorDataCycling.Cadence) data;
|
||||
return;
|
||||
}
|
||||
if (type instanceof SensorDataCycling.Speed) {
|
||||
this.cyclingSpeed = (SensorDataCycling.Speed) data;
|
||||
if (type instanceof SensorDataCycling.DistanceSpeed) {
|
||||
this.cyclingDistanceSpeed = (SensorDataCycling.DistanceSpeed) data;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,31 +65,28 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
return chartFragment;
|
||||
}
|
||||
|
||||
private final List<ChartPoint> pendingPoints = new ArrayList<>();
|
||||
private SharedPreferences sharedPreferences;
|
||||
private int recordingDistanceInterval;
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
// Stats gathered from the received data
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
|
||||
private ChartBinding viewBinding;
|
||||
|
||||
//TODO REMOVE: Used to restore the TrackStatistics via TrackStatisticsUpdater; for the diagrams we should consider not this setting and just use all data that is in the database
|
||||
@Deprecated
|
||||
private int recordingDistanceInterval;
|
||||
private final List<ChartPoint> pendingPoints = new ArrayList<>();
|
||||
private String category = "";
|
||||
|
||||
// Modes of operation
|
||||
private boolean chartByDistance;
|
||||
|
||||
private ChartBinding viewBinding;
|
||||
// UI elements
|
||||
private ChartView chartView;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
if (metricUnits != chartView.getMetricUnits()) {
|
||||
chartView.setMetricUnits(metricUnits);
|
||||
runOnUiThread(() -> {
|
||||
@@ -100,7 +97,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
||||
if (reportSpeed != chartView.getReportSpeed()) {
|
||||
chartView.setReportSpeed(reportSpeed);
|
||||
chartView.applyReportSpeed();
|
||||
@@ -114,7 +111,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(getContext());
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, getContext());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -165,8 +162,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
checkChartSettings();
|
||||
getActivity().runOnUiThread(updateChart);
|
||||
@@ -176,7 +174,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,6 +190,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
if (isResumed()) {
|
||||
@@ -201,7 +205,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
category = track.getCategory();
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category);
|
||||
if (reportSpeed != chartView.getReportSpeed()) {
|
||||
chartView.setReportSpeed(reportSpeed);
|
||||
chartView.applyReportSpeed();
|
||||
@@ -224,14 +228,14 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(TrackPoint trackPoint) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
pendingPoints.add(createPendingPoint(trackPoint));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
if (trackStatisticsUpdater != null) {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
@@ -240,7 +244,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused) {
|
||||
if (isResumed()) {
|
||||
chartView.addChartPoints(pendingPoints);
|
||||
pendingPoints.clear();
|
||||
|
||||
@@ -44,14 +44,16 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
private IntervalStatisticsAdapter adapter;
|
||||
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
private String category;
|
||||
|
||||
private IntervalListViewBinding viewBinding;
|
||||
|
||||
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
if (adapter != null) {
|
||||
adapter.notifyDataSetChanged();
|
||||
spinnerAdapter.notifyDataSetChanged();
|
||||
@@ -73,8 +75,9 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
||||
|
||||
@@ -125,6 +128,8 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +142,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||
sharedPreferences = null;
|
||||
|
||||
adapter = null;
|
||||
viewModel = null;
|
||||
@@ -183,7 +188,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
category = track != null ? track.getCategory() : "";
|
||||
|
||||
// Set rate label.
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category); //TODO Handle sharedPreferenceChangeListener
|
||||
viewBinding.intervalRate.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
}
|
||||
});
|
||||
@@ -198,21 +203,21 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(TrackPoint trackPoint) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
viewModel.add(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
viewModel.add(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused) {
|
||||
if (isResumed()) {
|
||||
runOnUiThread(this::loadIntervals);
|
||||
}
|
||||
|
||||
@@ -27,15 +27,24 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a recorded {@link Track}.
|
||||
@@ -43,36 +52,10 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//TODO During updateUI(): do not call PreferenceUtils (it is slow) rather use sharedPreferenceChangeListener.
|
||||
public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
private static final String TRACK_ID_KEY = "trackId";
|
||||
|
||||
private TrackStatistics trackStatistics;
|
||||
private String category = "";
|
||||
private Track.Id trackId;
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private StatisticsRecordedBinding viewBinding;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordedBinding.inflate(inflater, container, false);
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
public static StatisticsRecordedFragment newInstance(Track.Id trackId) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(TRACK_ID_KEY, trackId);
|
||||
@@ -82,18 +65,70 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private TrackStatistics trackStatistics;
|
||||
private SensorStatistics sensorStatistics;
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
private String category = "";
|
||||
private Track.Id trackId;
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private StatisticsRecordedBinding viewBinding;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private boolean preferenceMetricUnits;
|
||||
private boolean preferenceReportSpeed;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
boolean updateUInecessary = false;
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
||||
contentProviderUtils = new ContentProviderUtils(getContext());
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
|
||||
sensorsAdapter = new SensorsAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordedBinding.inflate(inflater, container, false);
|
||||
|
||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
loadStatistics();
|
||||
}
|
||||
@@ -103,7 +138,13 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
super.onDestroyView();
|
||||
viewBinding = null;
|
||||
|
||||
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
public void loadStatistics() {
|
||||
@@ -111,22 +152,36 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
trackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
trackStatistics = track.getTrackStatistics();
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
String newCategory = track.getCategory();
|
||||
if (!category.equals(newCategory)) {
|
||||
category = newCategory;
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
||||
}
|
||||
|
||||
loadTrackDescription(track);
|
||||
updateUI();
|
||||
updateSensorUI();
|
||||
|
||||
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon, viewBinding.statsNameValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||
private void loadTrackDescription(@NonNull Track track) {
|
||||
viewBinding.statsNameValue.setText(track.getName());
|
||||
viewBinding.statsDescriptionValue.setText(track.getDescription());
|
||||
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(getContext(), trackStatistics.getStartTime()));
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = trackStatistics == null ? Double.NaN : trackStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, metricUnits);
|
||||
double totalDistance = trackStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
@@ -139,39 +194,39 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
}
|
||||
|
||||
// Set time and start datetime
|
||||
if (trackStatistics != null) {
|
||||
{
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()));
|
||||
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
|
||||
}
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = trackStatistics != null ? trackStatistics.getAverageSpeed() : Double.NaN;
|
||||
viewBinding.statsAverageSpeedLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
double speed = trackStatistics.getAverageSpeed();
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsAverageSpeedValue.setText(parts.first);
|
||||
viewBinding.statsAverageSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = trackStatistics == null ? Double.NaN : trackStatistics.getMaxSpeed();
|
||||
double speed = trackStatistics.getMaxSpeed();
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMaxSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMaxSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = trackStatistics != null ? trackStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
double speed = trackStatistics.getAverageMovingSpeed();
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMovingSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||
}
|
||||
@@ -179,21 +234,43 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
// Set elevation gain and loss
|
||||
{
|
||||
// Make elevation visible?
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(sharedPreferences, getContext());
|
||||
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
Float elevationGain_m = trackStatistics != null ? trackStatistics.getTotalElevationGain() : null;
|
||||
Float elevationLoss_m = trackStatistics != null ? trackStatistics.getTotalElevationLoss() : null;
|
||||
Float elevationGain_m = trackStatistics.getTotalElevationGain();
|
||||
Float elevationLoss_m = trackStatistics.getTotalElevationLoss();
|
||||
|
||||
Pair<String, String> parts;
|
||||
|
||||
parts = StringUtils.formatElevation(getContext(), elevationGain_m, metricUnits);
|
||||
parts = StringUtils.formatElevation(getContext(), elevationGain_m, preferenceMetricUnits);
|
||||
viewBinding.statsElevationGainValue.setText(parts.first);
|
||||
viewBinding.statsElevationGainUnit.setText(parts.second);
|
||||
|
||||
parts = StringUtils.formatElevation(getContext(), elevationLoss_m, metricUnits);
|
||||
parts = StringUtils.formatElevation(getContext(), elevationLoss_m, preferenceMetricUnits);
|
||||
viewBinding.statsElevationLossValue.setText(parts.first);
|
||||
viewBinding.statsElevationLossUnit.setText(parts.second);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSensorUI() {
|
||||
if (sensorStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
||||
if (sensorStatistics.hasHeartRate()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_max, R.string.sensor_unit_beats_per_minute, sensorStatistics.getMaxHeartRate()));
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_avg, R.string.sensor_unit_beats_per_minute, sensorStatistics.getAvgHeartRate()));
|
||||
}
|
||||
if (sensorStatistics.hasCadence()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_max, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getMaxCadence()));
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_avg, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getAvgCadence()));
|
||||
}
|
||||
if (sensorStatistics.hasPower()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_power_avg, R.string.sensor_unit_power, sensorStatistics.getAvgPower()));
|
||||
}
|
||||
if (sensorDataList.size() > 0) {
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+134
-105
@@ -10,6 +10,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -26,18 +27,17 @@ import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorData;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
||||
@@ -45,54 +45,79 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//TODO isRecording should not be relevant anymore as we now have StatisticRecordedFragment.
|
||||
//TODO During updateUI(): do not call PreferenceUtils (it is slow) rather use sharedPreferenceChangeListener.
|
||||
public class StatisticsRecordingFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
private static final String TAG = StatisticsRecordingFragment.class.getSimpleName();
|
||||
|
||||
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND_MS;
|
||||
public static Fragment newInstance() {
|
||||
return new StatisticsRecordingFragment();
|
||||
}
|
||||
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
private Handler handlerUpdateUI;
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
|
||||
private TrackPoint lastTrackPoint;
|
||||
|
||||
//TODO Should not be nullable
|
||||
private TrackStatistics lastTrackStatistics;
|
||||
|
||||
private String category = "";
|
||||
|
||||
private StatisticsRecordingBinding viewBinding;
|
||||
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
|
||||
public static Fragment newInstance() {
|
||||
return new StatisticsRecordingFragment();
|
||||
}
|
||||
private SharedPreferences sharedPreferences;
|
||||
private boolean preferenceMetricUnits;
|
||||
private boolean preferenceReportSpeed;
|
||||
private boolean preferenceShowElevation;
|
||||
private boolean preferenceShowCoordinate;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
boolean updateUInecessary = false;
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_elevation_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceShowElevation = PreferencesUtils.isShowStatsElevation(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_coordinate_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceShowCoordinate = PreferencesUtils.isStatsShowCoordinate(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
private final Runnable updateUIeachSecond = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && isSelectedTrackRecording()) {
|
||||
if (!isSelectedTrackPaused() && lastTrackStatistics != null) {
|
||||
synchronized (this) {
|
||||
if (lastTrackPoint != null && lastTrackPoint.hasLocation() && !lastTrackPoint.isRecent()) {
|
||||
lastTrackPoint = null;
|
||||
setLocationValues();
|
||||
}
|
||||
}
|
||||
updateTotalTime();
|
||||
updateSensorDataUI();
|
||||
}
|
||||
@@ -103,27 +128,38 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
handlerUpdateUI = new Handler();
|
||||
|
||||
|
||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(category));
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
|
||||
sensorsAdapter = new SensorsAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(category));
|
||||
|
||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
|
||||
updateUIeachSecond.run();
|
||||
handlerUpdateUI.post(updateUIeachSecond);
|
||||
}
|
||||
|
||||
@@ -131,7 +167,8 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
handlerUpdateUI.removeCallbacks(updateUIeachSecond);
|
||||
}
|
||||
@@ -152,6 +189,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection = null;
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,7 +198,11 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
String newCategory = track != null ? track.getCategory() : "";
|
||||
if (!category.equals(newCategory)) {
|
||||
category = newCategory;
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
||||
}
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
@@ -173,33 +215,32 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(TrackPoint trackPoint) {
|
||||
lastTrackPoint = trackPoint;
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
|
||||
lastTrackPoint = trackPoint;
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint newLastTrackPoint) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
synchronized (this) {
|
||||
this.lastTrackPoint = newLastTrackPoint;
|
||||
|
||||
TrackPoint trackPoint = lastTrackPoint; //NOTE: There seems to be a race condition; just fix the symptom for now.
|
||||
if (trackPoint != null && trackPoint.hasLocation()) {
|
||||
boolean hasFix = !LocationUtils.isTrackPointOld(trackPoint);
|
||||
|
||||
if (!hasFix) {
|
||||
lastTrackPoint = null;
|
||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||
this.lastTrackPoint = null;
|
||||
}
|
||||
|
||||
if (this.lastTrackPoint != null && this.lastTrackPoint.hasLocation() && !this.lastTrackPoint.isRecent()) {
|
||||
this.lastTrackPoint = null;
|
||||
}
|
||||
setLocationValues();
|
||||
}
|
||||
setLocationValues();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -272,15 +313,15 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
} else {
|
||||
SensorDataSet sensorDataSet = trackRecordingService.getSensorData();
|
||||
if (sensorDataSet != null) {
|
||||
List<Pair<Integer, SensorData>> sensorDataList = new ArrayList<>();
|
||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
||||
if (sensorDataSet.getHeartRate() != null) {
|
||||
sensorDataList.add(new Pair<>(SensorsAdapter.HEART_RATE_TYPE, sensorDataSet.getHeartRate()));
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getHeartRate()));
|
||||
}
|
||||
if (sensorDataSet.getCyclingCadence() != null) {
|
||||
sensorDataList.add(new Pair<>(SensorsAdapter.CADENCE_TYPE, sensorDataSet.getCyclingCadence()));
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingCadence()));
|
||||
}
|
||||
if(sensorDataSet.getCyclingPower() != null) {
|
||||
sensorDataList.add(new Pair<>(SensorsAdapter.POWER_TYPE, sensorDataSet.getCyclingPower()));
|
||||
if (sensorDataSet.getCyclingPower() != null) {
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingPower()));
|
||||
}
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
setSpeedSensorData(sensorDataSet);
|
||||
@@ -293,8 +334,6 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
// Set elevation gain
|
||||
private void setTotalElevationGain(Float elevationGain_m) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
|
||||
Float totalElevationGain = elevationGain_m;
|
||||
|
||||
if (lastTrackStatistics != null && lastTrackStatistics.hasTotalElevationGain()) {
|
||||
@@ -305,15 +344,13 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
}
|
||||
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationGain, metricUnits);
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationGain, preferenceMetricUnits);
|
||||
viewBinding.statsElevationGainValue.setText(parts.first);
|
||||
viewBinding.statsElevationGainUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation loss
|
||||
private void setTotalElevationLoss(Float elevationLoss_m) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
|
||||
Float totalElevationLoss = elevationLoss_m;
|
||||
|
||||
if (lastTrackStatistics != null && lastTrackStatistics.hasTotalElevationLoss()) {
|
||||
@@ -324,40 +361,33 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
}
|
||||
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationLoss, metricUnits);
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationLoss, preferenceMetricUnits);
|
||||
viewBinding.statsElevationLossValue.setText(parts.first);
|
||||
viewBinding.statsElevationLossUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
private void setSpeedSensorData(SensorDataSet sensorDataSet) {
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
|
||||
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed();
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
|
||||
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
|
||||
|
||||
if (data.hasSpeed_mps() && data.isRecent()) {
|
||||
setSpeed(data.getSpeed_mps());
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
setTotalDistance(data.getValue().distance_overall_m);
|
||||
setSpeed(data.getValue().speed_mps);
|
||||
}
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
setSpeed(data.getValue().speed_mps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStats() {
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||
boolean isRecording = isSelectedTrackRecording();
|
||||
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, metricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
}
|
||||
setTotalDistance(0);
|
||||
|
||||
// Set activity type
|
||||
{
|
||||
viewBinding.statsActivityTypeIcon.setEnabled(isRecording);
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||
viewBinding.statsActivityTypeIcon.setEnabled(isSelectedTrackRecording());
|
||||
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
|
||||
}
|
||||
|
||||
@@ -370,9 +400,9 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageSpeed() : Double.NaN;
|
||||
viewBinding.statsAverageSpeedLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsAverageSpeedValue.setText(parts.first);
|
||||
viewBinding.statsAverageSpeedUnit.setText(parts.second);
|
||||
}
|
||||
@@ -381,9 +411,9 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
{
|
||||
double speed = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getMaxSpeed();
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMaxSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMaxSpeedUnit.setText(parts.second);
|
||||
}
|
||||
@@ -392,18 +422,16 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
{
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMovingSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation gain and loss
|
||||
{
|
||||
// Make elevation visible?
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
viewBinding.statsElevationGroup.setVisibility(preferenceShowElevation ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,46 +448,47 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
|
||||
private void setLocationValues() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
|
||||
// Set speed/pace
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
setSpeed(speed);
|
||||
|
||||
// Set elevation
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
viewBinding.statsElevationGroup.setVisibility(preferenceShowElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showElevation) {
|
||||
if (preferenceShowElevation) {
|
||||
// Current elevation
|
||||
Float altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? (float) lastTrackPoint.getAltitude() : null;
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, preferenceMetricUnits);
|
||||
viewBinding.statsElevationCurrentValue.setText(parts.first);
|
||||
viewBinding.statsElevationCurrentUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||
|
||||
viewBinding.statsCoordinateGroup.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (showCoordinate) {
|
||||
double latitude = lastTrackPoint != null ? lastTrackPoint.getLatitude() : Double.NaN;
|
||||
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
||||
viewBinding.statsCoordinateGroup.setVisibility(preferenceShowCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (preferenceShowCoordinate) {
|
||||
String latitudeText = getContext().getString(R.string.value_unknown);
|
||||
String longitudeText = getContext().getString(R.string.value_unknown);
|
||||
if (lastTrackPoint != null && lastTrackPoint.hasLocation()) {
|
||||
latitudeText = StringUtils.formatCoordinate(lastTrackPoint.getLatitude());
|
||||
longitudeText = StringUtils.formatCoordinate(lastTrackPoint.getLongitude());
|
||||
}
|
||||
viewBinding.statsLatitudeValue.setText(latitudeText);
|
||||
|
||||
double longitude = lastTrackPoint != null ? lastTrackPoint.getLongitude() : Double.NaN;
|
||||
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
|
||||
viewBinding.statsLongitudeValue.setText(longitudeText);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTotalDistance(double sensorDistanceSinceLastTrackpoint) {
|
||||
double totalDistance = lastTrackStatistics != null ? (lastTrackStatistics.getTotalDistance() + sensorDistanceSinceLastTrackpoint) : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
private void setSpeed(double speed) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
viewBinding.statsSpeedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsSpeedValue.setText(parts.first);
|
||||
viewBinding.statsSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
// List of tracks to be exported.
|
||||
private final ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||
private final ArrayList<Track> tracks = new ArrayList<>();
|
||||
|
||||
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
|
||||
private final Handler conflictsHandler = new Handler();
|
||||
@@ -187,7 +187,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
conflictsQueue.clear();
|
||||
trackIds.clear();
|
||||
tracks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -215,11 +215,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
for (int i = from; i < trackExportTotalCount; i++) {
|
||||
cursor.moveToPosition(i);
|
||||
Track track = ContentProviderUtils.createTrack(cursor);
|
||||
trackIds.add(track.getId());
|
||||
tracks.add(track);
|
||||
}
|
||||
|
||||
if (!trackIds.isEmpty()) {
|
||||
export(trackIds.get(0));
|
||||
if (!tracks.isEmpty()) {
|
||||
export(tracks.get(0));
|
||||
} else {
|
||||
onExportEnded();
|
||||
}
|
||||
@@ -227,26 +227,26 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue track identified by trackId to be exported if not exported already or there is a conflict resolution.
|
||||
* Enqueue track identified by UUID to be exported if not exported already or there is a conflict resolution.
|
||||
*
|
||||
* @param trackId the track's id.
|
||||
* @param track Track object.
|
||||
* @param conflictResolution conflict resolution to be applied if needed.
|
||||
*/
|
||||
private void export(Track.Id trackId, int conflictResolution) {
|
||||
boolean fileExists = ExportUtils.isExportFileExists(trackId, trackFileFormat.getExtension(), directoryFiles);
|
||||
private void export(Track track, int conflictResolution) {
|
||||
boolean fileExists = ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles);
|
||||
|
||||
if (fileExists && conflictResolution == CONFLICT_NONE) {
|
||||
conflict(trackId);
|
||||
conflict(track);
|
||||
} else if (fileExists && conflictResolution == CONFLICT_SKIP) {
|
||||
trackExportSkippedCount++;
|
||||
onExportCompleted(trackId);
|
||||
onExportCompleted(track);
|
||||
} else {
|
||||
ExportService.enqueue(this, resultReceiver, trackId, trackFileFormat, directoryUri);
|
||||
ExportService.enqueue(this, resultReceiver, track.getId(), trackFileFormat, directoryUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void export(Track.Id trackId) {
|
||||
export(trackId, autoConflict);
|
||||
private void export(Track track) {
|
||||
export(track, autoConflict);
|
||||
}
|
||||
|
||||
private void setConflictVisibility(int visibility) {
|
||||
@@ -278,14 +278,15 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
viewBinding.exportProgressSummaryErrorsGroup.setVisibility(trackExportErrorCount > 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void onExportCompleted(Track.Id trackId) {
|
||||
trackIds.remove(trackId);
|
||||
private void onExportCompleted(Track track) {
|
||||
tracks.remove(track);
|
||||
|
||||
setProgress();
|
||||
if (trackIds.isEmpty()) {
|
||||
if (tracks.isEmpty()) {
|
||||
onExportEnded();
|
||||
return;
|
||||
}
|
||||
export(trackIds.get(0));
|
||||
export(tracks.get(0));
|
||||
}
|
||||
|
||||
private void onExportEnded() {
|
||||
@@ -317,15 +318,15 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
}
|
||||
|
||||
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
switch (resultCode) {
|
||||
case ExportServiceResultReceiver.RESULT_CODE_ERROR:
|
||||
trackExportErrorCount++;
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
trackErrors.add(track.getName());
|
||||
break;
|
||||
case ExportServiceResultReceiver.RESULT_CODE_SUCCESS:
|
||||
if (ExportUtils.isExportFileExists(trackId, trackFileFormat.getExtension(), directoryFiles)) {
|
||||
if (ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles)) {
|
||||
trackExportOverwrittenCount++;
|
||||
} else {
|
||||
trackExportSuccessCount++;
|
||||
@@ -335,11 +336,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
throw new RuntimeException(TAG + ": export service result code invalid: " + resultCode);
|
||||
}
|
||||
|
||||
onExportCompleted(trackId);
|
||||
onExportCompleted(track);
|
||||
}
|
||||
|
||||
private void conflict(Track.Id trackId) {
|
||||
PendingConflict newConflict = new PendingConflict(trackId);
|
||||
private void conflict(Track track) {
|
||||
PendingConflict newConflict = new PendingConflict(track);
|
||||
conflictsQueue.add(newConflict);
|
||||
|
||||
if (conflictsQueue.size() == 1) {
|
||||
@@ -351,10 +352,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Handle conflicts (exporting file already exists).
|
||||
*/
|
||||
private class PendingConflict {
|
||||
private final Track.Id trackId;
|
||||
private final Track track;
|
||||
|
||||
public PendingConflict(Track.Id trackId) {
|
||||
this.trackId = trackId;
|
||||
public PendingConflict(Track track) {
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,14 +366,13 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
*/
|
||||
public boolean resolve() {
|
||||
if (autoConflict == CONFLICT_NONE) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
viewBinding.exportProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(ExportActivity.this, R.drawable.ic_report_problem_24));
|
||||
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, track.getName()));
|
||||
setConflictVisibility(View.VISIBLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
export(trackId);
|
||||
export(track);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Overwrite the export file and set the autoConflict if user set the "do it for all" switch button.
|
||||
*/
|
||||
public void overwrite() {
|
||||
export(trackId, CONFLICT_OVERWRITE);
|
||||
export(track, CONFLICT_OVERWRITE);
|
||||
|
||||
if (viewBinding.exportProgressApplyToAll.isChecked()) {
|
||||
autoConflict = CONFLICT_OVERWRITE;
|
||||
@@ -391,7 +391,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Skip the export file and set the autoConflict if user set the "do it for all" switch button.
|
||||
*/
|
||||
public void skip() {
|
||||
export(trackId, CONFLICT_SKIP);
|
||||
export(track, CONFLICT_SKIP);
|
||||
|
||||
if (viewBinding.exportProgressApplyToAll.isChecked()) {
|
||||
autoConflict = CONFLICT_SKIP;
|
||||
|
||||
@@ -34,9 +34,10 @@ import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Convert {@link Track} incl. {@link Marker} and {@link TrackPoint} to KML.
|
||||
* Convert {@link Track} incl. {@link Marker} and {@link TrackPoint} to GPX.
|
||||
* NOTE:
|
||||
* * does not export {@link TrackPoint} without a latitude/longitude (not supported by GPX 1.1).
|
||||
* * cannot export multiple {@link Track}s - all {@link TrackPoint}s are exported as if they would belong to the first track.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
@@ -48,8 +49,10 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat SPEED_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat DISTANCE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat CADENCE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat POWER_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
|
||||
static {
|
||||
/*
|
||||
@@ -71,6 +74,9 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
CADENCE_FORMAT.setMaximumFractionDigits(0);
|
||||
CADENCE_FORMAT.setGroupingUsed(false);
|
||||
|
||||
POWER_FORMAT.setMaximumFractionDigits(0);
|
||||
POWER_FORMAT.setGroupingUsed(false);
|
||||
}
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
@@ -85,13 +91,22 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
try {
|
||||
prepare(outputStream);
|
||||
writeHeader(track);
|
||||
writeHeader();
|
||||
|
||||
writeMarkers(track);
|
||||
for (Track track : tracks) {
|
||||
writeMarkers(track);
|
||||
}
|
||||
|
||||
writeLocations(track);
|
||||
for (Track track : tracks) {
|
||||
writeTrackPoints(track);
|
||||
}
|
||||
|
||||
writeFooter();
|
||||
close();
|
||||
@@ -103,12 +118,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private void writeLocations(Track track) throws InterruptedException {
|
||||
private void writeTrackPoints(Track track) throws InterruptedException {
|
||||
boolean wroteTrack = false;
|
||||
boolean wroteSegment = false;
|
||||
|
||||
@@ -119,7 +129,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
|
||||
if (!wroteTrack) {
|
||||
writeBeginTrack(track, trackPoint);
|
||||
writeBeginTrack(track);
|
||||
wroteTrack = true;
|
||||
}
|
||||
|
||||
@@ -158,14 +168,12 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
writeCloseSegment();
|
||||
}
|
||||
|
||||
if (wroteTrack) {
|
||||
TrackPoint lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
|
||||
writeEndTrack(track, lastValidTrackPoint);
|
||||
} else {
|
||||
if (!wroteTrack) {
|
||||
// Write an empty track
|
||||
writeBeginTrack(track, null);
|
||||
writeEndTrack(track, null);
|
||||
writeBeginTrack(track);
|
||||
}
|
||||
|
||||
writeEndTrack();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +189,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
|
||||
public void writeHeader(Track track) {
|
||||
public void writeHeader() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
printWriter.println("<gpx");
|
||||
@@ -193,17 +201,13 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
|
||||
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\"");
|
||||
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
|
||||
printWriter.println("xmlns:pwr=\"http://www.garmin.com/xmlschemas/PowerExtension/v1\"");
|
||||
printWriter.println("xsi:schemaLocation=" +
|
||||
"\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
|
||||
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
|
||||
+ " http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd"
|
||||
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
|
||||
|
||||
printWriter.println("<metadata>");
|
||||
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
printWriter.println("</metadata>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +247,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBeginTrack(Track track, TrackPoint startTrackPoint) {
|
||||
public void writeBeginTrack(Track track) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<trk>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
@@ -257,7 +261,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeEndTrack(Track track, TrackPoint endTrackPoint) {
|
||||
public void writeEndTrack() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</trk>");
|
||||
}
|
||||
@@ -297,6 +301,10 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasPower()) {
|
||||
printWriter.println("<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower()) + "</pwr:PowerInWatts>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasElevationGain()) {
|
||||
printWriter.println("<opentracks:gain>" + ELEVATION_FORMAT.format(trackPoint.getElevationGain()) + "</opentracks:gain>");
|
||||
}
|
||||
@@ -305,6 +313,10 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<opentracks:loss>" + ELEVATION_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:loss>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
printWriter.println("<opentracks:distance>" + DISTANCE_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:distance>");
|
||||
}
|
||||
|
||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -54,12 +53,11 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
private static final String TAG = KMLTrackExporter.class.getSimpleName();
|
||||
|
||||
public static final String MARKER_STYLE = "waypoint";
|
||||
private static final String START_STYLE = "start";
|
||||
private static final String END_STYLE = "end";
|
||||
private static final String TRACK_STYLE = "track";
|
||||
private static final String SCHEMA_ID = "schema";
|
||||
|
||||
public static final String EXTENDED_DATA_TYPE_SPEED = "speed";
|
||||
public static final String EXTENDED_DATA_TYPE_DISTANCE = "distance";
|
||||
public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence";
|
||||
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
||||
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
||||
@@ -67,25 +65,24 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
public static final String EXTENDED_DATA_TYPE_ELEVATION_LOSS = "elevation_loss";
|
||||
|
||||
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
||||
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
|
||||
private static final String END_ICON = "http://maps.google.com/mapfiles/kml/paddle/red-circle.png";
|
||||
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
|
||||
|
||||
private final Context context;
|
||||
private final boolean exportPhotos;
|
||||
private final boolean exportTrackDetail;
|
||||
private final boolean exportSensorData;
|
||||
private final DescriptionGenerator descriptionGenerator;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private PrintWriter printWriter;
|
||||
private final List<Float> speedList = new ArrayList<>();
|
||||
private final List<Float> distanceList = new ArrayList<>();
|
||||
private final List<Float> powerList = new ArrayList<>();
|
||||
private final List<Float> cadenceList = new ArrayList<>();
|
||||
private final List<Float> heartRateList = new ArrayList<>();
|
||||
private final List<Float> elevationGainList = new ArrayList<>();
|
||||
private final List<Float> elevationLossList = new ArrayList<>();
|
||||
|
||||
@Deprecated // Figure out a better way to do this! (if needed)
|
||||
private TrackPoint startTrackPoint;
|
||||
|
||||
public KMLTrackExporter(Context context, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) {
|
||||
@@ -93,7 +90,6 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
this.exportTrackDetail = exportTrackDetail;
|
||||
this.exportSensorData = exportSensorData;
|
||||
this.exportPhotos = exportPhotos;
|
||||
this.descriptionGenerator = new DescriptionGenerator(context);
|
||||
this.contentProviderUtils = new ContentProviderUtils(context);
|
||||
}
|
||||
|
||||
@@ -165,9 +161,12 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
setLocationTime(trackPoint, offset);
|
||||
if (startTrackPoint == null) {
|
||||
startTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
if (!wroteTrack) {
|
||||
writeBeginTrack(track, trackPoint);
|
||||
writeBeginTrack(track);
|
||||
wroteTrack = true;
|
||||
}
|
||||
|
||||
@@ -202,15 +201,14 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
writeCloseSegment();
|
||||
}
|
||||
|
||||
if (wroteTrack) {
|
||||
TrackPoint lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
|
||||
setLocationTime(lastValidTrackPoint, offset);
|
||||
writeEndTrack(track, lastValidTrackPoint);
|
||||
} else {
|
||||
if (!wroteTrack) {
|
||||
// Write an empty track
|
||||
writeBeginTrack(track, null);
|
||||
writeEndTrack(track, null);
|
||||
writeBeginTrack(track);
|
||||
}
|
||||
|
||||
writeEndTrack();
|
||||
|
||||
startTrackPoint = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,8 +245,6 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
writeTrackStyle();
|
||||
writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1);
|
||||
writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1);
|
||||
writePlacemarkerStyle(MARKER_STYLE, MARKER_ICON, 20, 2);
|
||||
printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">");
|
||||
|
||||
@@ -312,12 +308,8 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBeginTrack(Track track, TrackPoint startTrackPoint) {
|
||||
this.startTrackPoint = startTrackPoint;
|
||||
private void writeBeginTrack(Track track) {
|
||||
if (printWriter != null) {
|
||||
String name = context.getString(R.string.marker_label_start, track.getName());
|
||||
Location location = startTrackPoint != null ? startTrackPoint.getLocation() : null;
|
||||
writePlacemark(name, "", "", START_STYLE, location);
|
||||
printWriter.println("<Placemark>");
|
||||
|
||||
if (exportTrackDetail) {
|
||||
@@ -336,17 +328,10 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
|
||||
private void writeEndTrack(Track track, TrackPoint endTrackPoint) {
|
||||
private void writeEndTrack() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</gx:MultiTrack>");
|
||||
printWriter.println("</Placemark>");
|
||||
|
||||
if (exportTrackDetail) {
|
||||
String name = context.getString(R.string.marker_label_end, track.getName());
|
||||
String description = descriptionGenerator.generateTrackDescription(track, false);
|
||||
Location location = endTrackPoint != null ? endTrackPoint.getLocation() : null;
|
||||
writePlacemark(name, "", description, END_STYLE, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +340,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<gx:Track>");
|
||||
speedList.clear();
|
||||
distanceList.clear();
|
||||
powerList.clear();
|
||||
cadenceList.clear();
|
||||
heartRateList.clear();
|
||||
@@ -372,6 +358,9 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED);
|
||||
}
|
||||
if (exportSensorData) {
|
||||
if (distanceList.stream().anyMatch(Objects::nonNull)) {
|
||||
writeSimpleArrayData(distanceList, EXTENDED_DATA_TYPE_DISTANCE);
|
||||
}
|
||||
if (powerList.stream().anyMatch(Objects::nonNull)) {
|
||||
writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER);
|
||||
}
|
||||
@@ -409,6 +398,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null);
|
||||
|
||||
if (exportSensorData) {
|
||||
distanceList.add(trackPoint.hasSensorDistance() ? trackPoint.getSensorDistance() : null);
|
||||
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
|
||||
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
|
||||
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
|
||||
|
||||
+60
-26
@@ -17,10 +17,13 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
@@ -88,6 +91,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
protected String uuid;
|
||||
protected String gain;
|
||||
protected String loss;
|
||||
protected String distance;
|
||||
|
||||
// The current track data
|
||||
private TrackData trackData;
|
||||
@@ -95,10 +99,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
// The SAX locator to get the current line information
|
||||
private Locator locator;
|
||||
|
||||
private final SharedPreferences sharedPreferences;
|
||||
|
||||
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
||||
this.context = context;
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,11 +125,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
}
|
||||
|
||||
@Override
|
||||
public Track.Id importFile(InputStream inputStream) {
|
||||
@NonNull
|
||||
public List<Track.Id> importFile(InputStream inputStream) {
|
||||
try {
|
||||
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, this);
|
||||
return trackIds.get(0);
|
||||
} catch (IOException | SAXException | ParserConfigurationException e) {
|
||||
return trackIds;
|
||||
} catch (IOException | SAXException | ParserConfigurationException | ParsingException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
if (trackIds.size() > 0) {
|
||||
cleanImport();
|
||||
@@ -134,9 +142,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On file end.
|
||||
*/
|
||||
protected void onFileEnd() {
|
||||
// Add markers to the last imported track
|
||||
int size = trackIds.size();
|
||||
@@ -259,7 +264,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackData.track.getUuid());
|
||||
if (track != null) {
|
||||
if (PreferencesUtils.getPreventReimportTracks(context)) {
|
||||
if (PreferencesUtils.getPreventReimportTracks(sharedPreferences, context)) {
|
||||
throw new ImportAlreadyExistsException(context.getString(R.string.import_prevent_reimport));
|
||||
}
|
||||
|
||||
@@ -304,7 +309,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
}
|
||||
}
|
||||
|
||||
protected void addMarker() throws SAXException {
|
||||
protected void addMarker() throws ParsingException {
|
||||
// Markers must have a time, else cannot match to the track points
|
||||
if (time == null) {
|
||||
Log.w(TAG, "Marker without time ignored.");
|
||||
@@ -335,7 +340,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
markers.add(marker);
|
||||
}
|
||||
|
||||
protected TrackPoint getTrackPoint() throws SAXException {
|
||||
protected TrackPoint getTrackPoint() throws ParsingException {
|
||||
TrackPoint trackPoint = createTrackPoint();
|
||||
|
||||
if (trackPoint.hasLocation()) {
|
||||
@@ -350,7 +355,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
trackPoint.setTime(time);
|
||||
|
||||
} else if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
|
||||
throw new ParsingException(createErrorMessage("Invalid location detected: " + trackPoint));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +374,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
||||
float speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / timeDifference.toMillis();
|
||||
float speed = trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint) / timeDifference.toMillis();
|
||||
trackPoint.setSpeed(speed);
|
||||
}
|
||||
}
|
||||
@@ -377,8 +382,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
||||
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
|
||||
|
||||
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
|
||||
double distanceToLastTrackLocation = trackPoint.distanceTo(trackData.lastLocationInCurrentSegment);
|
||||
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
|
||||
double distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
|
||||
if (distanceToLastTrackLocation > maxRecordingDistance) {
|
||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
}
|
||||
@@ -424,16 +429,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*/
|
||||
private TrackPoint createTrackPoint() throws SAXException {
|
||||
private TrackPoint createTrackPoint() throws ParsingException {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
|
||||
try {
|
||||
trackPoint.setTime(StringUtils.parseTime(time));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
|
||||
if (latitude == null || longitude == null) {
|
||||
@@ -444,7 +446,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
trackPoint.setLatitude(Double.parseDouble(latitude));
|
||||
trackPoint.setLongitude(Double.parseDouble(longitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||
}
|
||||
|
||||
|
||||
@@ -452,7 +454,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
try {
|
||||
trackPoint.setAltitude(Double.parseDouble(altitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,14 +462,14 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
try {
|
||||
trackPoint.setSpeed(Float.parseFloat(speed));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||
}
|
||||
}
|
||||
if (heartrate != null) {
|
||||
try {
|
||||
trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +477,15 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
try {
|
||||
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (power != null) {
|
||||
try {
|
||||
trackPoint.setPower(Float.parseFloat(power));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,14 +493,21 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
try {
|
||||
trackPoint.setElevationGain(Float.parseFloat(gain));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation gain: %s", gain)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation gain: %s", gain)), e);
|
||||
}
|
||||
}
|
||||
if (loss != null) {
|
||||
try {
|
||||
trackPoint.setElevationLoss(Float.parseFloat(loss));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation loss: %s", loss)), e);
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation loss: %s", loss)), e);
|
||||
}
|
||||
}
|
||||
if (distance != null) {
|
||||
try {
|
||||
trackPoint.setSensorDistance(Float.parseFloat(distance));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,4 +563,21 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
// The buffered locations
|
||||
final List<TrackPoint> bufferedTrackPoints = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static class ParsingException extends RuntimeException {
|
||||
|
||||
private ParsingException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
private ParsingException(@NonNull String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-6
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
@@ -18,10 +19,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
|
||||
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
||||
|
||||
protected SharedPreferences sharedPreferences;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
|
||||
if (directoryUri == null) {
|
||||
@@ -38,6 +43,12 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
onActivityResultCustom(requestCode, resultCode, resultData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
@@ -80,14 +91,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
super.configureDirectoryChooserIntent(intent);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
|
||||
return PreferencesUtils.getDefaultExportDirectoryUri(this);
|
||||
return PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent createNextActivityIntent(Uri directoryUri) {
|
||||
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
||||
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
|
||||
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(this));
|
||||
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(sharedPreferences, this));
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
@@ -101,11 +112,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
case RESULT_OK:
|
||||
Uri directoryUri = resultData.getData();
|
||||
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(this, directoryUri);
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, directoryUri);
|
||||
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), resultData, directoryUri);
|
||||
break;
|
||||
case RESULT_CANCELED:
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(this, null);
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, null);
|
||||
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
|
||||
break;
|
||||
}
|
||||
@@ -118,8 +129,8 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
||||
super.configureDirectoryChooserIntent(intent);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
if (PreferencesUtils.isDefaultExportDirectoryUri(this)) {
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri());
|
||||
if (PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, this)) {
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this).getUri());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
/**
|
||||
* Imports a GPX file.
|
||||
* Uses https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd
|
||||
* Uses:
|
||||
* * https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd
|
||||
* * https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd
|
||||
* <p>
|
||||
* {@link de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter} does not export information if a segment was started automatic or manually.
|
||||
* Therefore, all segments starts are marked as SEGMENT_START_AUTOMATIC.
|
||||
@@ -57,9 +59,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
||||
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
|
||||
|
||||
private static final String TAG_EXTENSION_GAIN = "opentracks:gain";
|
||||
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
|
||||
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -156,6 +160,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
cadence = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_POWER:
|
||||
if (content != null) {
|
||||
power = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ID:
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
@@ -171,6 +180,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
loss = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_DISTANCE:
|
||||
if (content != null) {
|
||||
distance = content.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
@@ -196,11 +210,12 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
altitude = null;
|
||||
time = null;
|
||||
speed = null;
|
||||
power = null;
|
||||
gain = null;
|
||||
loss = null;
|
||||
}
|
||||
|
||||
private void onTrackPointEnd() throws SAXException {
|
||||
private void onTrackPointEnd() {
|
||||
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
|
||||
TrackPoint trackPoint = getTrackPoint();
|
||||
if (isFirstTrackPointInSegment) {
|
||||
@@ -221,7 +236,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
markerType = null;
|
||||
}
|
||||
|
||||
private void onMarkerEnd() throws SAXException {
|
||||
private void onMarkerEnd() {
|
||||
addMarker();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -30,6 +31,10 @@ import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
|
||||
@@ -47,17 +52,15 @@ public class ImportActivity extends FragmentActivity {
|
||||
|
||||
public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
|
||||
|
||||
private static final String BUNDLE_TOOLBAR_TITLE = "toolbar_title";
|
||||
private static final String BUNDLE_DOCUMENT_URI = "document_uri";
|
||||
private static final String BUNDLE_DOCUMENT_URIS = "document_uris";
|
||||
private static final String BUNDLE_IS_DIRECTORY = "is_directory";
|
||||
|
||||
private ImportActivityBinding viewBinding;
|
||||
|
||||
boolean doubleBackToCancel = false;
|
||||
|
||||
private Uri documentUri;
|
||||
private ArrayList<Uri> documentUris = new ArrayList<>();
|
||||
private boolean isDirectory;
|
||||
private String toolbarTitle;
|
||||
|
||||
private ImportViewModel viewModel;
|
||||
private ImportViewModel.Summary summary;
|
||||
@@ -70,35 +73,46 @@ public class ImportActivity extends FragmentActivity {
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
|
||||
final DocumentFile documentFile;
|
||||
final Intent intent = getIntent();
|
||||
final ClipData intentClipData = intent.getClipData();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (getIntent().getData() != null) {
|
||||
documentUri = getIntent().getData();
|
||||
isDirectory = false;
|
||||
} else if (getIntent().getClipData() != null && getIntent().getClipData().getItemCount() > 0) {
|
||||
documentUri = getIntent().getClipData().getItemAt(0).getUri();
|
||||
if (intent.getData() != null) {
|
||||
documentUris.add(intent.getData());
|
||||
isDirectory = false;
|
||||
} else {
|
||||
// Started from DirectoryChooserActivity
|
||||
documentUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||
isDirectory = true;
|
||||
if (intentClipData != null && intentClipData.getItemCount() > 0) {
|
||||
for (int i = 0; i < intentClipData.getItemCount(); i++) {
|
||||
documentUris.add(intentClipData.getItemAt(i).getUri());
|
||||
}
|
||||
isDirectory = false;
|
||||
} else {
|
||||
// Started from DirectoryChooserActivity
|
||||
documentUris.add(intent.getParcelableExtra(EXTRA_DIRECTORY_URI_KEY));
|
||||
isDirectory = true;
|
||||
}
|
||||
}
|
||||
|
||||
documentFile = isDirectory ? DocumentFile.fromTreeUri(this, documentUri) : DocumentFile.fromSingleUri(this, documentUri);
|
||||
toolbarTitle = getString(R.string.import_progress_message, documentFile.getName());
|
||||
} else {
|
||||
documentUri = savedInstanceState.getParcelable(BUNDLE_DOCUMENT_URI);
|
||||
toolbarTitle = savedInstanceState.getString(BUNDLE_TOOLBAR_TITLE);
|
||||
documentUris = savedInstanceState.getParcelable(BUNDLE_DOCUMENT_URIS);
|
||||
isDirectory = savedInstanceState.getBoolean(BUNDLE_IS_DIRECTORY);
|
||||
|
||||
documentFile = isDirectory ? DocumentFile.fromTreeUri(this, documentUri) : DocumentFile.fromSingleUri(this, documentUri);
|
||||
}
|
||||
|
||||
toolbar.setTitle(toolbarTitle);
|
||||
final List<DocumentFile> documentFiles;
|
||||
if (isDirectory) {
|
||||
documentFiles = new ArrayList<>();
|
||||
documentFiles.add(DocumentFile.fromTreeUri(this, documentUris.get(0)));
|
||||
} else {
|
||||
documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
//Works for a directory, but we might have received multiple files via SEND_MULTIPLE.
|
||||
toolbar.setTitle(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
|
||||
|
||||
initViews();
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(ImportViewModel.class);
|
||||
viewModel.getImportData(documentFile).observe(this, data -> {
|
||||
viewModel.getImportData(documentFiles).observe(this, data -> {
|
||||
summary = data;
|
||||
setProgress();
|
||||
});
|
||||
@@ -107,9 +121,8 @@ public class ImportActivity extends FragmentActivity {
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(BUNDLE_DOCUMENT_URI, documentUri);
|
||||
outState.putParcelableArrayList(BUNDLE_DOCUMENT_URIS, documentUris);
|
||||
outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory);
|
||||
outState.putString(BUNDLE_TOOLBAR_TITLE, toolbarTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -57,33 +58,34 @@ public class ImportService extends JobIntentService {
|
||||
trackImporter = new KmzTrackImporter(this, file.getUri());
|
||||
} else {
|
||||
Log.d(TAG, "Unsupported file format.");
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file.getName(), getString(R.string.import_unsupported_format));
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
||||
return;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = getContentResolver().openInputStream(file.getUri())) {
|
||||
Track.Id trackId = trackImporter.importFile(inputStream);
|
||||
if (trackId != null) {
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackId, file.getName(), getString(R.string.import_file_imported, file.getName()));
|
||||
ArrayList<Track.Id> trackIds = new ArrayList<>(trackImporter.importFile(inputStream));
|
||||
|
||||
if (!trackIds.isEmpty()) {
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName()));
|
||||
} else {
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackId, file.getName(), getString(R.string.import_unable_to_import_file, file.getName()));
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackIds, file, getString(R.string.import_unable_to_import_file, file.getName()));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to import file", e);
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file.getName(), getString(R.string.import_unable_to_import_file, e.getMessage()));
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unable_to_import_file, e.getMessage()));
|
||||
} catch (ImportParserException e) {
|
||||
Log.d(TAG, "Parser error: " + e.getMessage(), e);
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file.getName(), getString(R.string.import_parser_error, e.getMessage()));
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_parser_error, e.getMessage()));
|
||||
} catch (ImportAlreadyExistsException e) {
|
||||
Log.d(TAG, "Track already exists: " + e.getMessage(), e);
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS, null, file.getName(), e.getMessage());
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS, null, file, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendResult(int resultCode, Track.Id trackId, String fileName, String message) {
|
||||
private void sendResult(int resultCode, ArrayList<Track.Id> trackId, DocumentFile file, String message) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ImportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId);
|
||||
bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME, fileName);
|
||||
bundle.putParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID, trackId);
|
||||
bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME, file.getName());
|
||||
bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE, message);
|
||||
resultReceiver.send(resultCode, bundle);
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ public class ImportServiceResultReceiver extends ResultReceiver {
|
||||
public static final int RESULT_CODE_IMPORTED = 1;
|
||||
public static final int RESULT_CODE_ALREADY_EXISTS = 2;
|
||||
|
||||
public static final String RESULT_EXTRA_TRACK_ID = "result_track_id";
|
||||
public static final String RESULT_EXTRA_LIST_TRACK_ID = "result_track_id";
|
||||
public static final String RESULT_EXTRA_FILENAME = "result_extra_filename";
|
||||
public static final String RESULT_EXTRA_MESSAGE = "result_extra_message";
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -33,20 +35,20 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
||||
summary = new Summary();
|
||||
}
|
||||
|
||||
public LiveData<Summary> getImportData(DocumentFile documentFile) {
|
||||
LiveData<Summary> getImportData(List<DocumentFile> documentFiles) {
|
||||
if (importData == null) {
|
||||
importData = new MutableLiveData<>();
|
||||
loadData(documentFile);
|
||||
loadData(documentFiles);
|
||||
}
|
||||
return importData;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
void cancel() {
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
private void loadData(DocumentFile documentFile) {
|
||||
List<DocumentFile> fileList = FileUtils.getFiles(documentFile);
|
||||
private void loadData(List<DocumentFile> documentFiles) {
|
||||
List<DocumentFile> fileList = documentFiles.stream().map(FileUtils::getFiles).flatMap(Collection::stream).collect(Collectors.toList());
|
||||
summary.totalCount = fileList.size();
|
||||
filesToImport.addAll(fileList);
|
||||
importNextFile();
|
||||
@@ -66,7 +68,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
||||
throw new RuntimeException(TAG + ": onReceiveResult resultData NULL");
|
||||
}
|
||||
|
||||
Track.Id trackId = resultData.getParcelable(ImportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
||||
ArrayList<Track.Id> trackIds = resultData.getParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID);
|
||||
String fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME);
|
||||
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
|
||||
|
||||
@@ -76,7 +78,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
||||
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
|
||||
break;
|
||||
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED:
|
||||
summary.importedTrackIds.add(trackId);
|
||||
summary.importedTrackIds.addAll(trackIds);
|
||||
summary.successCount++;
|
||||
break;
|
||||
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
|
||||
@@ -90,12 +92,12 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
||||
importNextFile();
|
||||
}
|
||||
|
||||
public static class Summary {
|
||||
static class Summary {
|
||||
private int totalCount;
|
||||
private int successCount;
|
||||
private int existsCount;
|
||||
private int errorCount;
|
||||
private ArrayList<Track.Id> importedTrackIds = new ArrayList<>();
|
||||
private final ArrayList<Track.Id> importedTrackIds = new ArrayList<>();
|
||||
private final ArrayList<String> fileErrors = new ArrayList<>();
|
||||
|
||||
public int getTotalCount() {
|
||||
|
||||
@@ -35,6 +35,7 @@ import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
//TODO Do not use AbstractFileTrackImporter as TrackPoint sensor generation is done differently here.
|
||||
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
|
||||
@@ -65,6 +66,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private String extendedDataType;
|
||||
private final ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||
private final ArrayList<Float> speedList = new ArrayList<>();
|
||||
private final ArrayList<Float> distanceList = new ArrayList<>();
|
||||
private final ArrayList<Float> cadenceList = new ArrayList<>();
|
||||
private final ArrayList<Float> heartRateList = new ArrayList<>();
|
||||
private final ArrayList<Float> powerList = new ArrayList<>();
|
||||
@@ -215,6 +217,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
super.onTrackSegmentStart();
|
||||
trackPoints.clear();
|
||||
speedList.clear();
|
||||
distanceList.clear();
|
||||
heartRateList.clear();
|
||||
cadenceList.clear();
|
||||
powerList.clear();
|
||||
@@ -231,6 +234,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
if (i < speedList.size()) {
|
||||
trackPoint.setSpeed(speedList.get(i));
|
||||
}
|
||||
if (i < distanceList.size()) {
|
||||
trackPoint.setSensorDistance(distanceList.get(i));
|
||||
}
|
||||
if (i < heartRateList.size()) {
|
||||
trackPoint.setHeartRate_bpm(heartRateList.get(i));
|
||||
}
|
||||
@@ -309,6 +315,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED:
|
||||
speedList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE:
|
||||
distanceList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER:
|
||||
powerList.add(value);
|
||||
break;
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -66,17 +68,23 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Track.Id importFile(InputStream inputStream) {
|
||||
Track.Id trackId = findAndParseKmlFile(inputStream);
|
||||
public @NonNull
|
||||
List<Track.Id> importFile(InputStream inputStream) {
|
||||
List<Track.Id> trackIds = findAndParseKmlFile(inputStream);
|
||||
|
||||
if (!copyKmzImages(trackId)) {
|
||||
cleanImport(context, trackId);
|
||||
return null;
|
||||
ArrayList<Track.Id> trackIdsWithImages = new ArrayList<>();
|
||||
|
||||
for (Track.Id trackId : trackIds) {
|
||||
if (copyKmzImages(trackId)) {
|
||||
trackIdsWithImages.add(trackId);
|
||||
deleteOrphanImages(context, trackId);
|
||||
} else {
|
||||
cleanImport(context, trackId);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
deleteOrphanImages(context, trackId);
|
||||
|
||||
return trackId;
|
||||
return trackIdsWithImages;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,14 +158,13 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
|
||||
/**
|
||||
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it.
|
||||
* TODO: May load multiple tracks, but only returns the last Track.Id.
|
||||
*
|
||||
* @param inputStream kmz input stream.
|
||||
*/
|
||||
private Track.Id findAndParseKmlFile(InputStream inputStream) {
|
||||
private List<Track.Id> findAndParseKmlFile(InputStream inputStream) {
|
||||
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||
ZipEntry zipEntry;
|
||||
Track.Id trackId = null;
|
||||
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||
|
||||
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||
if (Thread.interrupted()) {
|
||||
@@ -167,20 +174,21 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
|
||||
String fileName = zipEntry.getName();
|
||||
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
|
||||
trackId = parseKml(zipInputStream);
|
||||
if (trackId == null) {
|
||||
List<Track.Id> trackId = parseKml(zipInputStream);
|
||||
if (trackId.isEmpty()) {
|
||||
Log.d(TAG, "Unable to parse kml in kmz");
|
||||
throw new ImportParserException(context.getString(R.string.import_unable_to_import_file, fileName));
|
||||
}
|
||||
trackIds.addAll(trackId);
|
||||
}
|
||||
|
||||
zipInputStream.closeEntry();
|
||||
}
|
||||
if (trackId == null) {
|
||||
if (trackIds.isEmpty()) {
|
||||
Log.d(TAG, "Unable to find doc.kml in kmz");
|
||||
throw new ImportParserException(context.getString(R.string.import_no_kml_file_found));
|
||||
}
|
||||
return trackId;
|
||||
return trackIds;
|
||||
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
throw e;
|
||||
@@ -234,13 +242,7 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses kml
|
||||
*
|
||||
* @param zipInputStream the zip input stream
|
||||
* @return the imported track id or -1L
|
||||
*/
|
||||
private Track.Id parseKml(ZipInputStream zipInputStream) {
|
||||
private List<Track.Id> parseKml(ZipInputStream zipInputStream) {
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
|
||||
|
||||
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
|
||||
@@ -30,8 +33,7 @@ public interface TrackImporter {
|
||||
* Import a file.
|
||||
*
|
||||
* @param inputStream the file's input stream
|
||||
* @return the imported track id or null.
|
||||
*/
|
||||
//TODO Figure out how can make the import an atomic operation (incl. database transaction rollback).
|
||||
Track.Id importFile(InputStream inputStream);
|
||||
@NonNull
|
||||
List<Track.Id> importFile(InputStream inputStream) throws ImportParserException;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
import de.dennisguse.opentracks.util.TrackPointUtils;
|
||||
|
||||
/**
|
||||
* A background service that registers a location listener and records track points.
|
||||
@@ -83,39 +82,40 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
private PeriodicTaskExecutor voiceExecutor;
|
||||
private TrackRecordingServiceNotificationManager notificationManager;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private Track.Id recordingTrackId;
|
||||
private boolean recordingTrackPaused;
|
||||
private int recordingDistanceInterval;
|
||||
private int maxRecordingDistance;
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
Context context = TrackRecordingService.this;
|
||||
if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) {
|
||||
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
|
||||
if (PreferencesUtils.isRecording(TrackRecordingService.this)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingService.this);
|
||||
if (PreferencesUtils.isRecording(sharedPreferences, TrackRecordingService.this)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackRecordingService.this);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
||||
voiceExecutor.setMetricUnits(metricUnits);
|
||||
notificationManager.setMetricUnits(metricUnits);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(sharedPreferences, context));
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
|
||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
|
||||
}
|
||||
|
||||
handlerServer.onSharedPreferenceChanged(context, preferences, key);
|
||||
handlerServer.onSharedPreferenceChanged(context, sharedPreferences, key);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,8 +148,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
// onSharedPreferenceChanged might not set recordingTrackId.
|
||||
recordingTrackId = null;
|
||||
|
||||
PreferencesUtils.register(this, sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
restartTrackAfterServiceRestart();
|
||||
}
|
||||
@@ -190,8 +191,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
// Reverse order from onCreate
|
||||
showNotification(false); //TODO Why?
|
||||
|
||||
PreferencesUtils.unregister(this, sharedPreferenceChangeListener);
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferences = null;
|
||||
|
||||
try {
|
||||
voiceExecutor.shutdown();
|
||||
@@ -306,7 +307,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
//TODO Pass TrackPoint
|
||||
track.setName(TrackNameUtils.getTrackName(this, trackId, segmentStartTrackPoint.getTime()));
|
||||
|
||||
String category = PreferencesUtils.getDefaultActivity(this);
|
||||
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, this);
|
||||
track.setCategory(category);
|
||||
track.setIcon(TrackIconUtils.getIconValue(this, category));
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
@@ -537,9 +538,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
private void updateRecordingState(Track.Id trackId, boolean paused) {
|
||||
recordingTrackId = trackId;
|
||||
long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
PreferencesUtils.setLong(this, R.string.recording_track_id_key, currentTrackId);
|
||||
PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, currentTrackId);
|
||||
recordingTrackPaused = paused;
|
||||
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, this, R.string.recording_track_paused_key, recordingTrackPaused);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -557,7 +558,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
|
||||
fillWithSensorDataSet(trackPoint);
|
||||
|
||||
notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy);
|
||||
notificationManager.updateTrackPoint(this, track.getTrackStatistics(), trackPoint, recordingGpsAccuracy);
|
||||
|
||||
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
@@ -571,7 +572,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return;
|
||||
}
|
||||
|
||||
double distanceToLastTrackLocation = trackPoint.distanceTo(lastValidTrackPoint);
|
||||
double distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
|
||||
if (distanceToLastTrackLocation > maxRecordingDistance) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
@@ -594,7 +595,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isIdle && !TrackPointUtils.isMoving(trackPoint)) {
|
||||
if (!isIdle && !trackPoint.isMoving()) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
@@ -605,7 +606,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIdle && TrackPointUtils.isMoving(trackPoint)) {
|
||||
if (isIdle && trackPoint.isMoving()) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
@@ -664,6 +665,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
trackPoint.setElevationLoss(elevationSumManager.getElevationLoss_m());
|
||||
elevationSumManager.reset();
|
||||
}
|
||||
if (remoteSensorManager != null) {
|
||||
fillWithSensorDataSet(trackPoint);
|
||||
remoteSensorManager.reset();
|
||||
}
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
|
||||
|
||||
+4
-2
@@ -20,6 +20,7 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.IBinder;
|
||||
import android.os.IBinder.DeathRecipient;
|
||||
import android.os.RemoteException;
|
||||
@@ -209,9 +210,10 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
try {
|
||||
if (showEditor) {
|
||||
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
|
||||
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context);
|
||||
trackRecordingService.endCurrentTrack();
|
||||
if (PreferencesUtils.isRecording(context)) {
|
||||
if (PreferencesUtils.isRecording(sharedPreferences, context)) {
|
||||
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId)
|
||||
.putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true);
|
||||
|
||||
+13
-5
@@ -12,7 +12,7 @@ import androidx.core.app.NotificationCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -30,6 +30,8 @@ class TrackRecordingServiceNotificationManager {
|
||||
|
||||
private boolean previousLocationWasAccurate = true;
|
||||
|
||||
private Boolean metricUnits = null;
|
||||
|
||||
TrackRecordingServiceNotificationManager(Context context) {
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -59,14 +61,14 @@ class TrackRecordingServiceNotificationManager {
|
||||
}
|
||||
|
||||
void updateContent(String content) {
|
||||
notificationBuilder.setContentText(content);
|
||||
notificationBuilder.setSubText(content);
|
||||
updateNotification();
|
||||
}
|
||||
|
||||
void updateTrackPoint(Context context, TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
void updateTrackPoint(Context context, TrackStatistics trackStatistics, TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
String formattedAccuracy = context.getString(R.string.value_none);
|
||||
if (trackPoint.hasAccuracy()) {
|
||||
formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getAccuracy(), PreferencesUtils.isMetricUnits(context));
|
||||
formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getAccuracy(), metricUnits);
|
||||
|
||||
boolean currentLocationWasAccurate = trackPoint.getAccuracy() < recordingGpsAccuracy;
|
||||
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
|
||||
@@ -74,7 +76,9 @@ class TrackRecordingServiceNotificationManager {
|
||||
previousLocationWasAccurate = currentLocationWasAccurate;
|
||||
}
|
||||
|
||||
notificationBuilder.setContentText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
|
||||
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, StringUtils.formatDistance(context, trackStatistics.getTotalDistance(), metricUnits)));
|
||||
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, StringUtils.formatSpeed(context, trackPoint.getSpeed(), metricUnits, true)));
|
||||
notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
|
||||
updateNotification();
|
||||
|
||||
notificationBuilder.setOnlyAlertOnce(true);
|
||||
@@ -93,6 +97,10 @@ class TrackRecordingServiceNotificationManager {
|
||||
return notificationBuilder.build();
|
||||
}
|
||||
|
||||
void setMetricUnits(boolean metricUnits) {
|
||||
this.metricUnits = metricUnits;
|
||||
}
|
||||
|
||||
private void updateNotification() {
|
||||
notificationManager.notify(NOTIFICATION_ID, getNotification());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Handler;
|
||||
|
||||
@@ -15,6 +16,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
/**
|
||||
* This class handle GPS status according to received locations and some thresholds.
|
||||
*/
|
||||
//TODO should handle sharedpreference changes
|
||||
class GpsStatus {
|
||||
|
||||
private static final String TAG = GpsStatus.class.getSimpleName();
|
||||
@@ -60,16 +62,16 @@ class GpsStatus {
|
||||
private final Handler gpsStatusHandler;
|
||||
private GpsStatusRunner gpsStatusRunner = null;
|
||||
|
||||
/**
|
||||
* @param context The context object.
|
||||
* @param client The client.
|
||||
* @param minRecordingInterval Value of min recording interval preference.
|
||||
*/
|
||||
public GpsStatus(Context context, GpsStatusListener client, Duration minRecordingInterval) {
|
||||
public GpsStatus(Context context, GpsStatusListener client) {
|
||||
this.client = client;
|
||||
this.context = context;
|
||||
signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||
|
||||
Duration minRecordingInterval = Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context));
|
||||
signalLostThreshold = !minRecordingInterval.isNegative() ? SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval) : SIGNAL_LOST_THRESHOLD;
|
||||
|
||||
gpsStatusHandler = new Handler();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ package de.dennisguse.opentracks.services.handlers;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
public class HandlerServer {
|
||||
|
||||
@@ -32,8 +34,9 @@ public class HandlerServer {
|
||||
public void start(Context context) {
|
||||
serviceExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
locationHandler.onStart(context);
|
||||
locationHandler.onSharedPreferenceChanged(context, null, null);
|
||||
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
@@ -45,7 +48,7 @@ public class HandlerServer {
|
||||
serviceExecutor = null;
|
||||
}
|
||||
|
||||
public void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences preferences, String key) {
|
||||
locationHandler.onSharedPreferenceChanged(context, preferences, key);
|
||||
}
|
||||
|
||||
@@ -70,8 +73,10 @@ public class HandlerServer {
|
||||
}
|
||||
|
||||
public interface Handler {
|
||||
void onStart(Context context);
|
||||
void onStop(Context context);
|
||||
void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key);
|
||||
void onStart(@NonNull Context context);
|
||||
|
||||
void onStop(@NonNull Context context);
|
||||
|
||||
void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences preferences, String key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackPointUtils;
|
||||
|
||||
class LocationHandler implements HandlerServer.Handler, LocationListener, GpsStatus.GpsStatusListener {
|
||||
|
||||
@@ -35,14 +34,14 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(Context context) {
|
||||
gpsStatus = new GpsStatus(context, this, Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(context)));
|
||||
public void onStart(@NonNull Context context) {
|
||||
gpsStatus = new GpsStatus(context, this);
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(Context context) {
|
||||
public void onStop(@NonNull Context context) {
|
||||
unregisterLocationListener();
|
||||
locationManager = null;
|
||||
if (gpsStatus != null) {
|
||||
@@ -52,9 +51,9 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
|
||||
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||
// Choose battery life over moving time accuracy.
|
||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(Duration.ofSeconds(30), Duration.ofSeconds(5), 5);
|
||||
@@ -70,16 +69,16 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||
if (gpsStatus != null) {
|
||||
gpsStatus.onMinRecordingIntervalChanged(PreferencesUtils.getMinRecordingInterval(context));
|
||||
gpsStatus.onMinRecordingIntervalChanged(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context));
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
if (gpsStatus != null) {
|
||||
gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(context));
|
||||
gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +91,7 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
|
||||
@Override
|
||||
public void onLocationChanged(@NonNull Location location) {
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
boolean isAccurate = TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy);
|
||||
boolean isAccurate = trackPoint.fulfillsAccuracy(recordingGpsAccuracy);
|
||||
boolean isValid = LocationUtils.isValidLocation(location);
|
||||
|
||||
if (gpsStatus != null) {
|
||||
|
||||
+25
-19
@@ -101,11 +101,11 @@ public abstract class BluetoothConnectionManager {
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||
String sensorName = gatt.getDevice().getName();
|
||||
Log.d(TAG, "Received data from " + sensorName);
|
||||
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress());
|
||||
|
||||
SensorData sensorData = parsePayload(sensorName, gatt.getDevice().getAddress(), characteristic);
|
||||
SensorData<?> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
|
||||
if (sensorData != null) {
|
||||
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
|
||||
observer.onChanged(sensorData);
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public abstract class BluetoothConnectionManager {
|
||||
Log.d(TAG, "Connecting to: " + device);
|
||||
|
||||
bluetoothGatt = device.connectGatt(context, true, connectCallback);
|
||||
SensorData sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
|
||||
SensorData<?> sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
|
||||
observer.onChanged(sensorData);
|
||||
}
|
||||
|
||||
@@ -148,12 +148,12 @@ public abstract class BluetoothConnectionManager {
|
||||
return address.equals(bluetoothGatt.getDevice().getAddress());
|
||||
}
|
||||
|
||||
protected abstract SensorData createEmptySensorData(String address);
|
||||
protected abstract SensorData<?> createEmptySensorData(String address);
|
||||
|
||||
/**
|
||||
* @return null if data could not be parsed.
|
||||
*/
|
||||
protected abstract SensorData parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
|
||||
protected abstract SensorData<?> parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
|
||||
|
||||
public static class HeartRate extends BluetoothConnectionManager {
|
||||
|
||||
@@ -162,7 +162,7 @@ public abstract class BluetoothConnectionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SensorData createEmptySensorData(String address) {
|
||||
protected SensorDataHeartRate createEmptySensorData(String address) {
|
||||
return new SensorDataHeartRate(address);
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ public abstract class BluetoothConnectionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SensorData createEmptySensorData(String address) {
|
||||
protected SensorDataCycling.Cadence createEmptySensorData(String address) {
|
||||
return new SensorDataCycling.Cadence(address);
|
||||
}
|
||||
|
||||
@@ -197,30 +197,36 @@ public abstract class BluetoothConnectionManager {
|
||||
}
|
||||
|
||||
//Workaround for Wahoo CADENCE: this sensor reports speed (instead of cadence)
|
||||
if (cadenceAndSpeed.getSpeed() != null) {
|
||||
return new SensorDataCycling.Cadence(cadenceAndSpeed.getSpeed());
|
||||
if (cadenceAndSpeed.getDistanceSpeed() != null) {
|
||||
return new SensorDataCycling.Cadence(cadenceAndSpeed.getDistanceSpeed());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CyclingSpeed extends BluetoothConnectionManager {
|
||||
public static class CyclingDistanceSpeed extends BluetoothConnectionManager {
|
||||
|
||||
CyclingSpeed(SensorDataObserver observer) {
|
||||
CyclingDistanceSpeed(SensorDataObserver observer) {
|
||||
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SensorData createEmptySensorData(String address) {
|
||||
return new SensorDataCycling.Speed(address);
|
||||
protected SensorDataCycling.DistanceSpeed createEmptySensorData(String address) {
|
||||
return new SensorDataCycling.DistanceSpeed(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SensorDataCycling.Speed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
protected SensorDataCycling.DistanceSpeed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
||||
if (cadenceAndSpeed != null) {
|
||||
return cadenceAndSpeed.getSpeed();
|
||||
// Workaround for Garmin Speed Sensor 2: provides cadence instead of speed
|
||||
SensorDataCycling.DistanceSpeed result = cadenceAndSpeed.getDistanceSpeed();
|
||||
if (result == null && cadenceAndSpeed.getCadence() != null) {
|
||||
result = new SensorDataCycling.DistanceSpeed(cadenceAndSpeed.getSensorAddress(), cadenceAndSpeed.getSensorName(), (int) cadenceAndSpeed.getCadence().getCrankRevolutionsCount(), cadenceAndSpeed.getCadence().getCrankRevolutionsTime());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -233,7 +239,7 @@ public abstract class BluetoothConnectionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SensorData createEmptySensorData(String address) {
|
||||
protected SensorDataCyclingPower createEmptySensorData(String address) {
|
||||
return new SensorDataCyclingPower(address);
|
||||
}
|
||||
|
||||
@@ -247,8 +253,8 @@ public abstract class BluetoothConnectionManager {
|
||||
|
||||
interface SensorDataObserver {
|
||||
|
||||
void onChanged(SensorData sensorData);
|
||||
void onChanged(SensorData<?> sensorData);
|
||||
|
||||
void onDisconnecting(SensorData sensorData);
|
||||
void onDisconnecting(SensorData<?> sensorData);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-15
@@ -53,43 +53,50 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private final Context context;
|
||||
private final SharedPreferences sharedPreferences;
|
||||
|
||||
private boolean started = false;
|
||||
|
||||
private final SharedPreferences sharedPreferences;
|
||||
private int preferenceWheelCircumference;
|
||||
|
||||
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
|
||||
private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
|
||||
private final BluetoothConnectionManager.CyclingSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingSpeed(this);
|
||||
private final BluetoothConnectionManager.CyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingDistanceSpeed(this);
|
||||
private final BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(this);
|
||||
|
||||
private final SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (!started) return;
|
||||
|
||||
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(sharedPreferences, context);
|
||||
connect(heartRate, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(context);
|
||||
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(sharedPreferences, context);
|
||||
connect(cyclingCadence, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(context);
|
||||
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(sharedPreferences, context);
|
||||
|
||||
connect(cyclingSpeed, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(context);
|
||||
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(sharedPreferences, context);
|
||||
|
||||
connect(cyclingPower, address);
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
|
||||
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(sharedPreferences, context);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -104,7 +111,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
//Trigger connection startup
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
@@ -155,11 +162,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
return sensorDataSet;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
sensorDataSet.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onChanged(SensorData sensorData) {
|
||||
public synchronized void onChanged(SensorData<?> sensorData) {
|
||||
if (sensorData instanceof SensorDataCycling.Cadence) {
|
||||
SensorDataCycling.Cadence previous = sensorDataSet.getCyclingCadence();
|
||||
Log.d(TAG, "previous " + previous + "; current" + sensorData);
|
||||
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
|
||||
|
||||
if (sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: cadence data repeated.");
|
||||
@@ -167,21 +178,21 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
}
|
||||
((SensorDataCycling.Cadence) sensorData).compute(previous);
|
||||
}
|
||||
if (sensorData instanceof SensorDataCycling.Speed) {
|
||||
SensorDataCycling.Speed previous = sensorDataSet.getCyclingSpeed();
|
||||
Log.d(TAG, "previous " + previous + "; current" + sensorData);
|
||||
if (sensorData instanceof SensorDataCycling.DistanceSpeed) {
|
||||
SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
|
||||
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
|
||||
if (sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: speed data repeated.");
|
||||
return;
|
||||
}
|
||||
((SensorDataCycling.Speed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(context));
|
||||
((SensorDataCycling.DistanceSpeed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(sharedPreferences, context));
|
||||
}
|
||||
|
||||
sensorDataSet.set(sensorData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnecting(SensorData sensorData) {
|
||||
public void onDisconnecting(SensorData<?> sensorData) {
|
||||
sensorDataSet.remove(sensorData);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-5
@@ -17,17 +17,19 @@
|
||||
package de.dennisguse.opentracks.services.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.AnnouncementUtils;
|
||||
@@ -102,6 +104,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
}
|
||||
};
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private TextToSpeech tts;
|
||||
// Response from TTS after its initialization
|
||||
private int ttsInitStatus = TextToSpeech.ERROR;
|
||||
@@ -112,12 +116,15 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
this.context = context;
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
Log.d(TAG, "Start");
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
if (tts == null) {
|
||||
tts = new TextToSpeech(context, status -> {
|
||||
Log.i(TAG, "TextToSpeech initialized with status " + status);
|
||||
@@ -164,14 +171,23 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
return;
|
||||
}
|
||||
|
||||
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context));
|
||||
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
|
||||
String category = track != null ? track.getCategory() : "";
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(track.getId());
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, (float) (PreferencesUtils.isMetricUnits(context) ? 1d * UnitConversions.KM_TO_M : 1d * UnitConversions.MI_TO_M));
|
||||
//TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else.
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
|
||||
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||
while (trackPointIterator.hasNext()) {
|
||||
trackPoints.add(trackPointIterator.next());
|
||||
}
|
||||
|
||||
boolean isMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
||||
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
|
||||
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, (float) (isMetricUnits ? 1d * UnitConversions.KM_TO_M : 1d * UnitConversions.MI_TO_M));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
|
||||
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, category, lastInterval);
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, isMetricUnits, isReportSpeed, lastInterval);
|
||||
speakAnnouncement(announcement);
|
||||
}
|
||||
|
||||
@@ -181,6 +197,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
tts.shutdown();
|
||||
tts = null;
|
||||
}
|
||||
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
private void onTtsReady() {
|
||||
|
||||
@@ -94,7 +94,7 @@ public class PeriodicTaskExecutor {
|
||||
if (timerTaskExecutor == null) {
|
||||
timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService);
|
||||
}
|
||||
timerTaskExecutor.scheduleTask(Duration.ofSeconds(taskFrequency));
|
||||
timerTaskExecutor.scheduleTask(Duration.ofMinutes(taskFrequency));
|
||||
} else {
|
||||
// For distance periodic task
|
||||
calculateNextTaskDistance();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@@ -48,7 +49,7 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
setDialogIcon(null);
|
||||
setPersistent(true);
|
||||
|
||||
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
|
||||
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), ActivityTypePreference.this.getContext());
|
||||
setSummaryProvider(summaryProvider);
|
||||
}
|
||||
|
||||
@@ -60,6 +61,8 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
|
||||
public static class ActivityPreferenceDialog extends PreferenceDialogFragmentCompat {
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private AutoCompleteTextView textView;
|
||||
private ImageView iconView;
|
||||
|
||||
@@ -77,9 +80,11 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
final Context context = getActivity();
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
|
||||
textView = view.findViewById(R.id.activity_type_preference_text_view);
|
||||
String category = PreferencesUtils.getDefaultActivity(context);
|
||||
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, context);
|
||||
textView.setText(category);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
textView.setAdapter(adapter);
|
||||
@@ -100,8 +105,14 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
updateIcon(TrackIconUtils.getIconValue(context, category));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
private void showIconSelectDialog() {
|
||||
String category = PreferencesUtils.getDefaultActivity(getActivity());
|
||||
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, getActivity());
|
||||
ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
|
||||
}
|
||||
|
||||
@@ -110,7 +121,7 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
if (positiveResult) {
|
||||
String newDefaultActivity = textView.getText().toString();
|
||||
if (getPreference().callChangeListener(newDefaultActivity)) {
|
||||
PreferencesUtils.setDefaultActivity(getActivity(), newDefaultActivity);
|
||||
PreferencesUtils.setDefaultActivity(sharedPreferences, getActivity(), newDefaultActivity);
|
||||
HackUtils.invalidatePreference(getPreference());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
|
||||
public static class PrefsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
|
||||
getActivity().runOnUiThread(this::updateReset);
|
||||
}
|
||||
@@ -99,7 +101,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
getActivity().runOnUiThread(this::updateUnits);
|
||||
}
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.night_mode_key, key)) {
|
||||
getActivity().runOnUiThread(() -> ActivityUtils.applyNightMode(getContext()));
|
||||
getActivity().runOnUiThread(() -> ActivityUtils.applyNightMode(sharedPreferences, getContext()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,6 +110,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
|
||||
try {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||
} catch (ClassCastException e) {
|
||||
@@ -123,7 +127,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
|
||||
Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key));
|
||||
instantExportDirectoryPreference.setSummaryProvider(preference -> {
|
||||
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(getContext());
|
||||
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, getContext());
|
||||
//Use same value for not set as Androidx ListPreference and EditTextPreference
|
||||
return directory != null ? directory.getName() : getString(R.string.not_set);
|
||||
});
|
||||
@@ -132,7 +136,10 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
updateUnits();
|
||||
updateReset();
|
||||
updateBluetooth();
|
||||
@@ -142,13 +149,13 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
|
||||
private void updatePostWorkoutExport() {
|
||||
Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key));
|
||||
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext()));
|
||||
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
PreferencesUtils.getSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -176,6 +183,12 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
public void setDefaultActivity(String iconValue) {
|
||||
if (activityPreferenceDialog != null) {
|
||||
activityPreferenceDialog.updateUI(iconValue);
|
||||
@@ -183,7 +196,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
}
|
||||
|
||||
private void updateReset() {
|
||||
final boolean isRecording = PreferencesUtils.isRecording(getActivity());
|
||||
final boolean isRecording = PreferencesUtils.isRecording(sharedPreferences, getActivity());
|
||||
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
|
||||
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
||||
resetPreference.setEnabled(!isRecording);
|
||||
@@ -196,7 +209,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
}
|
||||
|
||||
private void updateUnits() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity());
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
|
||||
|
||||
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
|
||||
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
public class SensorStatistics {
|
||||
private final Float maxHr;
|
||||
private final Float avgHr;
|
||||
private final Float maxCadence;
|
||||
private final Float avgCadence;
|
||||
private final Float avgPower;
|
||||
|
||||
public SensorStatistics(Float maxHr, Float avgHr, Float maxCadence, Float avgCadence, Float avgPower) {
|
||||
this.maxHr = maxHr;
|
||||
this.avgHr = avgHr;
|
||||
this.maxCadence = maxCadence;
|
||||
this.avgCadence = avgCadence;
|
||||
this.avgPower = avgPower;
|
||||
}
|
||||
|
||||
public boolean hasHeartRate() {
|
||||
return avgHr != null && maxHr != null;
|
||||
}
|
||||
|
||||
public float getMaxHeartRate() {
|
||||
return maxHr;
|
||||
}
|
||||
|
||||
public float getAvgHeartRate() {
|
||||
return avgHr;
|
||||
}
|
||||
|
||||
public boolean hasCadence() {
|
||||
return avgCadence != null && maxCadence != null;
|
||||
}
|
||||
|
||||
public float getMaxCadence() {
|
||||
return maxCadence;
|
||||
}
|
||||
|
||||
public float getAvgCadence() {
|
||||
return avgCadence;
|
||||
}
|
||||
|
||||
public boolean hasPower() {
|
||||
return avgPower != null;
|
||||
}
|
||||
|
||||
public float getAvgPower() {
|
||||
return avgPower;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.TrackPointUtils;
|
||||
|
||||
/**
|
||||
* Updater for {@link TrackStatistics}.
|
||||
@@ -104,10 +103,15 @@ public class TrackStatisticsUpdater {
|
||||
/**
|
||||
* Adds a trackPoint.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
* @param minRecordingDistance the min recording distance
|
||||
* @param trackPoint the trackPoint
|
||||
* @param minGPSDistance the min recording distance
|
||||
*/
|
||||
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
|
||||
public void addTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
|
||||
internalAddTrackPoint(trackPoint, minGPSDistance);
|
||||
Log.d(TAG, this.toString());
|
||||
}
|
||||
|
||||
private void internalAddTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
|
||||
if (!trackInitialized) {
|
||||
trackStatistics.setStartTime(trackPoint.getTime());
|
||||
trackInitialized = true;
|
||||
@@ -121,19 +125,26 @@ public class TrackStatisticsUpdater {
|
||||
currentSegment.setStopTime(trackPoint.getTime());
|
||||
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));
|
||||
|
||||
if (trackPoint.isSegmentStart() || trackPoint.isSegmentEnd()) {
|
||||
if (trackPoint.isSegmentEnd()) {
|
||||
if (lastTrackPoint != null && lastMovingTrackPoint != null && lastTrackPoint != lastMovingTrackPoint) {
|
||||
currentSegment.addTotalDistance(lastMovingTrackPoint.distanceTo(lastTrackPoint));
|
||||
}
|
||||
}
|
||||
trackStatistics.merge(currentSegment);
|
||||
currentSegment.reset(trackPoint.getTime());
|
||||
if (trackPoint.isSegmentStart()) {
|
||||
reset(trackPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
lastTrackPoint = null;
|
||||
lastMovingTrackPoint = null;
|
||||
elevationBuffer_m.reset();
|
||||
speedBuffer_ms.reset();
|
||||
// Process sensor data
|
||||
if (trackPoint.hasElevationGain()) {
|
||||
currentSegment.addTotalElevationGain(trackPoint.getElevationGain());
|
||||
}
|
||||
|
||||
if (trackPoint.hasElevationLoss()) {
|
||||
currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss());
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
currentSegment.addTotalDistance(trackPoint.getSensorDistance());
|
||||
}
|
||||
|
||||
if (trackPoint.isSegmentEnd()) {
|
||||
reset(trackPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,39 +153,30 @@ public class TrackStatisticsUpdater {
|
||||
updateAbsoluteElevation(trackPoint.getAltitude());
|
||||
}
|
||||
|
||||
//Get elevation gain
|
||||
if (trackPoint.hasElevationGain()) {
|
||||
currentSegment.addTotalElevationGain(trackPoint.getElevationGain());
|
||||
Log.d(TAG, "elevation gain: " + trackPoint.getElevationGain());
|
||||
}
|
||||
|
||||
//Get elevation loss
|
||||
if (trackPoint.hasElevationLoss()) {
|
||||
currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss());
|
||||
Log.d(TAG, "elevation loss: " + trackPoint.getElevationLoss());
|
||||
}
|
||||
|
||||
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
||||
lastTrackPoint = trackPoint;
|
||||
lastMovingTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint);
|
||||
if (movingDistance < minRecordingDistance && !TrackPointUtils.isMoving(trackPoint)) {
|
||||
speedBuffer_ms.reset();
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
if (!trackPoint.hasSensorDistance()) {
|
||||
// GPS-based distance/speed
|
||||
float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
|
||||
if (movingDistance < minGPSDistance && !trackPoint.isMoving()) {
|
||||
speedBuffer_ms.reset();
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
// Update total distance
|
||||
currentSegment.addTotalDistance(movingDistance);
|
||||
}
|
||||
|
||||
Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
|
||||
if (movingTime.isNegative()) {
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update total distance
|
||||
currentSegment.addTotalDistance(movingDistance);
|
||||
|
||||
// Update moving time
|
||||
currentSegment.addMovingTime(movingTime);
|
||||
|
||||
@@ -187,6 +189,16 @@ public class TrackStatisticsUpdater {
|
||||
lastMovingTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
private void reset(TrackPoint trackPoint) {
|
||||
trackStatistics.merge(currentSegment);
|
||||
currentSegment.reset(trackPoint.getTime());
|
||||
|
||||
lastTrackPoint = null;
|
||||
lastMovingTrackPoint = null;
|
||||
elevationBuffer_m.reset();
|
||||
speedBuffer_ms.reset();
|
||||
}
|
||||
|
||||
public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
|
||||
while (iterator.hasNext()) {
|
||||
TrackPoint location = iterator.next();
|
||||
@@ -211,7 +223,7 @@ public class TrackStatisticsUpdater {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||
if (!TrackPointUtils.isMoving(trackPoint)) {
|
||||
if (!trackPoint.isMoving()) {
|
||||
speedBuffer_ms.reset();
|
||||
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
|
||||
speedBuffer_ms.setNext(trackPoint.getSpeed());
|
||||
@@ -225,20 +237,14 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
/**
|
||||
* Updates an elevation reading.
|
||||
*
|
||||
* @param elevation the elevation
|
||||
* @return the difference
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private double updateAbsoluteElevation(double elevation) {
|
||||
private void updateAbsoluteElevation(double elevation) {
|
||||
// Update elevation using the smoothed average
|
||||
double oldAverage = elevationBuffer_m.getAverage();
|
||||
elevationBuffer_m.setNext(elevation);
|
||||
double newAverage = elevationBuffer_m.getAverage();
|
||||
|
||||
currentSegment.updateElevationExtremities(newAverage);
|
||||
|
||||
return newAverage - oldAverage;
|
||||
}
|
||||
|
||||
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||
@@ -268,4 +274,11 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TrackStatisticsUpdater{" +
|
||||
"trackStatistics=" + trackStatistics +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.util;
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
@@ -150,7 +151,7 @@ public class ActivityUtils {
|
||||
toast.show();
|
||||
}
|
||||
|
||||
public static void applyNightMode(Context context) {
|
||||
AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode(context));
|
||||
public static void applyNightMode(SharedPreferences sharedPreferences, Context context) {
|
||||
AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode(sharedPreferences, context));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ public class AnnouncementUtils {
|
||||
private AnnouncementUtils() {
|
||||
}
|
||||
|
||||
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, String category, @Nullable IntervalStatistics.Interval currentInterval) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(context, category);
|
||||
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MPS_TO_KMH;
|
||||
double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0;
|
||||
@@ -26,7 +24,7 @@ public class AnnouncementUtils {
|
||||
return context.getString(R.string.voice_total_distance_zero);
|
||||
}
|
||||
|
||||
if (!metricUnits) {
|
||||
if (!isMetricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
distancePerTime *= UnitConversions.KM_TO_MI;
|
||||
currentDistancePerTime *= UnitConversions.KM_TO_MI;
|
||||
@@ -35,15 +33,15 @@ public class AnnouncementUtils {
|
||||
String rate;
|
||||
String currentRate;
|
||||
String currentRateMsg;
|
||||
if (reportSpeed) {
|
||||
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
||||
if (isReportSpeed) {
|
||||
int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
||||
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
|
||||
|
||||
currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTime), currentDistancePerTime);
|
||||
currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate);
|
||||
} else {
|
||||
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
|
||||
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||
int paceId = isMetricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||
Duration time = Duration.ofMillis((long) (timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS));
|
||||
rate = context.getString(paceId, getAnnounceTime(context, time));
|
||||
|
||||
@@ -53,7 +51,7 @@ public class AnnouncementUtils {
|
||||
currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate);
|
||||
}
|
||||
|
||||
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||
int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
|
||||
|
||||
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
|
||||
@@ -65,8 +63,8 @@ public class AnnouncementUtils {
|
||||
private static String getAnnounceTime(Context context, Duration duration) {
|
||||
String result = "";
|
||||
|
||||
int hours = (int) (duration.getSeconds() / (60 * 60));
|
||||
int minutes = (int) (duration.getSeconds() / 60);
|
||||
int hours = (int) (duration.toHours());
|
||||
int minutes = (int) (duration.toMinutes() % 60);
|
||||
int seconds = (int) (duration.getSeconds() % 60);
|
||||
|
||||
if (hours != 0) {
|
||||
@@ -82,29 +80,8 @@ public class AnnouncementUtils {
|
||||
return result + minutesText + " " + secondsText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plural count to be used by getQuantityString.
|
||||
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
|
||||
* <p>
|
||||
* As a temporary workaround, we convert a double quantity to an integer quantity.
|
||||
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
|
||||
* Otherwise, we cast the double quantity to an integer quantity.
|
||||
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
|
||||
*
|
||||
* @param d the double value
|
||||
*/
|
||||
private static int getQuantityCount(double d) {
|
||||
if (d == 0) {
|
||||
return 0;
|
||||
} else if (d == 1) {
|
||||
return 1;
|
||||
} else if (d == 2) {
|
||||
return 2;
|
||||
} else {
|
||||
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
|
||||
int count = (int) d;
|
||||
return Math.max(count, 3);
|
||||
}
|
||||
static int getQuantityCount(double d) {
|
||||
return (int) d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,12 +126,12 @@ public class BluetoothUtils {
|
||||
cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime);
|
||||
}
|
||||
|
||||
SensorDataCycling.Speed speed = null;
|
||||
SensorDataCycling.DistanceSpeed speed = null;
|
||||
if (hasWheel && valueLength - index >= 4) {
|
||||
int wheelCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||
index += 2;
|
||||
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||
speed = new SensorDataCycling.Speed(address, sensorName, wheelCount, wheelTime);
|
||||
speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelCount, wheelTime);
|
||||
}
|
||||
|
||||
return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed);
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.DocumentsContract;
|
||||
@@ -14,6 +15,8 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
@@ -26,9 +29,10 @@ public class ExportUtils {
|
||||
private static final String TAG = ExportUtils.class.getSimpleName();
|
||||
|
||||
public static void postWorkoutExport(Context context, Track track, ExportServiceResultReceiver resultReceiver) {
|
||||
if (PreferencesUtils.shouldInstantExportAfterWorkout(context)) {
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(context);
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
if (PreferencesUtils.shouldInstantExportAfterWorkout(sharedPreferences, context)) {
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, context);
|
||||
|
||||
ExportService.enqueue(context, resultReceiver, track.getId(), trackFileFormat, directory.getUri());
|
||||
}
|
||||
@@ -37,7 +41,7 @@ public class ExportUtils {
|
||||
public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) {
|
||||
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context);
|
||||
|
||||
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track.getId(), trackFileFormat, directory);
|
||||
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory);
|
||||
if (exportDocumentFileUri == null) {
|
||||
Log.e(TAG, "Couldn't create document file for export");
|
||||
return false;
|
||||
@@ -62,8 +66,10 @@ public class ExportUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isExportFileExists(Track.Id trackId, String trackFileFormatExtension, List<String> filesName) {
|
||||
return filesName.contains(getExportFileNameByTrackId(trackId, trackFileFormatExtension));
|
||||
public static boolean isExportFileExists(UUID uuuid, String trackFileFormatExtension, List<String> filesName) {
|
||||
String word = uuuid.toString().substring(0, 8) + ".*\\." + trackFileFormatExtension;
|
||||
Pattern pattern = Pattern.compile(word);
|
||||
return filesName.stream().anyMatch(w -> pattern.matcher(w).matches());
|
||||
}
|
||||
|
||||
public static List<String> getAllFiles(Context context, Uri directoryUri) {
|
||||
@@ -82,8 +88,8 @@ public class ExportUtils {
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
private static Uri getExportDocumentFileUri(Context context, Track.Id trackId, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
String exportFileName = getExportFileNameByTrackId(trackId, trackFileFormat.getExtension());
|
||||
private static Uri getExportDocumentFileUri(Context context, Track track, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
String exportFileName = getExportFileNameForTrack(track, trackFileFormat.getExtension());
|
||||
Uri exportDocumentFileUri = findFile(context, directory.getUri(), exportFileName);
|
||||
if (exportDocumentFileUri == null) {
|
||||
final DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), exportFileName);
|
||||
@@ -94,8 +100,8 @@ public class ExportUtils {
|
||||
return exportDocumentFileUri;
|
||||
}
|
||||
|
||||
private static String getExportFileNameByTrackId(Track.Id trackId, String trackFileFormatExtension) {
|
||||
return trackId.getId() + "." + trackFileFormatExtension;
|
||||
private static String getExportFileNameForTrack(Track track, String trackFileFormatExtension) {
|
||||
return track.getUuid().toString().substring(0, 8) + "_" + track.getName() + "." + trackFileFormatExtension;
|
||||
}
|
||||
|
||||
private static Uri findFile(Context context, Uri directoryUri, String exportFileName) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.util;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -73,11 +74,13 @@ public class IntentDashboardUtils {
|
||||
|
||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||
|
||||
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(context));
|
||||
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(context));
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(sharedPreferences, context));
|
||||
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, context));
|
||||
intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
|
||||
if (isRecording) {
|
||||
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen(context));
|
||||
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen(sharedPreferences, context));
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
@@ -91,7 +91,7 @@ public class IntentUtils {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(context));
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(PreferencesUtils.getSharedPreferences(context), context));
|
||||
uris.add(uriAndMime.first);
|
||||
mime = uriAndMime.second;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,6 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
* Utility class for decimating tracks at a given level of precision.
|
||||
*
|
||||
@@ -31,14 +26,11 @@ public class LocationUtils {
|
||||
|
||||
private static final String TAG = LocationUtils.class.getSimpleName();
|
||||
|
||||
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
||||
|
||||
private LocationUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given location is a valid (i.e. physically possible) location on Earth.
|
||||
* Neither will locations with lat=0 and lng=0 as these are most likely "bad" measurements which often cause trouble.
|
||||
*
|
||||
* @param location the location to test
|
||||
* @return true if the location is a valid location.
|
||||
@@ -48,10 +40,4 @@ public class LocationUtils {
|
||||
&& Math.abs(location.getLatitude()) <= 90
|
||||
&& Math.abs(location.getLongitude()) <= 180;
|
||||
}
|
||||
|
||||
public static boolean isTrackPointOld(TrackPoint trackPoint) {
|
||||
return Duration.between(trackPoint.getTime(), Instant.now())
|
||||
.minus(MAX_LOCATION_AGE)
|
||||
.isNegative();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,35 +42,32 @@ public class PreferencesUtils {
|
||||
private PreferencesUtils() {
|
||||
}
|
||||
|
||||
@Deprecated //Should only be used to get a sharedPreference for more than one interaction!
|
||||
public static SharedPreferences getSharedPreferences(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public static void register(Context context, SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) {
|
||||
getSharedPreferences(context).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
public static void unregister(Context context, SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) {
|
||||
getSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
|
||||
//NOTE: This was also used to recover from a service restart, but this data should not be exposed to the whole application.
|
||||
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
|
||||
|
||||
@Deprecated //Use the TrackRecordingService
|
||||
public static Track.Id getRecordingTrackId(Context context) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
public static Track.Id getRecordingTrackId(Context context, SharedPreferences sharedPreferences) {
|
||||
return new Track.Id(sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT));
|
||||
}
|
||||
|
||||
public static String getDefaultActivity(Context context) {
|
||||
return getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
|
||||
@Deprecated //Use the TrackRecordingService
|
||||
public static Track.Id getRecordingTrackId(SharedPreferences sharedPreferences, Context context) {
|
||||
return getRecordingTrackId(context, sharedPreferences);
|
||||
}
|
||||
|
||||
public static void setDefaultActivity(Context context, String newDefaultActivity) {
|
||||
setString(context, R.string.default_activity_key, newDefaultActivity);
|
||||
public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
|
||||
return getString(sharedPreferences, context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
|
||||
}
|
||||
|
||||
public static void setDefaultActivity(SharedPreferences sharedPreferences, Context context, String newDefaultActivity) {
|
||||
setString(sharedPreferences, context, R.string.default_activity_key, newDefaultActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,44 +91,19 @@ public class PreferencesUtils {
|
||||
return key == null || key.equals(getKey(context, keyId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param defaultValue the default value
|
||||
*/
|
||||
private static boolean getBoolean(Context context, int keyId, boolean defaultValue) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
private static boolean getBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean defaultValue) {
|
||||
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a boolean preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
//TODO Don't use; this function is only to be used TrackRecordingService and will be removed.
|
||||
@VisibleForTesting
|
||||
public static void setBoolean(Context context, int keyId, boolean value) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
public static void setBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean value) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putBoolean(getKey(context, keyId), value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an integer preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param defaultValue the default value
|
||||
*/
|
||||
private static int getInt(Context context, int keyId, int defaultValue) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
|
||||
private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
|
||||
try {
|
||||
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
@@ -147,74 +119,42 @@ public class PreferencesUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an integer preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void setInt(Context context, int keyId, int value) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
public static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putInt(getKey(context, keyId), value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a long preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
//TODO Don't use; this function is only to be used TrackRecordingService and will be removed.
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public static void setLong(Context context, int keyId, long value) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
public static void setLong(SharedPreferences sharedPreferences, Context context, int keyId, long value) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putLong(getKey(context, keyId), value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param defaultValue default value
|
||||
*/
|
||||
public static String getString(Context context, int keyId, String defaultValue) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
public static String getString(SharedPreferences sharedPreferences, Context context, int keyId, String defaultValue) {
|
||||
return sharedPreferences.getString(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a string preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void setString(Context context, int keyId, String value) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
public static void setString(SharedPreferences sharedPreferences, Context context, int keyId, String value) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putString(getKey(context, keyId), value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static boolean isMetricUnits(Context context) {
|
||||
public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) {
|
||||
final String STATS_UNIT = context.getString(R.string.stats_units_default);
|
||||
return STATS_UNIT.equals(getString(context, R.string.stats_units_key, STATS_UNIT));
|
||||
return STATS_UNIT.equals(getString(sharedPreferences, context, R.string.stats_units_key, STATS_UNIT));
|
||||
}
|
||||
|
||||
public static boolean isReportSpeed(Context context, String category) {
|
||||
public static boolean isReportSpeed(SharedPreferences sharedPreferences, Context context, String category) {
|
||||
final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
|
||||
String currentStatsRate = getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT);
|
||||
if (currentStatsRate.equals(getString(context, R.string.stats_rate_speed_or_pace_default, STATS_RATE_DEFAULT))) {
|
||||
String currentStatsRate = getString(sharedPreferences, context, R.string.stats_rate_key, STATS_RATE_DEFAULT);
|
||||
if (currentStatsRate.equals(getString(sharedPreferences, context, R.string.stats_rate_speed_or_pace_default, STATS_RATE_DEFAULT))) {
|
||||
return TrackIconUtils.isSpeedIcon(context, category);
|
||||
}
|
||||
|
||||
@@ -222,8 +162,8 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
public static boolean isRecordingTrackPaused(Context context) {
|
||||
return getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
|
||||
public static boolean isRecordingTrackPaused(SharedPreferences sharedPreferences, Context context) {
|
||||
return getBoolean(sharedPreferences, context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
@@ -232,9 +172,9 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
public static void defaultRecordingTrackPaused(Context context) {
|
||||
public static void defaultRecordingTrackPaused(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean RECORDING_TRACK_PAUSED = context.getResources().getBoolean(R.bool.recording_track_paused_default);
|
||||
setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
||||
setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
||||
}
|
||||
|
||||
private static String getBluetoothSensorAddressNone(Context context) {
|
||||
@@ -245,78 +185,74 @@ public class PreferencesUtils {
|
||||
return getBluetoothSensorAddressNone(context).equals(currentValue);
|
||||
}
|
||||
|
||||
public static String getBluetoothHeartRateSensorAddress(Context context) {
|
||||
return getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone(context));
|
||||
public static String getBluetoothHeartRateSensorAddress(SharedPreferences sharedPreferences, Context context) {
|
||||
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone(context));
|
||||
}
|
||||
|
||||
public static String getBluetoothCyclingCadenceSensorAddress(Context context) {
|
||||
return getString(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone(context));
|
||||
public static String getBluetoothCyclingCadenceSensorAddress(SharedPreferences sharedPreferences, Context context) {
|
||||
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone(context));
|
||||
}
|
||||
|
||||
public static String getBluetoothCyclingSpeedSensorAddress(Context context) {
|
||||
return getString(context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(context));
|
||||
public static String getBluetoothCyclingSpeedSensorAddress(SharedPreferences sharedPreferences, Context context) {
|
||||
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(context));
|
||||
}
|
||||
|
||||
public static int getWheelCircumference(Context context) {
|
||||
public static int getWheelCircumference(SharedPreferences sharedPreferences, Context context) {
|
||||
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
|
||||
return getInt(context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT);
|
||||
return getInt(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean isBluetoothCyclingPowerSensorAddressNone(Context context) {
|
||||
return isBluetoothSensorAddressNone(context, getBluetoothCyclingPowerSensorAddress(context));
|
||||
public static String getBluetoothCyclingPowerSensorAddress(SharedPreferences sharedPreferences, Context context) {
|
||||
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
|
||||
}
|
||||
|
||||
public static String getBluetoothCyclingPowerSensorAddress(Context context) {
|
||||
return getString(context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
|
||||
}
|
||||
|
||||
|
||||
public static boolean shouldShowStatsOnLockscreen(Context context) {
|
||||
public static boolean shouldShowStatsOnLockscreen(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
|
||||
return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean shouldKeepScreenOn(Context context) {
|
||||
public static boolean shouldKeepScreenOn(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_keep_screen_on_while_recording_default);
|
||||
return getBoolean(context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean shouldUseFullscreen(Context context) {
|
||||
public static boolean shouldUseFullscreen(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_fullscreen_while_recording_default);
|
||||
return getBoolean(context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean isShowStatsElevation(Context context) {
|
||||
public static boolean isShowStatsElevation(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean STATS_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_elevation_default);
|
||||
return getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_ELEVATION);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_show_grade_elevation_key, STATS_SHOW_ELEVATION);
|
||||
}
|
||||
|
||||
public static boolean isStatsShowCoordinate(Context context) {
|
||||
public static boolean isStatsShowCoordinate(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean STATS_SHOW_COORDINATE = context.getResources().getBoolean(R.bool.stats_show_coordinate_default);
|
||||
return getBoolean(context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
|
||||
}
|
||||
|
||||
public static int getVoiceFrequency(Context context) {
|
||||
public static int getVoiceFrequency(SharedPreferences sharedPreferences, Context context) {
|
||||
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
|
||||
return getInt(context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
|
||||
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
|
||||
}
|
||||
|
||||
public static int getRecordingDistanceInterval(Context context) {
|
||||
return getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
|
||||
public static int getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) {
|
||||
return getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
|
||||
}
|
||||
|
||||
public static int getRecordingDistanceIntervalDefault(Context context) {
|
||||
return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
|
||||
}
|
||||
|
||||
public static int getMaxRecordingDistance(Context context) {
|
||||
public static int getMaxRecordingDistance(SharedPreferences sharedPreferences, Context context) {
|
||||
final int MAX_RECORDING_DISTANCE = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
|
||||
return getInt(context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE);
|
||||
return getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE);
|
||||
}
|
||||
|
||||
public static int getMinRecordingInterval(Context context) {
|
||||
//TODO Duration
|
||||
public static int getMinRecordingInterval(SharedPreferences sharedPreferences, Context context) {
|
||||
final int MIN_RECORDING_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||
return getInt(context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
|
||||
return getInt(sharedPreferences, context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
|
||||
}
|
||||
|
||||
public static int getMinRecordingIntervalAdaptAccuracy(Context context) {
|
||||
@@ -331,19 +267,19 @@ public class PreferencesUtils {
|
||||
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||
}
|
||||
|
||||
public static int getRecordingGPSAccuracy(Context context) {
|
||||
public static int getRecordingGPSAccuracy(SharedPreferences sharedPreferences, Context context) {
|
||||
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||
return getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
|
||||
return getInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
|
||||
}
|
||||
|
||||
public static boolean shouldInstantExportAfterWorkout(Context context) {
|
||||
public static boolean shouldInstantExportAfterWorkout(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean INSTANT_POST_WORKOUT_EXPORT_DEFAULT = context.getResources().getBoolean(R.bool.post_workout_export_enabled_default);
|
||||
return getBoolean(context, R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT);
|
||||
return getBoolean(sharedPreferences, context, R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT);
|
||||
}
|
||||
|
||||
public static TrackFileFormat getExportTrackFileFormat(Context context) {
|
||||
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(context, R.string.export_trackfileformat_default, null);
|
||||
String trackFileFormatName = getString(context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
|
||||
public static TrackFileFormat getExportTrackFileFormat(SharedPreferences sharedPreferences, Context context) {
|
||||
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(sharedPreferences, context, R.string.export_trackfileformat_default, null);
|
||||
String trackFileFormatName = getString(sharedPreferences, context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
|
||||
try {
|
||||
return TrackFileFormat.valueOf(trackFileFormatName);
|
||||
} catch (Exception e) {
|
||||
@@ -351,24 +287,24 @@ public class PreferencesUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean getPreventReimportTracks(Context context) {
|
||||
final boolean defaultValue = getBoolean(context, R.bool.import_prevent_reimport_default, false);
|
||||
return getBoolean(context, R.string.import_prevent_reimport_key, defaultValue);
|
||||
public static boolean getPreventReimportTracks(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean defaultValue = getBoolean(sharedPreferences, context, R.bool.import_prevent_reimport_default, false);
|
||||
return getBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link androidx.appcompat.app.AppCompatDelegate}.MODE_*
|
||||
*/
|
||||
public static int getDefaultNightMode(Context context) {
|
||||
public static int getDefaultNightMode(SharedPreferences sharedPreferences, Context context) {
|
||||
final String defaultValue = getKey(context, R.string.night_mode_default);
|
||||
final String value = getString(context, R.string.night_mode_key, defaultValue);
|
||||
final String value = getString(sharedPreferences, context, R.string.night_mode_key, defaultValue);
|
||||
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
public static boolean isRecording(Context context) {
|
||||
return isRecording(getRecordingTrackId(context));
|
||||
public static boolean isRecording(SharedPreferences sharedPreferences, Context context) {
|
||||
return isRecording(getRecordingTrackId(context, sharedPreferences));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -380,16 +316,19 @@ public class PreferencesUtils {
|
||||
return recordingTrackId.getId() != RECORDING_TRACK_ID_DEFAULT;
|
||||
}
|
||||
|
||||
public static void resetPreferences(Context context, boolean readAgain) {
|
||||
public static SharedPreferences resetPreferences(Context context, boolean readAgain) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
if (readAgain) {
|
||||
// We want to really clear settings now.
|
||||
getSharedPreferences(context).edit().clear().commit();
|
||||
sharedPreferences.edit().clear().commit();
|
||||
}
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
|
||||
|
||||
return sharedPreferences;
|
||||
}
|
||||
|
||||
public static DocumentFile getDefaultExportDirectoryUri(Context context) {
|
||||
String singleExportDirectorySettingsKey = getString(context, R.string.settings_default_export_directory_key, null);
|
||||
public static DocumentFile getDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
|
||||
String singleExportDirectorySettingsKey = getString(sharedPreferences, context, R.string.settings_default_export_directory_key, null);
|
||||
if (singleExportDirectorySettingsKey == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -401,12 +340,12 @@ public class PreferencesUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void setDefaultExportDirectoryUri(Context context, Uri directoryUri) {
|
||||
public static void setDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context, Uri directoryUri) {
|
||||
String value = directoryUri != null ? directoryUri.toString() : null;
|
||||
setString(context, R.string.settings_default_export_directory_key, value);
|
||||
setString(sharedPreferences, context, R.string.settings_default_export_directory_key, value);
|
||||
}
|
||||
|
||||
public static boolean isDefaultExportDirectoryUri(Context context) {
|
||||
return getDefaultExportDirectoryUri(context) != null;
|
||||
public static boolean isDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
|
||||
return getDefaultExportDirectoryUri(sharedPreferences, context) != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,9 @@ public class TrackNameUtils {
|
||||
private TrackNameUtils() {
|
||||
}
|
||||
|
||||
//TODO Should not access sharedPreferences; trackName should be an ENUM.
|
||||
public static String getTrackName(Context context, Track.Id trackId, Instant startTime) {
|
||||
String trackName = PreferencesUtils.getString(context, R.string.track_name_key, context.getString(R.string.track_name_default));
|
||||
String trackName = PreferencesUtils.getString(PreferencesUtils.getSharedPreferences(context), context, R.string.track_name_key, context.getString(R.string.track_name_default));
|
||||
|
||||
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
|
||||
return StringUtils.formatDateTime(context, startTime);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user