Compare commits

..

51 Commits

Author SHA1 Message Date
Dennis Guse 0b30ecabb1 Release: v3.16.0 2021-03-24 22:35:33 +01:00
mondstern 107cc14a2d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-03-24 17:23:33 +01:00
mondstern 6d3d8bb83c Translated using Weblate (Basque)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/eu/
2021-03-24 17:23:33 +01:00
Michael Munzert 9c8e0335ef Translated using Weblate (German)
Currently translated at 90.1% (477 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-03-24 17:23:33 +01:00
FTno 8c648c69e8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-03-24 17:23:33 +01:00
Valentine A 4422464f8d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-03-24 17:23:33 +01:00
mondstern d094531779 Translated using Weblate (Polish)
Currently translated at 88.2% (464 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2021-03-24 17:23:33 +01:00
mondstern a01341d235 Translated using Weblate (Basque)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/eu/
2021-03-24 17:23:33 +01:00
mondstern 83065dad39 Translated using Weblate (Estonian)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2021-03-24 17:23:33 +01:00
Dennis Guse 68ba3e0c2a Bugfix: announcements should consider hours when computing minutes.
Fixes #674.
2021-03-24 17:23:27 +01:00
Dennis Guse 3f21edc44c Cleanup. 2021-03-21 20:01:57 +01:00
Dennis Guse aefd1a6a35 Fixed test. 2021-03-21 19:58:18 +01:00
Dennis Guse 74fa73e4a4 TrackList: show aggregated stats icon in the first slot.
Fixes #657.
2021-03-20 22:21:18 +01:00
Dennis Guse 0a006d64f9 AnnouncementPeriodicTask: acquire sharedprefences on start (so it can be restarted).
Introduced in 15fbd90ca4.
Fixes #665.
2021-03-20 15:31:57 +01:00
dependabot[bot] 997ab6060e Bump gradle from 4.1.2 to 4.1.3
Bumps gradle from 4.1.2 to 4.1.3.

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-19 08:19:53 +01:00
Román Martínez 6d926ca7a8 Notification manager shows GPS accuracy, distance and speed. Fixes #584 and #483. 2021-03-18 21:49:55 +01:00
dependabot[bot] 3c1f1a9ba5 Bump espresso-core from 3.4.0-alpha04 to 3.4.0-alpha05
Bumps espresso-core from 3.4.0-alpha04 to 3.4.0-alpha05.

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-16 17:56:23 +01:00
Dennis Guse ed5da55fba Hack: Garmin Speed 2 might count backwards. 2021-03-15 17:27:41 +01:00
Dennis Guse d1e9649505 Garmin Speed 2: added workaround if cadence instead of speed is provided. 2021-03-15 17:27:41 +01:00
Dennis Guse 2431d60171 Cleanup. 2021-03-15 17:27:41 +01:00
Dennis Guse e08f5e7273 Cleanup: AnnouncementPeriodicTask uses TrackPointsIterator. 2021-03-15 17:27:41 +01:00
Dennis Guse 7d2a6fff59 TrackPointIterator: removed manual batching as it should not be necessary. 2021-03-15 17:27:41 +01:00
Dennis Guse a5158a55d9 Use distance from cycling speed sensors.
Fixes #505.
2021-03-15 17:27:41 +01:00
Dennis Guse 15fbd90ca4 Performance: Keep reference to sharedPreferences.
So far, we opened the SharedPreferences very often, which is very slow; also happened in the UI thread a lot.

Fixes #642.
2021-03-15 12:01:32 +01:00
mondstern eebc33f60c Translated using Weblate (Dutch)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nl/
2021-03-14 19:22:32 +01:00
mondstern ba2a755fb7 Translated using Weblate (Italian)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2021-03-14 19:22:32 +01:00
mondstern 344c50a20c Translated using Weblate (Finnish)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-03-14 19:22:32 +01:00
mondstern 9a9a610321 Translated using Weblate (Spanish)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-03-14 19:22:32 +01:00
mondstern 8af427ea48 Translated using Weblate (Danish)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/da/
2021-03-14 19:22:32 +01:00
Dennis Guse 7b7d7c8061 GPX: export and import power data.
Fixes #638.
2021-03-14 19:22:29 +01:00
Román Martínez 7d6919d3f9 Bug fixed: ImportService send a list of Track.Id. 2021-03-14 19:22:05 +01:00
Román Martínez 7eceb979c3 Merge pull request #646 from OpenTracksApp/exportTrackName#512
Track exported names: UUID, track's name and extension
2021-03-14 18:43:28 +01:00
Román Martínez bd48944ad5 Track export name: first UUID, name and extension (<First 32bits UUID>_<track's name>.<extension>). Fixes #512. 2021-03-14 18:42:27 +01:00
Dennis Guse 503cba1315 Import: allow import multiple select files.
Fixes #559.
2021-03-14 12:48:27 +01:00
Dennis Guse a313cb6244 Enabling StrictMode for debugging simplified. 2021-03-12 20:15:50 +01:00
Román Martínez 7f0c6309c0 Merge pull request #634 from OpenTracksApp/ui#566
Recorded fragment shows name, desc, start datetime.
2021-03-12 19:55:12 +01:00
Román Martínez 02c578327d Shared element transition (from List to Recorded). 2021-03-12 19:37:35 +01:00
Román Martínez 7fec0dcff4 StatisticsRecordedFragment does not need to check null for track/trackStatistics. Fixes #635. 2021-03-12 19:37:35 +01:00
Román Martínez 34eb7a833a Statistics recorded fragment shows activity's information (name, description, start datetime).
Re-arrange stats putting the focus on moving stats.
Fixes #566.
2021-03-12 19:37:35 +01:00
Román Martínez 5db3aa7ac1 Lowercase units for sensors and unit next to value. 2021-03-12 19:37:35 +01:00
mondstern 47fd7c51a9 Translated using Weblate (Finnish)
Currently translated at 98.6% (519 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-03-11 21:16:40 +01:00
mondstern 2f6f0b4e17 Translated using Weblate (German)
Currently translated at 90.1% (474 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-03-11 21:16:40 +01:00
mondstern 41b794f66b Translated using Weblate (Finnish)
Currently translated at 97.9% (515 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-03-11 21:16:40 +01:00
paulle e2c592367a Translated using Weblate (German)
Currently translated at 88.9% (468 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-03-11 21:16:40 +01:00
Hatsune Miku 7a24c56474 Translated using Weblate (Sinhala)
Currently translated at 0.3% (2 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/si/
2021-03-11 21:16:40 +01:00
Hatsune Miku 3f6bd6926a Added translation using Weblate (Sinhala) 2021-03-11 21:16:40 +01:00
Dennis Guse 508af45449 Refactored TrackImporter interface. 2021-03-11 21:16:36 +01:00
Dennis Guse 0f89cd7875 GPX: support multiple tracks.
Fixes #617.
2021-03-11 21:16:36 +01:00
Dennis Guse 29b522b8a2 Cleanup. 2021-03-11 21:16:24 +01:00
Dennis Guse 14e63a1a54 Release: v3.15.4 2021-03-09 21:44:02 +01:00
Román Martínez e559583d23 Voice announcement in seconds instead of minutes. Fixes #640. 2021-03-09 20:14:54 +01:00
106 changed files with 2370 additions and 1409 deletions
+6
View File
@@ -19,6 +19,12 @@ However, often only one value is provided.
### Speed ### Speed
* Garmin Speed Sensor 2 * 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) * Wahoo Speed (Model: WFRPMSPD)
### Cadence ### Cadence
+4 -4
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { 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 { defaultConfig {
applicationId "de.dennisguse.opentracks" applicationId "de.dennisguse.opentracks"
versionCode 3890 versionCode 3941
versionName "v3.15.3" versionName "v3.16.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -86,7 +86,7 @@ android {
} }
dependencies { dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha04' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha05'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
+1 -11
View File
@@ -2,17 +2,7 @@
interface TrackExporter { interface TrackExporter {
} }
class KmzTrackExporter implements TrackExporter class KmzTrackExporter implements TrackExporter
KmzTrackExporter o-- FileTrackExporter class KmlTrackExporter implements TrackExporter
class FileTrackExporter implements TrackExporter {
/' One track only '/
}
FileTrackExporter o-- TrackWriter
interface TrackWriter {
}
class GpxTrackWriter implements TrackWriter
class KmlTrackWriter implements TrackWriter
interface TrackImporter { interface TrackImporter {
} }
+28 -4
View File
@@ -1,8 +1,32 @@
<?xml version="1.0"?> <?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"> targetNamespace="http://opentracksapp.com/xmlschemas/v1">
<xs:element name="trackuuid" <xsd:element name="trackuuid"
type="xs:string" /> 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,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 java.util.List;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
public class TestDataUtil { 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); 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;
}
} }
@@ -7,8 +7,8 @@ import java.util.List;
public class TestSensorDataUtil { public class TestSensorDataUtil {
private List<TrackPoint> trackPointList = new ArrayList<>(); private final List<TrackPoint> trackPointList = new ArrayList<>();
private List<SensorData> sensorDataList = new ArrayList<>(); private final List<SensorData> sensorDataList = new ArrayList<>();
public void add(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) { public void add(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type)); sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type));
@@ -33,7 +33,7 @@ public class TestSensorDataUtil {
} }
public SensorDataStats computeStats() { public SensorDataStats computeStats() {
if (sensorDataList == null || sensorDataList.size() <= 1) { if (sensorDataList.size() <= 1) {
return null; return null;
} }
@@ -49,9 +49,9 @@ public class TestSensorDataUtil {
if (dataPrev.type != TrackPoint.Type.SEGMENT_START_MANUAL) { if (dataPrev.type != TrackPoint.Type.SEGMENT_START_MANUAL) {
timeElapsed = dataCurrent.type != TrackPoint.Type.SEGMENT_START_MANUAL ? dataCurrent.time.getEpochSecond() - dataPrev.time.getEpochSecond() : 0; timeElapsed = dataCurrent.type != TrackPoint.Type.SEGMENT_START_MANUAL ? dataCurrent.time.getEpochSecond() - dataPrev.time.getEpochSecond() : 0;
stats.avgHr += (dataPrev.hr * timeElapsed); stats.avgHr += (dataPrev.hr * timeElapsed);
stats.maxHr = dataPrev.hr > stats.maxHr ? dataPrev.hr : stats.maxHr; stats.maxHr = Math.max(dataPrev.hr, stats.maxHr);
stats.avgCadence += (dataPrev.cadence * timeElapsed); stats.avgCadence += (dataPrev.cadence * timeElapsed);
stats.maxCadence = dataPrev.cadence > stats.maxCadence ? dataPrev.cadence : stats.maxCadence; stats.maxCadence = Math.max(dataPrev.cadence, stats.maxCadence);
stats.avgPower += (dataPrev.power * timeElapsed); stats.avgPower += (dataPrev.power * timeElapsed);
movingTime += timeElapsed; movingTime += timeElapsed;
@@ -67,11 +67,11 @@ public class TestSensorDataUtil {
} }
private static class SensorData { private static class SensorData {
Instant time; final Instant time;
float hr; final float hr;
float cadence; final float cadence;
float power; final float power;
TrackPoint.Type type; final TrackPoint.Type type;
public SensorData(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) { public SensorData(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
this.time = time; this.time = time;
@@ -94,29 +94,22 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testLocationIterator_noPoints() { public void testLocationIterator_noPoints() {
testIterator(new Track.Id(1), 0, 1); testIterator(new Track.Id(1), 0);
} }
@Test @Test
public void testLocationIterator_noBatchAscending() { public void testLocationIterator_noAscending() {
testIterator(new Track.Id(1), 50, 100); testIterator(new Track.Id(1), 50);
testIterator(new Track.Id(2), 50, 50); testIterator(new Track.Id(2), 50);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(new Track.Id(1), 50, 11);
testIterator(new Track.Id(2), 50, 25);
} }
@Test @Test
public void testLocationIterator_largeTrack() { 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) { private void testIterator(Track.Id trackId, int numPoints) {
long lastPointId = initializeTrack(trackId, numPoints); TrackPoint.Id lastPointId = initializeTrack(trackId, numPoints);
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
List<TrackPoint> locations = new ArrayList<>(numPoints); List<TrackPoint> locations = new ArrayList<>(numPoints);
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) { try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
while (it.hasNext()) { while (it.hasNext()) {
@@ -124,13 +117,13 @@ public class CustomContentProviderUtilsTest {
assertNotNull(trackPoint); assertNotNull(trackPoint);
locations.add(trackPoint); locations.add(trackPoint);
// Make sure the IDs are returned in the right order. // 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()); 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 track = new Track();
track.setId(id); track.setId(id);
track.setName("Test: " + id.getId()); track.setName("Test: " + id.getId());
@@ -150,17 +143,17 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id); contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
// Load all inserted trackPoints. // Load all inserted trackPoints.
long lastPointId = -1; TrackPoint.Id lastPointId = null;
int counter = 0; int counter = 0;
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) { try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) {
while (it.hasNext()) { while (it.hasNext()) {
TrackPoint trackPoint = it.next(); TrackPoint trackPoint = it.next();
lastPointId = trackPoint.getId().getId(); lastPointId = trackPoint.getId();
counter++; counter++;
} }
} }
assertTrue(numPoints == 0 || lastPointId > 0); assertTrue(numPoints == 0 || lastPointId.getId() > 0);
assertEquals(numPoints, counter); assertEquals(numPoints, counter);
return lastPointId; return lastPointId;
@@ -770,9 +763,9 @@ public class CustomContentProviderUtilsTest {
// when / then // when / then
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId); 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); contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId);
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount()); assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
} }
/** /**
@@ -829,7 +822,7 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId); contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size()); assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
} }
@Test @Test
@@ -847,7 +840,7 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertTrackPoint(trackPoint, trackId); contentProviderUtils.insertTrackPoint(trackPoint, trackId);
// then // then
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrue(trackPoints.get(10).hasHeartRate()); assertTrue(trackPoints.get(10).hasHeartRate());
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01); assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01); assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
@@ -879,7 +872,7 @@ public class CustomContentProviderUtilsTest {
.map(TrackPoint.Id::new).collect(Collectors.toList()); .map(TrackPoint.Id::new).collect(Collectors.toList());
// when // when
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8), 5); Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8));
// then // then
assertEquals(2, cursor.getCount()); assertEquals(2, cursor.getCount());
@@ -96,34 +96,36 @@ public class SensorDataCyclingTest {
@Test @Test
public void compute_speed() { public void compute_speed() {
// given // given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184); SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016); SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
// when // when
current.compute(previous, 2150); current.compute(previous, 2150);
// then // then
assertEquals(1.20, current.getValue(), 0.01); assertEquals(1.20, current.getValue().distance_m, 2150);
assertEquals(1.20, current.getValue().speed_mps, 0.01);
} }
@Test @Test
public void compute_speed_rollOverCount() { public void compute_speed_rollOverCount() {
// given // given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024); SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048); SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when // when
current.compute(previous, 2000); current.compute(previous, 2000);
// then // then
assertEquals(2, current.getValue(), 0.01); assertEquals(1.20, current.getValue().distance_m, 2000);
assertEquals(2, current.getValue().speed_mps, 0.01);
} }
@Test @Test
public void equals_speed_with_no_data() { public void equals_speed_with_no_data() {
// given // given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress"); SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048); SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when // when
previous.toString(); previous.toString();
@@ -2,7 +2,7 @@ package de.dennisguse.opentracks.io.file.importer;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.location.Location; import android.content.SharedPreferences;
import android.os.Looper; import android.os.Looper;
import android.util.Log; import android.util.Log;
@@ -23,6 +23,7 @@ import org.junit.runners.JUnit4;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -31,6 +32,7 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker; 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.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -69,6 +71,7 @@ public class ExportImportTest {
} }
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
@@ -89,17 +92,17 @@ public class ExportImportTest {
trackId = service.startNewTrack(); 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.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.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), 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.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
service.pauseCurrentTrack(); service.pauseCurrentTrack();
service.resumeCurrentTrack(); service.resumeCurrentTrack();
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 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), 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), 0); service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, 5), 0);
service.endCurrentTrack(); service.endCurrentTrack();
track = contentProviderUtils.getTrack(trackId); track = contentProviderUtils.getTrack(trackId);
@@ -109,7 +112,7 @@ public class ExportImportTest {
contentProviderUtils.updateTrack(track); contentProviderUtils.updateTrack(track);
track = contentProviderUtils.getTrack(trackId); track = contentProviderUtils.getTrack(trackId);
trackPoints = contentProviderUtils.getTrackPoints(trackId); trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
markers = contentProviderUtils.getMarkers(trackId); markers = contentProviderUtils.getMarkers(trackId);
assertEquals(10, trackPoints.size()); assertEquals(10, trackPoints.size());
assertEquals(2, markers.size()); assertEquals(2, markers.size());
@@ -148,7 +151,7 @@ public class ExportImportTest {
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context); AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
// then // then
// 1. track // 1. track
@@ -161,10 +164,10 @@ public class ExportImportTest {
assertEquals(track.getUuid(), importedTrack.getUuid()); assertEquals(track.getUuid(), importedTrack.getUuid());
// 2. trackpoints // 2. trackpoints
assertTrackpoints(trackPoints, false, false, false, false, false); assertTrackpoints(trackPoints, false, false, false, false, false, false);
// 3. trackstatistics // 3. trackstatistics
assertTrackStatistics(false, false); assertTrackStatistics(false, false, false);
// 4. markers // 4. markers
assertMarkers(); assertMarkers();
@@ -187,7 +190,7 @@ public class ExportImportTest {
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context); AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
// then // then
// 1. track // 1. track
@@ -199,10 +202,10 @@ public class ExportImportTest {
assertEquals(track.getIcon(), importedTrack.getIcon()); assertEquals(track.getIcon(), importedTrack.getIcon());
// 2. trackpoints // 2. trackpoints
assertTrackpoints(trackPoints, true, true, true, true, true); assertTrackpoints(trackPoints, true, true, true, true, true, true);
// 2. trackstatistics // 2. trackstatistics
assertTrackStatistics(false, true); assertTrackStatistics(false, true, true);
// 4. markers // 4. markers
assertMarkers(); assertMarkers();
@@ -212,7 +215,7 @@ public class ExportImportTest {
@Test(expected = ImportAlreadyExistsException.class) @Test(expected = ImportAlreadyExistsException.class)
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() { public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
// given // 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); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
@@ -225,45 +228,13 @@ public class ExportImportTest {
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context); AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
// then // then
Track importedTrack = contentProviderUtils.getTrack(importTrackId); Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNull(importedTrack); 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 @LargeTest
@Test @Test
public void gpx() { public void gpx() {
@@ -281,7 +252,7 @@ public class ExportImportTest {
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils); AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
// then // then
// 1. track // 1. track
@@ -301,10 +272,10 @@ public class ExportImportTest {
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
trackPointsWithCoordinates.get(3).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 // 3. trackstatistics
assertTrackStatistics(true, true); assertTrackStatistics(true, true, false);
// 4. markers // 4. markers
assertMarkers(); assertMarkers();
@@ -314,7 +285,7 @@ public class ExportImportTest {
@Test(expected = ImportAlreadyExistsException.class) @Test(expected = ImportAlreadyExistsException.class)
public void gpx_duplicate_trackUUID() { public void gpx_duplicate_trackUUID() {
// given // 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); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
@@ -327,7 +298,7 @@ public class ExportImportTest {
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils); AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
// then // then
// 1. track // 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) { private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss, boolean verifyDistance) {
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId); List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(trackPoints.size(), importedTrackPoints.size()); assertEquals(trackPoints.size(), importedTrackPoints.size());
for (int i = 0; i < trackPoints.size(); i++) { for (int i = 0; i < trackPoints.size(); i++) {
@@ -391,13 +361,13 @@ public class ExportImportTest {
} }
if (verifyHeartrate) { if (verifyHeartrate) {
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01); assertEquals("" + trackPoint, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
} }
if (verifyCadence) { if (verifyCadence) {
assertEquals(trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01); assertEquals("" + trackPoint, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
} }
if (verifyPower) { if (verifyPower) {
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01); assertEquals("" + trackPoint, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
} }
if (verifyElevationGain) { if (verifyElevationGain) {
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01); assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
@@ -405,10 +375,13 @@ public class ExportImportTest {
if (verifyElevationLoss) { if (verifyElevationLoss) {
assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01); 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); Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack.getTrackStatistics()); assertNotNull(importedTrack.getTrackStatistics());
@@ -426,7 +399,9 @@ public class ExportImportTest {
assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime()); assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
// Distance // Distance
if (verifyDistance) {
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01); assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
}
// Speed // Speed
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed(), 0.01); 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) { 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) {
Location location = new Location(""); TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time));
location.setTime(time); tp.setAccuracy(accuracy);
location.setLongitude(longitude); tp.setSpeed(speed);
location.setLatitude(latitude);
location.setAccuracy(accuracy);
location.setAltitude(altitude);
location.setSpeed(speed);
TrackPoint tp = new TrackPoint(location);
tp.setHeartRate_bpm(heartRate); tp.setHeartRate_bpm(heartRate);
tp.setCyclingCadence_rpm(cyclingCadence); tp.setCyclingCadence_rpm(cyclingCadence);
tp.setPower(power); tp.setPower(power);
tp.setElevationGain(elevationGain); tp.setElevationGain(elevationGain);
tp.setSensorDistance(distance);
return tp; return tp;
} }
} }
@@ -14,6 +14,7 @@ import org.junit.runners.JUnit4;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -54,7 +55,7 @@ public class LegacyImportTest {
// when // when
// 1. import // 1. import
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
// then // then
// 1. track // 1. track
@@ -69,7 +70,7 @@ public class LegacyImportTest {
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId)); assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
// 3. trackpoints // 3. trackpoints
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId); List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size()); assertEquals(6, importedTrackPoints.size());
// first 3 trackpoints // 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); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
// when // when
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
} }
/** /**
@@ -110,7 +111,7 @@ public class LegacyImportTest {
// when // when
// 1. import // 1. import
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream).get(0);
// then // then
// 1. track // 1. track
@@ -122,7 +123,7 @@ public class LegacyImportTest {
assertEquals("UNKNOWN", importedTrack.getIcon()); assertEquals("UNKNOWN", importedTrack.getIcon());
// 3. trackpoints // 3. trackpoints
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId); List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size()); assertEquals(6, importedTrackPoints.size());
// first segment // first segment
@@ -12,6 +12,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
@@ -25,7 +26,10 @@ public class TrackRecordingServiceNotificationManagerTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Mock @Mock
private TrackPoint locationMock; private TrackPoint trackPointMock;
@Mock
private TrackStatistics trackStatisticsMock;
@Mock @Mock
private NotificationCompat.Builder notificationCompatBuilder; private NotificationCompat.Builder notificationCompatBuilder;
@@ -35,18 +39,20 @@ public class TrackRecordingServiceNotificationManagerTest {
@Test @Test
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() { public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
when(locationMock.hasAccuracy()).thenReturn(true); when(trackPointMock.hasAccuracy()).thenReturn(true);
when(locationMock.getAccuracy()).thenReturn(999f); when(trackPointMock.getAccuracy()).thenReturn(999f);
when(trackStatisticsMock.getTotalDistance()).thenReturn(0d);
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder); when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder); when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder); TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
subject.setMetricUnits(true);
// when // when
subject.updateTrackPoint(context, locationMock, 100); subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
subject.updateTrackPoint(context, locationMock, 100); subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
subject.updateTrackPoint(context, locationMock, 1000); subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 1000);
subject.updateTrackPoint(context, locationMock, 100); subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
// then // then
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true); verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
@@ -45,6 +45,7 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker; 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.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -84,6 +85,7 @@ public class TrackRecordingServiceTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong())); private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
@@ -102,7 +104,6 @@ public class TrackRecordingServiceTest {
contentProviderUtils = new ContentProviderUtils(context); contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values. // Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit(); sharedPreferences.edit().clear().commit();
// Ensure that the database is empty before every test // Ensure that the database is empty before every test
@@ -190,7 +191,7 @@ public class TrackRecordingServiceTest {
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// then // then
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(1, trackPoints.size()); assertEquals(1, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType()); assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
@@ -207,7 +208,7 @@ public class TrackRecordingServiceTest {
service.endCurrentTrack(); service.endCurrentTrack();
// then // then
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(2, trackPoints.size()); assertEquals(2, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType()); assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
@@ -225,7 +226,7 @@ public class TrackRecordingServiceTest {
service.pauseCurrentTrack(); service.pauseCurrentTrack();
// then // then
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size()); assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
//when //when
service.resumeTrack(trackId); service.resumeTrack(trackId);
@@ -234,7 +235,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording()); assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId()); assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(3, trackPoints.size()); assertEquals(3, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType()); assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType()); assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
@@ -250,7 +251,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording()); assertTrue(service.isRecording());
service.endCurrentTrack(); service.endCurrentTrack();
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size()); assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
// when // when
service.resumeTrack(trackId); service.resumeTrack(trackId);
@@ -260,7 +261,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording()); assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId()); assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(4, trackPoints.size()); assertEquals(4, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType()); assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType()); assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
@@ -277,7 +278,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording()); assertTrue(service.isRecording());
service.pauseCurrentTrack(); service.pauseCurrentTrack();
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size()); assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
// when // when
service.endCurrentTrack(); service.endCurrentTrack();
@@ -286,7 +287,7 @@ public class TrackRecordingServiceTest {
assertFalse(service.isRecording()); assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId()); assertNull(service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(2, trackPoints.size()); assertEquals(2, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType()); assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType()); assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
@@ -302,7 +303,7 @@ public class TrackRecordingServiceTest {
// when // when
// Just set recording track to a bogus value. // Just set recording track to a bogus value.
// Make sure that the service will not start recording and will clear the bogus track. // 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 // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
@@ -325,7 +326,7 @@ public class TrackRecordingServiceTest {
// then // then
assertNull(newTrackId); assertNull(newTrackId);
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context)); assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
assertEquals(trackId, service.getRecordingTrackId()); assertEquals(trackId, service.getRecordingTrackId());
} }
@@ -341,7 +342,7 @@ public class TrackRecordingServiceTest {
service.endCurrentTrack(); service.endCurrentTrack();
// then // then
assertFalse(PreferencesUtils.isRecording(context)); assertFalse(PreferencesUtils.isRecording(sharedPreferences, context));
assertNull(service.getRecordingTrackId()); assertNull(service.getRecordingTrackId());
} }
@@ -388,8 +389,8 @@ public class TrackRecordingServiceTest {
assertNotNull(track.getId()); assertNotNull(track.getId());
contentProviderUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId()); 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.setLong(sharedPreferences, 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.setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, !isRecording);
} }
// NOTE: Do not use to create a track that is currently recording. // NOTE: Do not use to create a track that is currently recording.
@@ -422,7 +423,6 @@ public class TrackRecordingServiceTest {
location.setTime(time); location.setTime(time);
location.setBearing(3.0f); location.setBearing(3.0f);
TrackPoint trackPoint = new TrackPoint(location); TrackPoint trackPoint = new TrackPoint(location);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext()); trackRecordingService.newTrackPoint(trackPoint, 50);
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
} }
} }
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; 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.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -104,7 +105,7 @@ public class TrackRecordingServiceTestLocation {
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(8, trackPoints.size()); assertEquals(8, trackPoints.size());
assertTrackPoints(List.of( assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null), new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -138,7 +139,7 @@ public class TrackRecordingServiceTestLocation {
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(4, trackPoints.size()); assertEquals(4, trackPoints.size());
assertTrackPoints(List.of( assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null), new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -185,7 +186,7 @@ public class TrackRecordingServiceTestLocation {
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrackPoints(List.of( assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null), new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1), new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
@@ -214,7 +215,7 @@ public class TrackRecordingServiceTestLocation {
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(6, trackPoints.size()); assertEquals(6, trackPoints.size());
assertTrackPoints(List.of( assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null), new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -260,7 +261,7 @@ public class TrackRecordingServiceTestLocation {
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(8, trackPoints.size()); assertEquals(8, trackPoints.size());
assertTrackPoints(List.of( assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null), new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -292,7 +293,7 @@ public class TrackRecordingServiceTestLocation {
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(7, trackPoints.size()); assertEquals(7, trackPoints.size());
assertTrackPoints(List.of( assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null), new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -53,6 +53,7 @@ public class TrackRecordingServiceTestLooper {
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS); public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
@@ -77,7 +78,6 @@ public class TrackRecordingServiceTestLooper {
contentProviderUtils = new ContentProviderUtils(context); contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values. // Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply(); sharedPreferences.edit().clear().apply();
// Ensure that the database is empty before every test // Ensure that the database is empty before every test
@@ -99,28 +99,28 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRequiredAccuracy() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -128,14 +128,14 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_maxRecordingDistance() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -143,42 +143,42 @@ public class TrackRecordingServiceTestLooper {
@Test @Test
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException { public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)); 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingDistance() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_metricUnitsDisabled() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingInterval() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -186,7 +186,7 @@ public class TrackRecordingServiceTestLooper {
@Test @Test
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException { public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)); 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(); fullRecordingSession();
} }
@@ -209,7 +209,7 @@ public class TrackRecordingServiceTestLooper {
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
assertNotNull(track); assertNotNull(track);
assertEquals(trackId, track.getId()); assertEquals(trackId, track.getId());
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context)); assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
assertEquals(trackId, service.getRecordingTrackId()); assertEquals(trackId, service.getRecordingTrackId());
// Insert a few points, markers and statistics. // Insert a few points, markers and statistics.
@@ -223,7 +223,7 @@ public class TrackRecordingServiceTestLooper {
location.setTime(startTime + i * 10000); location.setTime(startTime + i * 10000);
location.setBearing(3.0f); location.setBearing(3.0f);
TrackPoint trackPoint = new TrackPoint(location); TrackPoint trackPoint = new TrackPoint(location);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context); int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
service.newTrackPoint(trackPoint, prefAccuracy); service.newTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) { if (i % 7 == 0) {
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.services.handlers; package de.dennisguse.opentracks.services.handlers;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@@ -25,6 +26,9 @@ public class HandlerServerTest {
@Mock @Mock
private LocationHandler locationHandler; private LocationHandler locationHandler;
@Mock
private SharedPreferences sharedPreferences;
private HandlerServer subject; private HandlerServer subject;
@Before @Before
@@ -41,10 +45,10 @@ public class HandlerServerTest {
@Test @Test
public void onSharedPreferenceChanged() { public void onSharedPreferenceChanged() {
// when // when
subject.onSharedPreferenceChanged(null, null, null); subject.onSharedPreferenceChanged(context, sharedPreferences, null);
// then // then
verify(locationHandler).onSharedPreferenceChanged(null, null, null); verify(locationHandler).onSharedPreferenceChanged(context, sharedPreferences, null);
} }
@Test @Test
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
public class LocationHandlerTest { public class LocationHandlerTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@Mock @Mock
private HandlerServer handlerServer; private HandlerServer handlerServer;
@@ -43,11 +44,10 @@ public class LocationHandlerTest {
@Before @Before
public void setUp() { public void setUp() {
// Let's use default values. // Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit(); sharedPreferences.edit().clear().commit();
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key)); locationHandler.onSharedPreferenceChanged(context, sharedPreferences, 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.min_recording_interval_key));
locationHandler.onStart(context); locationHandler.onStart(context);
} }
@@ -85,7 +85,7 @@ public class LocationHandlerTest {
@Test @Test
public void testOnLocationChanged_poorAccuracy() { public void testOnLocationChanged_poorAccuracy() {
// given // given
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context); int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
// when // when
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis())); locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
@@ -1,27 +1,36 @@
package de.dennisguse.opentracks.stats; package de.dennisguse.opentracks.stats;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class TrackStatisticsUpdaterTest { public class TrackStatisticsUpdaterTest {
private static final int GPS_DISTANCE = 50;
@Test @Test
public void addTrackPoint() { public void addTrackPoint_TestingTrack() {
// given // given
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1)); TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
// when // when
TrackStatisticsUpdater updater = new TrackStatisticsUpdater(); TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
data.trackPoints.forEach(it -> updater.addTrackPoint(it, 50)); data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE));
// then // then
TrackStatistics statistics = updater.getTrackStatistics(); TrackStatistics statistics = subject.getTrackStatistics();
assertEquals(85.35, statistics.getTotalDistance(), 0.01); assertEquals(85.35, statistics.getTotalDistance(), 0.01);
assertEquals(Duration.ofMillis(13999), statistics.getTotalTime()); assertEquals(Duration.ofMillis(13999), statistics.getTotalTime());
assertEquals(Duration.ofSeconds(6), statistics.getMovingTime()); assertEquals(Duration.ofSeconds(6), statistics.getMovingTime());
@@ -35,4 +44,122 @@ public class TrackStatisticsUpdaterTest {
assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01); assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01);
assertEquals(6.566, statistics.getAverageSpeed(), 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.time.Duration;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
@@ -27,30 +26,26 @@ public class AnnouncementUtilsTest {
@Test @Test
public void getAnnouncement_metric() { public void getAnnouncement_metric() {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
TrackStatistics stats = new TrackStatistics(); TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000); stats.setTotalDistance(20000);
stats.setTotalTime(Duration.ofMillis(600000)); stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofMillis(300000)); stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(100); stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000f); stats.setTotalElevationGain(6000f);
// when // when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null); String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
// then // 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 @Test
public void getAnnouncement_withInterval_metric() { public void getAnnouncement_withInterval_metric() {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
TrackStatistics stats = new TrackStatistics(); TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000); stats.setTotalDistance(20000);
stats.setTotalTime(Duration.ofMillis(600000)); stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofMillis(300000)); stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(100); stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000f); stats.setTotalElevationGain(6000f);
@@ -58,12 +53,10 @@ public class AnnouncementUtilsTest {
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000); IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000);
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1); IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
String expected = "OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour Lap speed of 51.2 kilometers per hour";
// when // when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", lastInterval); String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
// then // then
assertEquals(expected, announcement); 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 // then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount()); assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertNull(sensor.getSpeed()); assertNull(sensor.getDistanceSpeed());
} }
@Test @Test
@@ -60,7 +60,7 @@ public class BluetoothUtilsTest {
// then // then
assertNull(sensor.getCadence()); assertNull(sensor.getCadence());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount()); assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
} }
@Test @Test
@@ -73,7 +73,7 @@ public class BluetoothUtilsTest {
// then // then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount()); assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount()); assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
} }
@Test @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 @Test
public void ExportTrackFileFormat_ok() { public void ExportTrackFileFormat_ok() {
// given // 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.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
editor.commit(); editor.commit();
// when // when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context); TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then // then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat); assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
@@ -36,12 +38,13 @@ public class PreferencesUtilsTest {
@Test @Test
public void ExportTrackFileFormat_invalid() { public void ExportTrackFileFormat_invalid() {
// given // 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.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
editor.commit(); editor.commit();
// when // when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context); TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then // then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat); assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -50,12 +53,13 @@ public class PreferencesUtilsTest {
@Test @Test
public void ExportTrackFileFormat_noValue() { public void ExportTrackFileFormat_noValue() {
// given // given
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit(); SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear(); editor.clear();
editor.commit(); editor.commit();
// when // when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context); TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then // then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat); assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.util; package de.dennisguse.opentracks.util;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -46,12 +47,14 @@ public class TrackNameUtilsTest {
private static final Context CONTEXT = ApplicationProvider.getApplicationContext(); 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. * Tests when the track_name_key is settings_recording_track_name_date_local_value.
*/ */
@Test @Test
public void testTrackName_date_local() { 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)); assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
} }
@@ -60,7 +63,7 @@ public class TrackNameUtilsTest {
*/ */
@Test @Test
public void testTrackName_date_iso_8601() { 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); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME)); assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
} }
@@ -70,7 +73,7 @@ public class TrackNameUtilsTest {
*/ */
@Test @Test
public void testTrackName_number() { 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)); assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
} }
} }
+5
View File
@@ -73,11 +73,13 @@ limitations under the License.
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/gpx+xml" /> <data android:mimeType="application/gpx+xml" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/gpx" /> <data android:mimeType="application/gpx" />
</intent-filter> </intent-filter>
@@ -135,6 +137,7 @@ limitations under the License.
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kmz" /> <data android:mimeType="application/vnd.google-earth.kmz" />
</intent-filter> </intent-filter>
@@ -192,6 +195,8 @@ limitations under the License.
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kml+xml" /> <data android:mimeType="application/vnd.google-earth.kml+xml" />
</intent-filter> </intent-filter>
@@ -66,7 +66,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
public void onConfirmDeleteDone(Track.Id... trackIds) { public void onConfirmDeleteDone(Track.Id... trackIds) {
boolean stopRecording = false; boolean stopRecording = false;
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(this); Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(PreferencesUtils.getSharedPreferences(this), this);
for (Track.Id trackId : trackIds) { for (Track.Id trackId : trackIds) {
if (trackId.equals(recordingTrackId)) { if (trackId.equals(recordingTrackId)) {
stopRecording = true; stopRecording = true;
@@ -66,12 +66,12 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @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)) { 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)) { 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) { if (key != null) {
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu); runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
@@ -138,7 +138,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
} }
@Override @Override
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks; package de.dennisguse.opentracks;
import android.app.Application; import android.app.Application;
import android.content.SharedPreferences;
import android.os.StrictMode; import android.os.StrictMode;
import android.util.Log; 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); 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. //Set default values of preferences on first start.
PreferencesUtils.resetPreferences(this, false); SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) { 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); 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. //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."); 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. //In debug builds: show thread and VM warnings.
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.d(TAG, "Enabling strict mode"); Log.d(TAG, "Enabling strict mode");
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() StrictMode.enableDefaults();
.detectAll()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
} }
} }
} }
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks; package de.dennisguse.opentracks;
import android.Manifest; import android.Manifest;
import android.app.ActivityOptions;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -29,6 +30,7 @@ import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@@ -107,18 +109,18 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @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)) { 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)) { 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)) { if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
trackRecordingServiceConnection.startAndBind(TrackListActivity.this); trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
} }
} }
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) { 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) { if (key != null) {
runOnUiThread(() -> { runOnUiThread(() -> {
@@ -195,17 +197,20 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView); viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> { viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
Intent newIntent;
if (trackId == recordingTrackId.getId()) { if (trackId == recordingTrackId.getId()) {
// Is recording -> open record activity. // 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)); .putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
startActivity(newIntent);
} else { } else {
// Not recording -> open detail activity. // 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)); .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) { resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@@ -257,7 +262,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
super.onStart(); super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
trackRecordingServiceConnection.startConnection(this); trackRecordingServiceConnection.startConnection(this);
} }
@@ -24,6 +24,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.adapter.FragmentStateAdapter;
@@ -55,6 +56,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
private static final String TAG = TrackRecordedActivity.class.getSimpleName(); 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_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id"; public static final String EXTRA_MARKER_ID = "marker_id";
@@ -84,6 +87,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
if (savedInstanceState != null) { if (savedInstanceState != null) {
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY)); viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
} }
postponeEnterTransition();
} }
@Override @Override
@@ -138,7 +143,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { 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_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); Track track = contentProviderUtils.getTrack(trackId);
setTitle(track != null ? track.getName() : ""); setTitle(track != null ? track.getName() : "");
return super.onPrepareOptionsMenu(menu); 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() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @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)) { if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackRecordingActivity.this);
setLockscreenPolicy(); setLockscreenPolicy();
setScreenOnPolicy(); setScreenOnPolicy();
} }
@@ -170,7 +170,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
private void setLockscreenPolicy() { 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen); setShowWhenLocked(showOnLockScreen);
@@ -182,7 +182,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
private void setScreenOnPolicy() { private void setScreenOnPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this); boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
if (keepScreenOn) { if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -192,7 +192,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
private void setFullscreenPolicy() { private void setFullscreenPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this); boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
if (keepScreenOn) { if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -206,7 +206,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
super.onStart(); super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
trackRecordingServiceConnection.startConnection(this); trackRecordingServiceConnection.startConnection(this);
trackDataHub.start(); trackDataHub.start();
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.adapters; package de.dennisguse.opentracks.adapters;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.util.Pair; import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -134,11 +135,13 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
} }
} }
//TODO Check preference handling.
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) { private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String category = aggregatedStatistic.getCategory(); String category = aggregatedStatistic.getCategory();
reportSpeed = PreferencesUtils.isReportSpeed(context, category); SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
metricsUnits = PreferencesUtils.isMetricUnits(context); reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
sportIcon.setImageResource(getIcon(aggregatedStatistic)); sportIcon.setImageResource(getIcon(aggregatedStatistic));
typeLabel.setText(category); typeLabel.setText(category);
@@ -25,11 +25,12 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) { public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) {
super(context, R.layout.interval_stats_list_item, intervalList); super(context, R.layout.interval_stats_list_item, intervalList);
metricUnits = PreferencesUtils.isMetricUnits(context); metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
this.category = category; this.category = category;
this.stackMode = stackMode; this.stackMode = stackMode;
} }
//TODO Check preference handling! Should not be accessed in getView()
@NonNull @NonNull
@Override @Override
public View getView(int position, @Nullable View intervalView, @NonNull ViewGroup parent) { 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)); 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)); viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, true));
} else { } else {
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, false)); viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, false));
@@ -34,7 +34,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
private final Activity activity; private final Activity activity;
//TODO Should be Marker.Id //TODO Should be Marker.Id
private final ExecutorListViewService executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT); private final ExecutorListViewService<Long> executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
private boolean scroll = false; private boolean scroll = false;
@@ -71,6 +71,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
private final int targetNumPoints; private final int targetNumPoints;
private SharedPreferences sharedPreferences;
private boolean started; private boolean started;
private HandlerThread handlerThread; private HandlerThread handlerThread;
private Handler handler; private Handler handler;
@@ -140,8 +142,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver); contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
PreferencesUtils.register(context, this); sharedPreferences = PreferencesUtils.getSharedPreferences(context);
onSharedPreferenceChanged(null, null); sharedPreferences.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(sharedPreferences, null);
handler.post(() -> { handler.post(() -> {
if (started) { if (started) {
loadDataForAll(); loadDataForAll();
@@ -155,7 +159,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
return; return;
} }
PreferencesUtils.unregister(context, this); sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
sharedPreferences = null;
started = false; started = false;
@@ -224,10 +229,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
handler.post(() -> { handler.post(() -> {
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) { 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)) { if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context); recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context);
} }
}); });
} }
@@ -109,6 +109,19 @@ public class Track {
this.trackStatistics = trackStatistics; 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 { public static class Id implements Parcelable {
private final long id; private final long id;
@@ -57,6 +57,7 @@ public class TrackPoint {
private Double altitude_m; private Double altitude_m;
private Float speed_mps; private Float speed_mps;
private Float bearing; private Float bearing;
private Float sensorDistance_m;
public enum Type { public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume) SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
@@ -224,7 +225,7 @@ public class TrackPoint {
return elevationGain != null; return elevationGain != null;
} }
public Float getElevationGain() { public float getElevationGain() {
return elevationGain; return elevationGain;
} }
@@ -310,8 +311,12 @@ public class TrackPoint {
this.accuracy = horizontalAccuracy; this.accuracy = horizontalAccuracy;
} }
public float distanceTo(@NonNull TrackPoint dest) { public float distanceToPrevious(@NonNull TrackPoint previous) {
return getLocation().distanceTo(dest.getLocation()); if (hasSensorDistance()) {
return getSensorDistance();
}
return getLocation().distanceTo(previous.getLocation());
} }
public boolean fulfillsAccuracy(int poorAccuracy) { public boolean fulfillsAccuracy(int poorAccuracy) {
@@ -327,6 +332,18 @@ public class TrackPoint {
} }
// Sensor data // 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() { public boolean hasSensorData() {
return hasHeartRate() || hasCyclingCadence() || hasPower(); return hasHeartRate() || hasCyclingCadence() || hasPower();
} }
@@ -347,7 +364,7 @@ public class TrackPoint {
return cyclingCadence_rpm != null; return cyclingCadence_rpm != null;
} }
public Float getCyclingCadence_rpm() { public float getCyclingCadence_rpm() {
return cyclingCadence_rpm; return cyclingCadence_rpm;
} }
@@ -359,7 +376,7 @@ public class TrackPoint {
return power != null; return power != null;
} }
public Float getPower() { public float getPower() {
return power; return power;
} }
@@ -50,6 +50,7 @@ public interface TrackPointsColumns extends BaseColumns {
String BEARING = "bearing"; String BEARING = "bearing";
String SENSOR_HEARTRATE = "sensor_heartrate"; String SENSOR_HEARTRATE = "sensor_heartrate";
String SENSOR_CADENCE = "sensor_cadence"; String SENSOR_CADENCE = "sensor_cadence";
String SENSOR_DISTANCE = "sensor_distance"; //DISTANCE from previous TrackPoint
String SENSOR_POWER = "sensor_power"; String SENSOR_POWER = "sensor_power";
String ELEVATION_GAIN = "elevation_gain"; String ELEVATION_GAIN = "elevation_gain";
String ELEVATION_LOSS = "elevation_loss"; String ELEVATION_LOSS = "elevation_loss";
@@ -77,6 +78,7 @@ public interface TrackPointsColumns extends BaseColumns {
+ ELEVATION_GAIN + " FLOAT, " + ELEVATION_GAIN + " FLOAT, "
+ ELEVATION_LOSS + " FLOAT, " + ELEVATION_LOSS + " FLOAT, "
+ TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), " + 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" + "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ ")"; + ")";
@@ -19,6 +19,7 @@ class CachedTrackPointsIndexes {
final int bearingIndex; final int bearingIndex;
final int sensorHeartRateIndex; final int sensorHeartRateIndex;
final int sensorCadenceIndex; final int sensorCadenceIndex;
final int sensorDistanceIndex;
final int sensorPowerIndex; final int sensorPowerIndex;
final int elevationGainIndex; final int elevationGainIndex;
final int elevationLossIndex; final int elevationLossIndex;
@@ -35,6 +36,7 @@ class CachedTrackPointsIndexes {
bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE); sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE); sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE);
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER); sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN); elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS); elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS);
@@ -70,7 +70,6 @@ public class ContentProviderUtils {
private static final String ID_SEPARATOR = ","; private static final String ID_SEPARATOR = ",";
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
private int defaultCursorBatchSize = 2000;
public ContentProviderUtils(Context context) { public ContentProviderUtils(Context context) {
contentResolver = context.getContentResolver(); contentResolver = context.getContentResolver();
@@ -556,6 +555,9 @@ public class ContentProviderUtils {
if (!cursor.isNull(indexes.sensorCadenceIndex)) { if (!cursor.isNull(indexes.sensorCadenceIndex)) {
trackPoint.setCyclingCadence_rpm(cursor.getFloat(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)) { if (!cursor.isNull(indexes.sensorPowerIndex)) {
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex)); trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
} }
@@ -629,9 +631,9 @@ public class ContentProviderUtils {
* *
* @param trackId the track id * @param trackId the track id
* @param startTrackPointId the starting trackPoint id. `null` to ignore * @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 selection;
String[] selectionArgs; String[] selectionArgs;
if (startTrackPointId != null) { if (startTrackPointId != null) {
@@ -642,11 +644,7 @@ public class ContentProviderUtils {
selectionArgs = new String[]{Long.toString(trackId.getId())}; selectionArgs = new String[]{Long.toString(trackId.getId())};
} }
String sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER; return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER);
if (maxLocations != null) {
sortOrder += " LIMIT " + maxLocations;
}
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
} }
/** /**
@@ -708,6 +706,9 @@ public class ContentProviderUtils {
if (trackPoint.hasCyclingCadence()) { if (trackPoint.hasCyclingCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm()); values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
} }
if (trackPoint.hasSensorDistance()) {
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance());
}
if (trackPoint.hasPower()) { if (trackPoint.hasPower()) {
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower()); values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
} }
@@ -757,39 +758,6 @@ public class ContentProviderUtils {
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder); 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) { public static String formatIdListForUri(Track.Id... trackIds) {
long[] ids = new long[trackIds.length]; long[] ids = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) { for (int i = 0; i < trackIds.length; i++) {
@@ -259,8 +259,7 @@ public class CustomContentProvider extends ContentProvider {
break; break;
case TRACKS_SENSOR_STATS: case TRACKS_SENSOR_STATS:
long trackId = ContentUris.parseId(url); long trackId = ContentUris.parseId(url);
Cursor cursor = db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)}); return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
return cursor;
case MARKERS: case MARKERS:
queryBuilder.setTables(MarkerColumns.TABLE_NAME); queryBuilder.setTables(MarkerColumns.TABLE_NAME);
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER; sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
@@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName(); private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 30; private static final int DATABASE_VERSION = 31;
@VisibleForTesting @VisibleForTesting
public static final String DATABASE_NAME = "database.db"; public static final String DATABASE_NAME = "database.db";
@@ -82,6 +82,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 30: case 30:
upgradeFrom29to30(db); upgradeFrom29to30(db);
break; break;
case 31:
upgradeFrom30to31(db);
break;
default: default:
throw new RuntimeException("Not implemented: upgrade to " + toVersion); throw new RuntimeException("Not implemented: upgrade to " + toVersion);
@@ -115,7 +118,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 29: case 29:
downgradeFrom30to29(db); downgradeFrom30to29(db);
break; break;
case 30:
downgradeFrom31to30(db);
break;
default: default:
throw new RuntimeException("Not implemented: downgrade to " + toVersion); throw new RuntimeException("Not implemented: downgrade to " + toVersion);
} }
@@ -394,4 +399,32 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); 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; package de.dennisguse.opentracks.content.provider;
import android.database.Cursor; import android.database.Cursor;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@@ -10,9 +12,8 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; 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 { public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private static final String TAG = TrackPointIterator.class.getSimpleName(); 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 ContentProviderUtils contentProviderUtils;
private final Track.Id trackId; private final Track.Id trackId;
private final CachedTrackPointsIndexes indexes; private final CachedTrackPointsIndexes indexes;
private TrackPoint.Id lastTrackPointId = null;
private Cursor cursor; private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) { public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
@@ -28,28 +28,11 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
this.trackId = trackId; this.trackId = trackId;
cursor = getCursor(startTrackPointId); cursor = getCursor(startTrackPointId);
indexes = cursor != null ? new CachedTrackPointsIndexes(cursor) indexes = new CachedTrackPointsIndexes(cursor);
: null;
} }
/**
* Gets the track point cursor.
*
* @param trackPointId the starting track point id
*/
private Cursor getCursor(TrackPoint.Id trackPointId) { private Cursor getCursor(TrackPoint.Id trackPointId) {
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize()); return contentProviderUtils.getTrackPointCursor(trackId, trackPointId);
}
/**
* 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;
} }
@Override @Override
@@ -57,32 +40,23 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
if (cursor == null) { if (cursor == null) {
return false; return false;
} }
if (cursor.isAfterLast()) { return !cursor.isLast() && !cursor.isAfterLast();
return false;
}
if (cursor.isLast()) {
if (cursor.getCount() != contentProviderUtils.getDefaultCursorBatchSize()) {
return false;
}
return advanceCursorToNextBatch() && !cursor.isAfterLast();
}
return true;
} }
@Override @Override
@NonNull
public TrackPoint next() { public TrackPoint next() {
if (cursor == null) { if (cursor == null || !cursor.moveToNext()) {
throw new NoSuchElementException(); 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); return ContentProviderUtils.fillTrackPoint(cursor, indexes);
} }
@VisibleForTesting
public int getCount() {
return cursor.getCount();
}
@Override @Override
public void close() { public void close() {
if (cursor != null) { if (cursor != null) {
@@ -54,6 +54,12 @@ public abstract class SensorData<T> {
return value; 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. * Is the data recent considering the current time.
*/ */
@@ -61,4 +67,10 @@ public abstract class SensorData<T> {
return Instant.now() return Instant.now()
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)); .isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
} }
@NonNull
@Override
public String toString() {
return "sensorAddress='" + sensorAddress;
}
} }
@@ -41,7 +41,7 @@ public final class SensorDataCycling {
/** /**
* Workaround for Wahoo CADENCE: provides speed instead of cadence * 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); this(speed.getSensorAddress(), speed.getSensorName(), speed.wheelRevolutionsCount, speed.wheelRevolutionsTime);
} }
@@ -74,7 +74,7 @@ public final class SensorDataCycling {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount; return super.toString() + " cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
} }
@Override @Override
@@ -90,18 +90,18 @@ public final class SensorDataCycling {
} }
} }
public static class Speed extends SensorData<Float> { public static class DistanceSpeed extends SensorData<DistanceSpeed.Data> {
private final Integer wheelRevolutionsCount; // UINT16 private final Integer wheelRevolutionsCount; // UINT16
private final Integer wheelRevolutionsTime; // UINT16; 1/1024s private final Integer wheelRevolutionsTime; // UINT16; 1/1024s
public Speed(String sensorAddress) { public DistanceSpeed(String sensorAddress) {
super(sensorAddress); super(sensorAddress);
this.wheelRevolutionsCount = null; this.wheelRevolutionsCount = null;
this.wheelRevolutionsTime = 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); super(sensorAddress, sensorName);
this.wheelRevolutionsCount = wheelRevolutionsCount; this.wheelRevolutionsCount = wheelRevolutionsCount;
this.wheelRevolutionsTime = wheelRevolutionsTime; this.wheelRevolutionsTime = wheelRevolutionsTime;
@@ -119,7 +119,7 @@ public final class SensorDataCycling {
return wheelRevolutionsTime; return wheelRevolutionsTime;
} }
public void compute(Speed previous, int wheel_circumference_mm) { public void compute(DistanceSpeed previous, int wheel_circumference_mm) {
if (hasData() && previous != null && previous.hasData()) { if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS; float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
if (timeDiff_ms <= 0) { if (timeDiff_ms <= 0) {
@@ -127,43 +127,79 @@ public final class SensorDataCycling {
value = null; value = null;
} else { } else {
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX); 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; double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S;
value = (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 @NonNull
@Override @Override
public String toString() { public String toString() {
return "speed=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount; return super.toString() + " data=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
} }
@Override @Override
public boolean equals(@Nullable Object obj) { 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()) { if (hasData() && comp.hasData() == hasData()) {
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime(); return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
} else { } else {
return false; 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;
} }
public static class CadenceAndSpeed extends SensorData<Pair<Cadence, Speed>> { @Override
public String toString() {
return "Data{" +
"distance_m=" + distance_m +
", distance_overall_m=" + distance_overall_m +
", speed_mps=" + speed_mps +
'}';
}
}
}
public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) { public static class CadenceAndSpeed extends SensorData<Pair<Cadence, DistanceSpeed>> {
public CadenceAndSpeed(String sensorAddress, String sensorName, @Nullable Cadence cadence, @Nullable DistanceSpeed distanceSpeed) {
super(sensorAddress, sensorName); super(sensorAddress, sensorName);
this.value = new Pair<>(cadence, speed); this.value = new Pair<>(cadence, distanceSpeed);
} }
public Cadence getCadence() { public Cadence getCadence() {
return this.value != null ? this.value.first : null; return this.value != null ? this.value.first : null;
} }
public Speed getSpeed() { public DistanceSpeed getDistanceSpeed() {
return this.value != null ? this.value.second : null; return this.value != null ? this.value.second : null;
} }
} }
@@ -16,6 +16,6 @@ public class SensorDataCyclingPower extends SensorData<Float> {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "power=" + value; return super.toString() + " power=" + value;
} }
} }
@@ -16,6 +16,6 @@ public class SensorDataHeartRate extends SensorData<Float> {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "heart=" + value; return super.toString() + " heart=" + value;
} }
} }
@@ -10,7 +10,7 @@ public final class SensorDataSet {
private SensorDataCycling.Cadence cyclingCadence; private SensorDataCycling.Cadence cyclingCadence;
private SensorDataCycling.Speed cyclingSpeed; private SensorDataCycling.DistanceSpeed cyclingDistanceSpeed;
private SensorDataCyclingPower cyclingPower; private SensorDataCyclingPower cyclingPower;
@@ -25,8 +25,8 @@ public final class SensorDataSet {
return cyclingCadence; return cyclingCadence;
} }
public SensorDataCycling.Speed getCyclingSpeed() { public SensorDataCycling.DistanceSpeed getCyclingDistanceSpeed() {
return cyclingSpeed; return cyclingDistanceSpeed;
} }
public SensorDataCyclingPower getCyclingPower() { public SensorDataCyclingPower getCyclingPower() {
@@ -44,7 +44,7 @@ public final class SensorDataSet {
public void clear() { public void clear() {
this.heartRate = null; this.heartRate = null;
this.cyclingCadence = null; this.cyclingCadence = null;
this.cyclingSpeed = null; this.cyclingDistanceSpeed = null;
this.cyclingPower = null; this.cyclingPower = null;
} }
@@ -57,8 +57,9 @@ public final class SensorDataSet {
trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue()); trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue());
} }
if (cyclingSpeed != null && cyclingSpeed.hasValue()) { if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
trackPoint.setSpeed(cyclingSpeed.getValue()); trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m);
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps);
} }
if (cyclingPower != null && cyclingPower.hasValue()) { if (cyclingPower != null && cyclingPower.hasValue()) {
@@ -66,12 +67,19 @@ public final class SensorDataSet {
} }
} }
public void reset() {
if (heartRate != null) heartRate.reset();
if (cyclingCadence != null) cyclingCadence.reset();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
if (cyclingPower != null) cyclingPower.reset();
}
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return (getHeartRate() != null ? "" + getHeartRate() : "") return (getHeartRate() != null ? "" + getHeartRate() : "")
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "") + (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "") + (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
+ (getCyclingPower() != null ? " " + getCyclingPower() : ""); + (getCyclingPower() != null ? " " + getCyclingPower() : "");
} }
@@ -85,8 +93,8 @@ public final class SensorDataSet {
this.cyclingCadence = (SensorDataCycling.Cadence) data; this.cyclingCadence = (SensorDataCycling.Cadence) data;
return; return;
} }
if (type instanceof SensorDataCycling.Speed) { if (type instanceof SensorDataCycling.DistanceSpeed) {
this.cyclingSpeed = (SensorDataCycling.Speed) data; this.cyclingDistanceSpeed = (SensorDataCycling.DistanceSpeed) data;
return; return;
} }
@@ -65,31 +65,28 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return chartFragment; return chartFragment;
} }
private final List<ChartPoint> pendingPoints = new ArrayList<>(); private SharedPreferences sharedPreferences;
private int recordingDistanceInterval;
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
// Stats gathered from the received data // Stats gathered from the received data
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(); private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
private final List<ChartPoint> pendingPoints = new ArrayList<>();
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 String category = ""; private String category = "";
// Modes of operation // Modes of operation
private boolean chartByDistance; private boolean chartByDistance;
private ChartBinding viewBinding;
// UI elements // UI elements
private ChartView chartView; private ChartView chartView;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override @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)) { 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()) { if (metricUnits != chartView.getMetricUnits()) {
chartView.setMetricUnits(metricUnits); chartView.setMetricUnits(metricUnits);
runOnUiThread(() -> { runOnUiThread(() -> {
@@ -100,7 +97,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
} }
} }
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) { 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()) { if (reportSpeed != chartView.getReportSpeed()) {
chartView.setReportSpeed(reportSpeed); chartView.setReportSpeed(reportSpeed);
chartView.applyReportSpeed(); chartView.applyReportSpeed();
@@ -114,7 +111,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
} }
if (PreferencesUtils.isKey(getContext(), R.string.recording_distance_interval_key, key)) { 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() { public void onResume() {
super.onResume(); super.onResume();
resumeTrackDataHub(); resumeTrackDataHub();
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener); sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
checkChartSettings(); checkChartSettings();
getActivity().runOnUiThread(updateChart); getActivity().runOnUiThread(updateChart);
@@ -176,7 +174,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
pauseTrackDataHub(); pauseTrackDataHub();
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
} }
@Override @Override
@@ -192,6 +190,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
viewBinding = null; viewBinding = null;
} }
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
@Override @Override
public void onTrackUpdated(Track track) { public void onTrackUpdated(Track track) {
if (isResumed()) { if (isResumed()) {
@@ -201,7 +205,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
} }
category = track.getCategory(); category = track.getCategory();
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category); boolean reportSpeed = PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category);
if (reportSpeed != chartView.getReportSpeed()) { if (reportSpeed != chartView.getReportSpeed()) {
chartView.setReportSpeed(reportSpeed); chartView.setReportSpeed(reportSpeed);
chartView.applyReportSpeed(); chartView.applyReportSpeed();
@@ -44,14 +44,16 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
private IntervalStatisticsAdapter adapter; private IntervalStatisticsAdapter adapter;
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter; private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
private String category; private String category;
private IntervalListViewBinding viewBinding; 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)) { 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) { if (adapter != null) {
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
spinnerAdapter.notifyDataSetChanged(); spinnerAdapter.notifyDataSetChanged();
@@ -73,8 +75,9 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener); sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
metricUnits = PreferencesUtils.isMetricUnits(getContext()); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView); viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
@@ -125,6 +128,8 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
pauseTrackDataHub(); pauseTrackDataHub();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
} }
@Override @Override
@@ -137,7 +142,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener); sharedPreferences = null;
adapter = null; adapter = null;
viewModel = null; viewModel = null;
@@ -183,7 +188,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
category = track != null ? track.getCategory() : ""; category = track != null ? track.getCategory() : "";
// Set rate label. // 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); viewBinding.intervalRate.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
} }
}); });
@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.adapters.SensorsAdapter; import de.dennisguse.opentracks.adapters.SensorsAdapter;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -64,7 +65,6 @@ public class StatisticsRecordedFragment extends Fragment {
return fragment; return fragment;
} }
private TrackStatistics trackStatistics; private TrackStatistics trackStatistics;
private SensorStatistics sensorStatistics; private SensorStatistics sensorStatistics;
private SensorsAdapter sensorsAdapter; private SensorsAdapter sensorsAdapter;
@@ -152,26 +152,35 @@ public class StatisticsRecordedFragment extends Fragment {
getActivity().runOnUiThread(() -> { getActivity().runOnUiThread(() -> {
if (isResumed()) { if (isResumed()) {
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
trackStatistics = track != null ? track.getTrackStatistics() : null; trackStatistics = track.getTrackStatistics();
sensorStatistics = contentProviderUtils.getSensorStats(trackId); sensorStatistics = contentProviderUtils.getSensorStats(trackId);
String newCategory = track != null ? track.getCategory() : ""; String newCategory = track.getCategory();
if (!category.equals(newCategory)) { if (!category.equals(newCategory)) {
category = newCategory; category = newCategory;
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key)); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
} }
loadTrackDescription(track);
updateUI(); updateUI();
updateSensorUI(); updateSensorUI();
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon, viewBinding.statsNameValue);
} }
}); });
} }
} }
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() { private void updateUI() {
// Set total distance // Set total distance
{ {
double totalDistance = trackStatistics == null ? Double.NaN : trackStatistics.getTotalDistance(); double totalDistance = trackStatistics.getTotalDistance();
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits); Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
viewBinding.statsDistanceValue.setText(parts.first); viewBinding.statsDistanceValue.setText(parts.first);
@@ -185,14 +194,14 @@ public class StatisticsRecordedFragment extends Fragment {
} }
// Set time and start datetime // Set time and start datetime
if (trackStatistics != null) { {
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getMovingTime())); viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()));
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime())); viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
} }
// Set average speed/pace // Set average speed/pace
{ {
double speed = trackStatistics != null ? trackStatistics.getAverageSpeed() : Double.NaN; double speed = trackStatistics.getAverageSpeed();
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? 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, preferenceMetricUnits, preferenceReportSpeed); Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
@@ -202,7 +211,7 @@ public class StatisticsRecordedFragment extends Fragment {
// Set max speed/pace // Set max speed/pace
{ {
double speed = trackStatistics == null ? Double.NaN : trackStatistics.getMaxSpeed(); double speed = trackStatistics.getMaxSpeed();
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace); viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
@@ -213,7 +222,7 @@ public class StatisticsRecordedFragment extends Fragment {
// Set moving speed/pace // Set moving speed/pace
{ {
double speed = trackStatistics != null ? trackStatistics.getAverageMovingSpeed() : Double.NaN; double speed = trackStatistics.getAverageMovingSpeed();
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? 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);
@@ -225,11 +234,11 @@ public class StatisticsRecordedFragment extends Fragment {
// Set elevation gain and loss // Set elevation gain and loss
{ {
// Make elevation visible? // Make elevation visible?
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext()); boolean showElevation = PreferencesUtils.isShowStatsElevation(sharedPreferences, getContext());
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE); viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
Float elevationGain_m = trackStatistics != null ? trackStatistics.getTotalElevationGain() : null; Float elevationGain_m = trackStatistics.getTotalElevationGain();
Float elevationLoss_m = trackStatistics != null ? trackStatistics.getTotalElevationLoss() : null; Float elevationLoss_m = trackStatistics.getTotalElevationLoss();
Pair<String, String> parts; Pair<String, String> parts;
@@ -62,6 +62,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection(); private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private TrackPoint lastTrackPoint; private TrackPoint lastTrackPoint;
//TODO Should not be nullable
private TrackStatistics lastTrackStatistics; private TrackStatistics lastTrackStatistics;
private String category = ""; private String category = "";
@@ -366,29 +367,26 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
} }
private void setSpeedSensorData(SensorDataSet sensorDataSet) { private void setSpeedSensorData(SensorDataSet sensorDataSet) {
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) { if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed(); SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
if (data.hasValue() && data.isRecent()) { if (data.hasValue() && data.isRecent()) {
setSpeed(data.getValue()); setTotalDistance(data.getValue().distance_overall_m);
setSpeed(data.getValue().speed_mps);
}
if (data.hasValue() && data.isRecent()) {
setSpeed(data.getValue().speed_mps);
} }
} }
} }
private void updateStats() { private void updateStats() {
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
// Set total distance setTotalDistance(0);
{
double totalDistance = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getTotalDistance();
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
viewBinding.statsDistanceValue.setText(parts.first);
viewBinding.statsDistanceUnit.setText(parts.second);
}
// Set activity type // Set activity type
{ {
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
viewBinding.statsActivityTypeIcon.setEnabled(isSelectedTrackRecording()); viewBinding.statsActivityTypeIcon.setEnabled(isSelectedTrackRecording());
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue)); viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
} }
@@ -479,6 +477,14 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
} }
} }
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) { private void setSpeed(double speed) {
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace); viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
@@ -91,7 +91,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
// List of tracks to be exported. // 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 LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
private final Handler conflictsHandler = new Handler(); private final Handler conflictsHandler = new Handler();
@@ -187,7 +187,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
conflictsQueue.clear(); conflictsQueue.clear();
trackIds.clear(); tracks.clear();
} }
@Override @Override
@@ -215,11 +215,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
for (int i = from; i < trackExportTotalCount; i++) { for (int i = from; i < trackExportTotalCount; i++) {
cursor.moveToPosition(i); cursor.moveToPosition(i);
Track track = ContentProviderUtils.createTrack(cursor); Track track = ContentProviderUtils.createTrack(cursor);
trackIds.add(track.getId()); tracks.add(track);
} }
if (!trackIds.isEmpty()) { if (!tracks.isEmpty()) {
export(trackIds.get(0)); export(tracks.get(0));
} else { } else {
onExportEnded(); 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. * @param conflictResolution conflict resolution to be applied if needed.
*/ */
private void export(Track.Id trackId, int conflictResolution) { private void export(Track track, int conflictResolution) {
boolean fileExists = ExportUtils.isExportFileExists(trackId, trackFileFormat.getExtension(), directoryFiles); boolean fileExists = ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles);
if (fileExists && conflictResolution == CONFLICT_NONE) { if (fileExists && conflictResolution == CONFLICT_NONE) {
conflict(trackId); conflict(track);
} else if (fileExists && conflictResolution == CONFLICT_SKIP) { } else if (fileExists && conflictResolution == CONFLICT_SKIP) {
trackExportSkippedCount++; trackExportSkippedCount++;
onExportCompleted(trackId); onExportCompleted(track);
} else { } else {
ExportService.enqueue(this, resultReceiver, trackId, trackFileFormat, directoryUri); ExportService.enqueue(this, resultReceiver, track.getId(), trackFileFormat, directoryUri);
} }
} }
private void export(Track.Id trackId) { private void export(Track track) {
export(trackId, autoConflict); export(track, autoConflict);
} }
private void setConflictVisibility(int visibility) { 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); viewBinding.exportProgressSummaryErrorsGroup.setVisibility(trackExportErrorCount > 0 ? View.VISIBLE : View.GONE);
} }
private void onExportCompleted(Track.Id trackId) { private void onExportCompleted(Track track) {
trackIds.remove(trackId); tracks.remove(track);
setProgress(); setProgress();
if (trackIds.isEmpty()) { if (tracks.isEmpty()) {
onExportEnded(); onExportEnded();
return; return;
} }
export(trackIds.get(0)); export(tracks.get(0));
} }
private void onExportEnded() { 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.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
Track track = contentProviderUtils.getTrack(trackId);
switch (resultCode) { switch (resultCode) {
case ExportServiceResultReceiver.RESULT_CODE_ERROR: case ExportServiceResultReceiver.RESULT_CODE_ERROR:
trackExportErrorCount++; trackExportErrorCount++;
Track track = contentProviderUtils.getTrack(trackId);
trackErrors.add(track.getName()); trackErrors.add(track.getName());
break; break;
case ExportServiceResultReceiver.RESULT_CODE_SUCCESS: case ExportServiceResultReceiver.RESULT_CODE_SUCCESS:
if (ExportUtils.isExportFileExists(trackId, trackFileFormat.getExtension(), directoryFiles)) { if (ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles)) {
trackExportOverwrittenCount++; trackExportOverwrittenCount++;
} else { } else {
trackExportSuccessCount++; trackExportSuccessCount++;
@@ -335,11 +336,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
throw new RuntimeException(TAG + ": export service result code invalid: " + resultCode); throw new RuntimeException(TAG + ": export service result code invalid: " + resultCode);
} }
onExportCompleted(trackId); onExportCompleted(track);
} }
private void conflict(Track.Id trackId) { private void conflict(Track track) {
PendingConflict newConflict = new PendingConflict(trackId); PendingConflict newConflict = new PendingConflict(track);
conflictsQueue.add(newConflict); conflictsQueue.add(newConflict);
if (conflictsQueue.size() == 1) { if (conflictsQueue.size() == 1) {
@@ -351,10 +352,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
* Handle conflicts (exporting file already exists). * Handle conflicts (exporting file already exists).
*/ */
private class PendingConflict { private class PendingConflict {
private final Track.Id trackId; private final Track track;
public PendingConflict(Track.Id trackId) { public PendingConflict(Track track) {
this.trackId = trackId; this.track = track;
} }
/** /**
@@ -365,14 +366,13 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
*/ */
public boolean resolve() { public boolean resolve() {
if (autoConflict == CONFLICT_NONE) { if (autoConflict == CONFLICT_NONE) {
Track track = contentProviderUtils.getTrack(trackId);
viewBinding.exportProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(ExportActivity.this, R.drawable.ic_report_problem_24)); 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())); viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, track.getName()));
setConflictVisibility(View.VISIBLE); setConflictVisibility(View.VISIBLE);
return false; return false;
} }
export(trackId); export(track);
return true; 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. * Overwrite the export file and set the autoConflict if user set the "do it for all" switch button.
*/ */
public void overwrite() { public void overwrite() {
export(trackId, CONFLICT_OVERWRITE); export(track, CONFLICT_OVERWRITE);
if (viewBinding.exportProgressApplyToAll.isChecked()) { if (viewBinding.exportProgressApplyToAll.isChecked()) {
autoConflict = CONFLICT_OVERWRITE; 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. * Skip the export file and set the autoConflict if user set the "do it for all" switch button.
*/ */
public void skip() { public void skip() {
export(trackId, CONFLICT_SKIP); export(track, CONFLICT_SKIP);
if (viewBinding.exportProgressApplyToAll.isChecked()) { if (viewBinding.exportProgressApplyToAll.isChecked()) {
autoConflict = CONFLICT_SKIP; autoConflict = CONFLICT_SKIP;
@@ -49,8 +49,10 @@ public class GPXTrackExporter implements TrackExporter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat COORDINATE_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 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 HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat CADENCE_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 { static {
/* /*
@@ -72,6 +74,9 @@ public class GPXTrackExporter implements TrackExporter {
CADENCE_FORMAT.setMaximumFractionDigits(0); CADENCE_FORMAT.setMaximumFractionDigits(0);
CADENCE_FORMAT.setGroupingUsed(false); CADENCE_FORMAT.setGroupingUsed(false);
POWER_FORMAT.setMaximumFractionDigits(0);
POWER_FORMAT.setGroupingUsed(false);
} }
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
@@ -91,15 +96,16 @@ public class GPXTrackExporter implements TrackExporter {
@Override @Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) { public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
Track mainTrack = tracks[0];
try { try {
prepare(outputStream); prepare(outputStream);
writeHeader(mainTrack); writeHeader();
writeMarkers(mainTrack);
for (Track track : tracks) { for (Track track : tracks) {
writeLocations(track); writeMarkers(track);
}
for (Track track : tracks) {
writeTrackPoints(track);
} }
writeFooter(); writeFooter();
@@ -112,7 +118,7 @@ public class GPXTrackExporter implements TrackExporter {
} }
} }
private void writeLocations(Track track) throws InterruptedException { private void writeTrackPoints(Track track) throws InterruptedException {
boolean wroteTrack = false; boolean wroteTrack = false;
boolean wroteSegment = false; boolean wroteSegment = false;
@@ -183,7 +189,7 @@ public class GPXTrackExporter implements TrackExporter {
} }
public void writeHeader(Track track) { public void writeHeader() {
if (printWriter != null) { if (printWriter != null) {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
printWriter.println("<gpx"); printWriter.println("<gpx");
@@ -195,17 +201,13 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\""); printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\""); printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\"");
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\""); 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=" + 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/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.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/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\">"); + " 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>");
} }
} }
@@ -299,6 +301,10 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>"); 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()) { if (trackPoint.hasElevationGain()) {
printWriter.println("<opentracks:gain>" + ELEVATION_FORMAT.format(trackPoint.getElevationGain()) + "</opentracks:gain>"); printWriter.println("<opentracks:gain>" + ELEVATION_FORMAT.format(trackPoint.getElevationGain()) + "</opentracks:gain>");
} }
@@ -307,6 +313,10 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<opentracks:loss>" + ELEVATION_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:loss>"); 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>"); printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
} }
@@ -57,6 +57,7 @@ public class KMLTrackExporter implements TrackExporter {
private static final String SCHEMA_ID = "schema"; private static final String SCHEMA_ID = "schema";
public static final String EXTENDED_DATA_TYPE_SPEED = "speed"; 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_CADENCE = "cadence";
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate"; public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
public static final String EXTENDED_DATA_TYPE_POWER = "power"; public static final String EXTENDED_DATA_TYPE_POWER = "power";
@@ -74,6 +75,7 @@ public class KMLTrackExporter implements TrackExporter {
private PrintWriter printWriter; private PrintWriter printWriter;
private final List<Float> speedList = new ArrayList<>(); 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> powerList = new ArrayList<>();
private final List<Float> cadenceList = new ArrayList<>(); private final List<Float> cadenceList = new ArrayList<>();
private final List<Float> heartRateList = new ArrayList<>(); private final List<Float> heartRateList = new ArrayList<>();
@@ -338,6 +340,7 @@ public class KMLTrackExporter implements TrackExporter {
if (printWriter != null) { if (printWriter != null) {
printWriter.println("<gx:Track>"); printWriter.println("<gx:Track>");
speedList.clear(); speedList.clear();
distanceList.clear();
powerList.clear(); powerList.clear();
cadenceList.clear(); cadenceList.clear();
heartRateList.clear(); heartRateList.clear();
@@ -355,6 +358,9 @@ public class KMLTrackExporter implements TrackExporter {
writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED); writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED);
} }
if (exportSensorData) { if (exportSensorData) {
if (distanceList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(distanceList, EXTENDED_DATA_TYPE_DISTANCE);
}
if (powerList.stream().anyMatch(Objects::nonNull)) { if (powerList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER); writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER);
} }
@@ -392,6 +398,7 @@ public class KMLTrackExporter implements TrackExporter {
speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null); speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null);
if (exportSensorData) { if (exportSensorData) {
distanceList.add(trackPoint.hasSensorDistance() ? trackPoint.getSensorDistance() : null);
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null); heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null); cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null); powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.io.file.importer; package de.dennisguse.opentracks.io.file.importer;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
@@ -90,6 +91,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
protected String uuid; protected String uuid;
protected String gain; protected String gain;
protected String loss; protected String loss;
protected String distance;
// The current track data // The current track data
private TrackData trackData; private TrackData trackData;
@@ -97,10 +99,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The SAX locator to get the current line information // The SAX locator to get the current line information
private Locator locator; private Locator locator;
private final SharedPreferences sharedPreferences;
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) { AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
this.context = context; this.context = context;
this.contentProviderUtils = contentProviderUtils; this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context); sharedPreferences = PreferencesUtils.getSharedPreferences(context);
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
} }
@Override @Override
@@ -120,10 +125,11 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
} }
@Override @Override
public Track.Id importFile(InputStream inputStream) { @NonNull
public List<Track.Id> importFile(InputStream inputStream) {
try { try {
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, this); SAXParserFactory.newInstance().newSAXParser().parse(inputStream, this);
return trackIds.get(0); return trackIds;
} catch (IOException | SAXException | ParserConfigurationException | ParsingException e) { } catch (IOException | SAXException | ParserConfigurationException | ParsingException e) {
Log.e(TAG, "Unable to import file", e); Log.e(TAG, "Unable to import file", e);
if (trackIds.size() > 0) { if (trackIds.size() > 0) {
@@ -136,9 +142,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
} }
} }
/**
* On file end.
*/
protected void onFileEnd() { protected void onFileEnd() {
// Add markers to the last imported track // Add markers to the last imported track
int size = trackIds.size(); int size = trackIds.size();
@@ -261,7 +264,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
Track track = contentProviderUtils.getTrack(trackData.track.getUuid()); Track track = contentProviderUtils.getTrack(trackData.track.getUuid());
if (track != null) { if (track != null) {
if (PreferencesUtils.getPreventReimportTracks(context)) { if (PreferencesUtils.getPreventReimportTracks(sharedPreferences, context)) {
throw new ImportAlreadyExistsException(context.getString(R.string.import_prevent_reimport)); throw new ImportAlreadyExistsException(context.getString(R.string.import_prevent_reimport));
} }
@@ -371,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. * 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()) { 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); trackPoint.setSpeed(speed);
} }
} }
@@ -379,8 +382,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) { if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint)); trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context); long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
double distanceToLastTrackLocation = trackPoint.distanceTo(trackData.lastLocationInCurrentSegment); double distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
if (distanceToLastTrackLocation > maxRecordingDistance) { if (distanceToLastTrackLocation > maxRecordingDistance) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
} }
@@ -478,6 +481,14 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
} }
} }
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);
}
}
if (gain != null) { if (gain != null) {
try { try {
trackPoint.setElevationGain(Float.parseFloat(gain)); trackPoint.setElevationGain(Float.parseFloat(gain));
@@ -492,6 +503,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
throw new ParsingException(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);
}
}
return trackPoint; return trackPoint;
} }
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
@@ -18,10 +19,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6; private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
protected SharedPreferences sharedPreferences;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
DocumentFile directoryUri = configureDirectoryChooserIntent(intent); DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
if (directoryUri == null) { if (directoryUri == null) {
@@ -38,6 +43,12 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
onActivityResultCustom(requestCode, resultCode, resultData); onActivityResultCustom(requestCode, resultCode, resultData);
} }
@Override
protected void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) { protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) {
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) { if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
@@ -80,14 +91,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
super.configureDirectoryChooserIntent(intent); super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
return PreferencesUtils.getDefaultExportDirectoryUri(this); return PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this);
} }
@Override @Override
protected Intent createNextActivityIntent(Uri directoryUri) { protected Intent createNextActivityIntent(Uri directoryUri) {
Intent intent = IntentUtils.newIntent(this, ExportActivity.class); Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri); 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; return intent;
} }
} }
@@ -101,11 +112,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
case RESULT_OK: case RESULT_OK:
Uri directoryUri = resultData.getData(); Uri directoryUri = resultData.getData();
PreferencesUtils.setDefaultExportDirectoryUri(this, directoryUri); PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, directoryUri);
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), resultData, directoryUri); IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), resultData, directoryUri);
break; break;
case RESULT_CANCELED: case RESULT_CANCELED:
PreferencesUtils.setDefaultExportDirectoryUri(this, null); PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, null);
//TODO Remove stored permission contentResolver.releasePersistableUriPermission //TODO Remove stored permission contentResolver.releasePersistableUriPermission
break; break;
} }
@@ -118,8 +129,8 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
protected DocumentFile configureDirectoryChooserIntent(Intent intent) { protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
super.configureDirectoryChooserIntent(intent); super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (PreferencesUtils.isDefaultExportDirectoryUri(this)) { if (PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, this)) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri()); intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this).getUri());
} }
return null; return null;
} }
@@ -28,7 +28,9 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
/** /**
* Imports a GPX file. * 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> * <p>
* {@link de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter} does not export information if a segment was started automatic or manually. * {@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. * 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_SPEED = "gpxtpx:speed";
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr"; private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad"; 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_GAIN = "opentracks:gain";
private static final String TAG_EXTENSION_LOSS = "opentracks:loss"; private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
/** /**
* Constructor. * Constructor.
@@ -156,6 +160,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
cadence = content.trim(); cadence = content.trim();
} }
break; break;
case TAG_EXTENSION_POWER:
if (content != null) {
power = content.trim();
}
break;
case TAG_ID: case TAG_ID:
if (content != null) { if (content != null) {
uuid = content.trim(); uuid = content.trim();
@@ -171,6 +180,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
loss = content.trim(); loss = content.trim();
} }
break; break;
case TAG_EXTENSION_DISTANCE:
if (content != null) {
distance = content.trim();
}
break;
} }
// Reset element content // Reset element content
@@ -196,11 +210,12 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
altitude = null; altitude = null;
time = null; time = null;
speed = null; speed = null;
power = null;
gain = null; gain = null;
loss = null; loss = null;
} }
private void onTrackPointEnd() throws SAXException { private void onTrackPointEnd() {
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment(); boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
TrackPoint trackPoint = getTrackPoint(); TrackPoint trackPoint = getTrackPoint();
if (isFirstTrackPointInSegment) { if (isFirstTrackPointInSegment) {
@@ -221,7 +236,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
markerType = null; markerType = null;
} }
private void onMarkerEnd() throws SAXException { private void onMarkerEnd() {
addMarker(); addMarker();
} }
} }
@@ -16,6 +16,7 @@
package de.dennisguse.opentracks.io.file.importer; package de.dennisguse.opentracks.io.file.importer;
import android.content.ClipData;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -30,6 +31,10 @@ import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; 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.R;
import de.dennisguse.opentracks.TrackRecordedActivity; import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.databinding.ImportActivityBinding; 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"; 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_URIS = "document_uris";
private static final String BUNDLE_DOCUMENT_URI = "document_uri";
private static final String BUNDLE_IS_DIRECTORY = "is_directory"; private static final String BUNDLE_IS_DIRECTORY = "is_directory";
private ImportActivityBinding viewBinding; private ImportActivityBinding viewBinding;
boolean doubleBackToCancel = false; boolean doubleBackToCancel = false;
private Uri documentUri; private ArrayList<Uri> documentUris = new ArrayList<>();
private boolean isDirectory; private boolean isDirectory;
private String toolbarTitle;
private ImportViewModel viewModel; private ImportViewModel viewModel;
private ImportViewModel.Summary summary; private ImportViewModel.Summary summary;
@@ -70,35 +73,46 @@ public class ImportActivity extends FragmentActivity {
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
final DocumentFile documentFile; final Intent intent = getIntent();
final ClipData intentClipData = intent.getClipData();
if (savedInstanceState == null) { if (savedInstanceState == null) {
if (getIntent().getData() != null) { if (intent.getData() != null) {
documentUri = getIntent().getData(); documentUris.add(intent.getData());
isDirectory = false; isDirectory = false;
} else if (getIntent().getClipData() != null && getIntent().getClipData().getItemCount() > 0) { } else {
documentUri = getIntent().getClipData().getItemAt(0).getUri(); if (intentClipData != null && intentClipData.getItemCount() > 0) {
for (int i = 0; i < intentClipData.getItemCount(); i++) {
documentUris.add(intentClipData.getItemAt(i).getUri());
}
isDirectory = false; isDirectory = false;
} else { } else {
// Started from DirectoryChooserActivity // Started from DirectoryChooserActivity
documentUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY); documentUris.add(intent.getParcelableExtra(EXTRA_DIRECTORY_URI_KEY));
isDirectory = true; 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);
isDirectory = savedInstanceState.getBoolean(BUNDLE_IS_DIRECTORY);
documentFile = isDirectory ? DocumentFile.fromTreeUri(this, documentUri) : DocumentFile.fromSingleUri(this, documentUri);
} }
toolbar.setTitle(toolbarTitle); } else {
documentUris = savedInstanceState.getParcelable(BUNDLE_DOCUMENT_URIS);
isDirectory = savedInstanceState.getBoolean(BUNDLE_IS_DIRECTORY);
}
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(); initViews();
viewModel = new ViewModelProvider(this).get(ImportViewModel.class); viewModel = new ViewModelProvider(this).get(ImportViewModel.class);
viewModel.getImportData(documentFile).observe(this, data -> { viewModel.getImportData(documentFiles).observe(this, data -> {
summary = data; summary = data;
setProgress(); setProgress();
}); });
@@ -107,9 +121,8 @@ public class ImportActivity extends FragmentActivity {
@Override @Override
protected void onSaveInstanceState(@NonNull Bundle outState) { protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_DOCUMENT_URI, documentUri); outState.putParcelableArrayList(BUNDLE_DOCUMENT_URIS, documentUris);
outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory); outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory);
outState.putString(BUNDLE_TOOLBAR_TITLE, toolbarTitle);
} }
@Override @Override
@@ -14,6 +14,7 @@ import androidx.documentfile.provider.DocumentFile;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
@@ -62,11 +63,12 @@ public class ImportService extends JobIntentService {
} }
try (InputStream inputStream = getContentResolver().openInputStream(file.getUri())) { try (InputStream inputStream = getContentResolver().openInputStream(file.getUri())) {
Track.Id trackId = trackImporter.importFile(inputStream); ArrayList<Track.Id> trackIds = new ArrayList<>(trackImporter.importFile(inputStream));
if (trackId != null) {
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackId, file, getString(R.string.import_file_imported, file.getName())); if (!trackIds.isEmpty()) {
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName()));
} else { } else {
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackId, file, 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) { } catch (IOException e) {
Log.d(TAG, "Unable to import file", e); Log.d(TAG, "Unable to import file", e);
@@ -80,9 +82,9 @@ public class ImportService extends JobIntentService {
} }
} }
private void sendResult(int resultCode, Track.Id trackId, DocumentFile file, String message) { private void sendResult(int resultCode, ArrayList<Track.Id> trackId, DocumentFile file, String message) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(ImportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId); bundle.putParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID, trackId);
bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME, file.getName()); bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME, file.getName());
bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE, message); bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE, message);
resultReceiver.send(resultCode, bundle); resultReceiver.send(resultCode, bundle);
@@ -16,7 +16,7 @@ public class ImportServiceResultReceiver extends ResultReceiver {
public static final int RESULT_CODE_IMPORTED = 1; public static final int RESULT_CODE_IMPORTED = 1;
public static final int RESULT_CODE_ALREADY_EXISTS = 2; 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_FILENAME = "result_extra_filename";
public static final String RESULT_EXTRA_MESSAGE = "result_extra_message"; public static final String RESULT_EXTRA_MESSAGE = "result_extra_message";
@@ -11,7 +11,9 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
@@ -33,20 +35,20 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
summary = new Summary(); summary = new Summary();
} }
public LiveData<Summary> getImportData(DocumentFile documentFile) { LiveData<Summary> getImportData(List<DocumentFile> documentFiles) {
if (importData == null) { if (importData == null) {
importData = new MutableLiveData<>(); importData = new MutableLiveData<>();
loadData(documentFile); loadData(documentFiles);
} }
return importData; return importData;
} }
public void cancel() { void cancel() {
cancel = true; cancel = true;
} }
private void loadData(DocumentFile documentFile) { private void loadData(List<DocumentFile> documentFiles) {
List<DocumentFile> fileList = FileUtils.getFiles(documentFile); List<DocumentFile> fileList = documentFiles.stream().map(FileUtils::getFiles).flatMap(Collection::stream).collect(Collectors.toList());
summary.totalCount = fileList.size(); summary.totalCount = fileList.size();
filesToImport.addAll(fileList); filesToImport.addAll(fileList);
importNextFile(); importNextFile();
@@ -66,7 +68,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
throw new RuntimeException(TAG + ": onReceiveResult resultData NULL"); 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 fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME);
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE); 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)); summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
break; break;
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED: case ImportServiceResultReceiver.RESULT_CODE_IMPORTED:
summary.importedTrackIds.add(trackId); summary.importedTrackIds.addAll(trackIds);
summary.successCount++; summary.successCount++;
break; break;
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS: case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
@@ -90,7 +92,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
importNextFile(); importNextFile();
} }
public static class Summary { static class Summary {
private int totalCount; private int totalCount;
private int successCount; private int successCount;
private int existsCount; private int existsCount;
@@ -35,6 +35,7 @@ import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
//TODO Do not use AbstractFileTrackImporter as TrackPoint sensor generation is done differently here.
public class KmlFileTrackImporter extends AbstractFileTrackImporter { public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG = KmlFileTrackImporter.class.getSimpleName(); private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
@@ -65,6 +66,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private String extendedDataType; private String extendedDataType;
private final ArrayList<TrackPoint> trackPoints = new ArrayList<>(); private final ArrayList<TrackPoint> trackPoints = new ArrayList<>();
private final ArrayList<Float> speedList = 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> cadenceList = new ArrayList<>();
private final ArrayList<Float> heartRateList = new ArrayList<>(); private final ArrayList<Float> heartRateList = new ArrayList<>();
private final ArrayList<Float> powerList = new ArrayList<>(); private final ArrayList<Float> powerList = new ArrayList<>();
@@ -215,6 +217,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
super.onTrackSegmentStart(); super.onTrackSegmentStart();
trackPoints.clear(); trackPoints.clear();
speedList.clear(); speedList.clear();
distanceList.clear();
heartRateList.clear(); heartRateList.clear();
cadenceList.clear(); cadenceList.clear();
powerList.clear(); powerList.clear();
@@ -231,6 +234,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (i < speedList.size()) { if (i < speedList.size()) {
trackPoint.setSpeed(speedList.get(i)); trackPoint.setSpeed(speedList.get(i));
} }
if (i < distanceList.size()) {
trackPoint.setSensorDistance(distanceList.get(i));
}
if (i < heartRateList.size()) { if (i < heartRateList.size()) {
trackPoint.setHeartRate_bpm(heartRateList.get(i)); trackPoint.setHeartRate_bpm(heartRateList.get(i));
} }
@@ -309,6 +315,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED: case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED:
speedList.add(value); speedList.add(value);
break; break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE:
distanceList.add(value);
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER: case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER:
powerList.add(value); powerList.add(value);
break; break;
@@ -20,6 +20,8 @@ import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@@ -66,17 +68,23 @@ public class KmzTrackImporter implements TrackImporter {
} }
@Override @Override
public Track.Id importFile(InputStream inputStream) { public @NonNull
Track.Id trackId = findAndParseKmlFile(inputStream); List<Track.Id> importFile(InputStream inputStream) {
List<Track.Id> trackIds = findAndParseKmlFile(inputStream);
if (!copyKmzImages(trackId)) { ArrayList<Track.Id> trackIdsWithImages = new ArrayList<>();
for (Track.Id trackId : trackIds) {
if (copyKmzImages(trackId)) {
trackIdsWithImages.add(trackId);
deleteOrphanImages(context, trackId);
} else {
cleanImport(context, trackId); cleanImport(context, trackId);
return null; return new ArrayList<>();
}
} }
deleteOrphanImages(context, trackId); return trackIdsWithImages;
return trackId;
} }
/** /**
@@ -150,14 +158,13 @@ public class KmzTrackImporter implements TrackImporter {
/** /**
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it. * 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. * @param inputStream kmz input stream.
*/ */
private Track.Id findAndParseKmlFile(InputStream inputStream) { private List<Track.Id> findAndParseKmlFile(InputStream inputStream) {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry; ZipEntry zipEntry;
Track.Id trackId = null; ArrayList<Track.Id> trackIds = new ArrayList<>();
while ((zipEntry = zipInputStream.getNextEntry()) != null) { while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
@@ -167,20 +174,21 @@ public class KmzTrackImporter implements TrackImporter {
String fileName = zipEntry.getName(); String fileName = zipEntry.getName();
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) { if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
trackId = parseKml(zipInputStream); List<Track.Id> trackId = parseKml(zipInputStream);
if (trackId == null) { if (trackId.isEmpty()) {
Log.d(TAG, "Unable to parse kml in kmz"); Log.d(TAG, "Unable to parse kml in kmz");
throw new ImportParserException(context.getString(R.string.import_unable_to_import_file, fileName)); throw new ImportParserException(context.getString(R.string.import_unable_to_import_file, fileName));
} }
trackIds.addAll(trackId);
} }
zipInputStream.closeEntry(); zipInputStream.closeEntry();
} }
if (trackId == null) { if (trackIds.isEmpty()) {
Log.d(TAG, "Unable to find doc.kml in kmz"); Log.d(TAG, "Unable to find doc.kml in kmz");
throw new ImportParserException(context.getString(R.string.import_no_kml_file_found)); throw new ImportParserException(context.getString(R.string.import_no_kml_file_found));
} }
return trackId; return trackIds;
} catch (ImportParserException | ImportAlreadyExistsException e) { } catch (ImportParserException | ImportAlreadyExistsException e) {
Log.e(TAG, "Unable to import file", e); Log.e(TAG, "Unable to import file", e);
throw e; throw e;
@@ -234,13 +242,7 @@ public class KmzTrackImporter implements TrackImporter {
} }
} }
/** private List<Track.Id> parseKml(ZipInputStream zipInputStream) {
* Parses kml
*
* @param zipInputStream the zip input stream
* @return the imported track id or -1L
*/
private Track.Id parseKml(ZipInputStream zipInputStream) {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context); KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) { try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
@@ -15,7 +15,10 @@
*/ */
package de.dennisguse.opentracks.io.file.importer; package de.dennisguse.opentracks.io.file.importer;
import androidx.annotation.NonNull;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
@@ -30,8 +33,7 @@ public interface TrackImporter {
* Import a file. * Import a file.
* *
* @param inputStream the file's input stream * @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). @NonNull
Track.Id importFile(InputStream inputStream); List<Track.Id> importFile(InputStream inputStream) throws ImportParserException;
} }
@@ -82,39 +82,40 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private PeriodicTaskExecutor voiceExecutor; private PeriodicTaskExecutor voiceExecutor;
private TrackRecordingServiceNotificationManager notificationManager; private TrackRecordingServiceNotificationManager notificationManager;
private SharedPreferences sharedPreferences;
private Track.Id recordingTrackId; private Track.Id recordingTrackId;
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
private int recordingDistanceInterval; private int recordingDistanceInterval;
private int maxRecordingDistance; private int maxRecordingDistance;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Context context = TrackRecordingService.this; Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) { 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. // Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
if (PreferencesUtils.isRecording(TrackRecordingService.this)) { if (PreferencesUtils.isRecording(sharedPreferences, TrackRecordingService.this)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingService.this); recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackRecordingService.this);
} }
} }
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) { 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)) { if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context); boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
voiceExecutor.setMetricUnits(metricUnits); voiceExecutor.setMetricUnits(metricUnits);
notificationManager.setMetricUnits(metricUnits);
} }
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) { 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)) { 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)) { 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);
} }
}; };
@@ -147,8 +148,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// onSharedPreferenceChanged might not set recordingTrackId. // onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = null; recordingTrackId = null;
PreferencesUtils.register(this, sharedPreferenceChangeListener); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
restartTrackAfterServiceRestart(); restartTrackAfterServiceRestart();
} }
@@ -189,8 +191,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Reverse order from onCreate // Reverse order from onCreate
showNotification(false); //TODO Why? showNotification(false); //TODO Why?
PreferencesUtils.unregister(this, sharedPreferenceChangeListener); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferences = null;
try { try {
voiceExecutor.shutdown(); voiceExecutor.shutdown();
@@ -305,7 +307,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
//TODO Pass TrackPoint //TODO Pass TrackPoint
track.setName(TrackNameUtils.getTrackName(this, trackId, segmentStartTrackPoint.getTime())); track.setName(TrackNameUtils.getTrackName(this, trackId, segmentStartTrackPoint.getTime()));
String category = PreferencesUtils.getDefaultActivity(this); String category = PreferencesUtils.getDefaultActivity(sharedPreferences, this);
track.setCategory(category); track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(this, category)); track.setIcon(TrackIconUtils.getIconValue(this, category));
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics()); track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
@@ -536,9 +538,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private void updateRecordingState(Track.Id trackId, boolean paused) { private void updateRecordingState(Track.Id trackId, boolean paused) {
recordingTrackId = trackId; recordingTrackId = trackId;
long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; 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; recordingTrackPaused = paused;
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused); PreferencesUtils.setBoolean(sharedPreferences, this, R.string.recording_track_paused_key, recordingTrackPaused);
} }
@Override @Override
@@ -556,7 +558,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
fillWithSensorDataSet(trackPoint); 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 //TODO Figure out how to avoid loading the lastValidTrackPoint from the database
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId()); TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
@@ -570,7 +572,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return; return;
} }
double distanceToLastTrackLocation = trackPoint.distanceTo(lastValidTrackPoint); double distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
if (distanceToLastTrackLocation > maxRecordingDistance) { if (distanceToLastTrackLocation > maxRecordingDistance) {
insertTrackPointIfNewer(track, lastTrackPoint); insertTrackPointIfNewer(track, lastTrackPoint);
@@ -663,6 +665,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
trackPoint.setElevationLoss(elevationSumManager.getElevationLoss_m()); trackPoint.setElevationLoss(elevationSumManager.getElevationLoss_m());
elevationSumManager.reset(); elevationSumManager.reset();
} }
if (remoteSensorManager != null) {
fillWithSensorDataSet(trackPoint);
remoteSensorManager.reset();
}
contentProviderUtils.insertTrackPoint(trackPoint, track.getId()); contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval); trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.IBinder; import android.os.IBinder;
import android.os.IBinder.DeathRecipient; import android.os.IBinder.DeathRecipient;
import android.os.RemoteException; import android.os.RemoteException;
@@ -209,9 +210,10 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
try { try {
if (showEditor) { if (showEditor) {
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L. // 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(); trackRecordingService.endCurrentTrack();
if (PreferencesUtils.isRecording(context)) { if (PreferencesUtils.isRecording(sharedPreferences, context)) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class) Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId) .putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId)
.putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true); .putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true);
@@ -12,7 +12,7 @@ import androidx.core.app.NotificationCompat;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TrackPoint; 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; import de.dennisguse.opentracks.util.StringUtils;
/** /**
@@ -30,6 +30,8 @@ class TrackRecordingServiceNotificationManager {
private boolean previousLocationWasAccurate = true; private boolean previousLocationWasAccurate = true;
private Boolean metricUnits = null;
TrackRecordingServiceNotificationManager(Context context) { TrackRecordingServiceNotificationManager(Context context) {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -59,14 +61,14 @@ class TrackRecordingServiceNotificationManager {
} }
void updateContent(String content) { void updateContent(String content) {
notificationBuilder.setContentText(content); notificationBuilder.setSubText(content);
updateNotification(); 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); String formattedAccuracy = context.getString(R.string.value_none);
if (trackPoint.hasAccuracy()) { 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 currentLocationWasAccurate = trackPoint.getAccuracy() < recordingGpsAccuracy;
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate; boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
@@ -74,7 +76,9 @@ class TrackRecordingServiceNotificationManager {
previousLocationWasAccurate = currentLocationWasAccurate; 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(); updateNotification();
notificationBuilder.setOnlyAlertOnce(true); notificationBuilder.setOnlyAlertOnce(true);
@@ -93,6 +97,10 @@ class TrackRecordingServiceNotificationManager {
return notificationBuilder.build(); return notificationBuilder.build();
} }
void setMetricUnits(boolean metricUnits) {
this.metricUnits = metricUnits;
}
private void updateNotification() { private void updateNotification() {
notificationManager.notify(NOTIFICATION_ID, getNotification()); notificationManager.notify(NOTIFICATION_ID, getNotification());
} }
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.services.handlers; package de.dennisguse.opentracks.services.handlers;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Handler; 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. * This class handle GPS status according to received locations and some thresholds.
*/ */
//TODO should handle sharedpreference changes
class GpsStatus { class GpsStatus {
private static final String TAG = GpsStatus.class.getSimpleName(); private static final String TAG = GpsStatus.class.getSimpleName();
@@ -60,16 +62,16 @@ class GpsStatus {
private final Handler gpsStatusHandler; private final Handler gpsStatusHandler;
private GpsStatusRunner gpsStatusRunner = null; private GpsStatusRunner gpsStatusRunner = null;
/** public GpsStatus(Context context, GpsStatusListener client) {
* @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) {
this.client = client; this.client = client;
this.context = context; 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; signalLostThreshold = !minRecordingInterval.isNegative() ? SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval) : SIGNAL_LOST_THRESHOLD;
gpsStatusHandler = new Handler(); gpsStatusHandler = new Handler();
} }
@@ -3,12 +3,14 @@ package de.dennisguse.opentracks.services.handlers;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
public class HandlerServer { public class HandlerServer {
@@ -32,8 +34,9 @@ public class HandlerServer {
public void start(Context context) { public void start(Context context) {
serviceExecutor = Executors.newSingleThreadExecutor(); serviceExecutor = Executors.newSingleThreadExecutor();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
locationHandler.onStart(context); locationHandler.onStart(context);
locationHandler.onSharedPreferenceChanged(context, null, null); locationHandler.onSharedPreferenceChanged(context, sharedPreferences, null);
} }
public void stop(Context context) { public void stop(Context context) {
@@ -45,7 +48,7 @@ public class HandlerServer {
serviceExecutor = null; 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); locationHandler.onSharedPreferenceChanged(context, preferences, key);
} }
@@ -70,8 +73,10 @@ public class HandlerServer {
} }
public interface Handler { public interface Handler {
void onStart(Context context); void onStart(@NonNull Context context);
void onStop(Context context);
void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key); void onStop(@NonNull Context context);
void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences preferences, String key);
} }
} }
@@ -34,14 +34,14 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
} }
@Override @Override
public void onStart(Context context) { public void onStart(@NonNull Context context) {
gpsStatus = new GpsStatus(context, this, Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(context))); gpsStatus = new GpsStatus(context, this);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
registerLocationListener(); registerLocationListener();
} }
@Override @Override
public void onStop(Context context) { public void onStop(@NonNull Context context) {
unregisterLocationListener(); unregisterLocationListener();
locationManager = null; locationManager = null;
if (gpsStatus != null) { if (gpsStatus != null) {
@@ -51,9 +51,9 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
} }
@Override @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)) { 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)) { if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
// Choose battery life over moving time accuracy. // Choose battery life over moving time accuracy.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(Duration.ofSeconds(30), Duration.ofSeconds(5), 5); locationListenerPolicy = new AdaptiveLocationListenerPolicy(Duration.ofSeconds(30), Duration.ofSeconds(5), 5);
@@ -69,16 +69,16 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
} }
} }
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) { 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 (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
if (gpsStatus != null) { 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 (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
if (gpsStatus != null) { if (gpsStatus != null) {
gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(context)); gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context));
} }
} }
} }
@@ -101,11 +101,11 @@ public abstract class BluetoothConnectionManager {
@Override @Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) { public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
String sensorName = gatt.getDevice().getName(); Log.d(TAG, "Received data from " + gatt.getDevice().getAddress());
Log.d(TAG, "Received data from " + sensorName);
SensorData sensorData = parsePayload(sensorName, gatt.getDevice().getAddress(), characteristic); SensorData<?> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
if (sensorData != null) { if (sensorData != null) {
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
observer.onChanged(sensorData); observer.onChanged(sensorData);
} }
} }
@@ -125,7 +125,7 @@ public abstract class BluetoothConnectionManager {
Log.d(TAG, "Connecting to: " + device); Log.d(TAG, "Connecting to: " + device);
bluetoothGatt = device.connectGatt(context, true, connectCallback); bluetoothGatt = device.connectGatt(context, true, connectCallback);
SensorData sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress()); SensorData<?> sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
observer.onChanged(sensorData); observer.onChanged(sensorData);
} }
@@ -148,12 +148,12 @@ public abstract class BluetoothConnectionManager {
return address.equals(bluetoothGatt.getDevice().getAddress()); 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. * @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 { public static class HeartRate extends BluetoothConnectionManager {
@@ -162,7 +162,7 @@ public abstract class BluetoothConnectionManager {
} }
@Override @Override
protected SensorData createEmptySensorData(String address) { protected SensorDataHeartRate createEmptySensorData(String address) {
return new SensorDataHeartRate(address); return new SensorDataHeartRate(address);
} }
@@ -181,7 +181,7 @@ public abstract class BluetoothConnectionManager {
} }
@Override @Override
protected SensorData createEmptySensorData(String address) { protected SensorDataCycling.Cadence createEmptySensorData(String address) {
return new SensorDataCycling.Cadence(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) //Workaround for Wahoo CADENCE: this sensor reports speed (instead of cadence)
if (cadenceAndSpeed.getSpeed() != null) { if (cadenceAndSpeed.getDistanceSpeed() != null) {
return new SensorDataCycling.Cadence(cadenceAndSpeed.getSpeed()); return new SensorDataCycling.Cadence(cadenceAndSpeed.getDistanceSpeed());
} }
return null; 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); super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
} }
@Override @Override
protected SensorData createEmptySensorData(String address) { protected SensorDataCycling.DistanceSpeed createEmptySensorData(String address) {
return new SensorDataCycling.Speed(address); return new SensorDataCycling.DistanceSpeed(address);
} }
@Override @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); SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed != null) { 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; return null;
} }
@@ -233,7 +239,7 @@ public abstract class BluetoothConnectionManager {
} }
@Override @Override
protected SensorData createEmptySensorData(String address) { protected SensorDataCyclingPower createEmptySensorData(String address) {
return new SensorDataCyclingPower(address); return new SensorDataCyclingPower(address);
} }
@@ -247,8 +253,8 @@ public abstract class BluetoothConnectionManager {
interface SensorDataObserver { interface SensorDataObserver {
void onChanged(SensorData sensorData); void onChanged(SensorData<?> sensorData);
void onDisconnecting(SensorData sensorData); void onDisconnecting(SensorData<?> sensorData);
} }
} }
@@ -53,43 +53,50 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
private final BluetoothAdapter bluetoothAdapter; private final BluetoothAdapter bluetoothAdapter;
private final Context context; private final Context context;
private final SharedPreferences sharedPreferences;
private boolean started = false; private boolean started = false;
private final SharedPreferences sharedPreferences;
private int preferenceWheelCircumference;
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this); private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(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 BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(this);
private final SensorDataSet sensorDataSet = new SensorDataSet(); private final SensorDataSet sensorDataSet = new SensorDataSet();
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (!started) return; if (!started) return;
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) { 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); connect(heartRate, address);
} }
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) { 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); connect(cyclingCadence, address);
} }
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) { 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); connect(cyclingSpeed, address);
} }
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) { 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); 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); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
//Trigger connection startup //Trigger connection startup
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
} }
public synchronized void stop() { public synchronized void stop() {
@@ -155,11 +162,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return sensorDataSet; return sensorDataSet;
} }
public void reset() {
sensorDataSet.reset();
}
@Override @Override
public synchronized void onChanged(SensorData sensorData) { public synchronized void onChanged(SensorData<?> sensorData) {
if (sensorData instanceof SensorDataCycling.Cadence) { if (sensorData instanceof SensorDataCycling.Cadence) {
SensorDataCycling.Cadence previous = sensorDataSet.getCyclingCadence(); SensorDataCycling.Cadence previous = sensorDataSet.getCyclingCadence();
Log.d(TAG, "previous " + previous + "; current" + sensorData); Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
if (sensorData.equals(previous)) { if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: cadence data repeated."); Log.d(TAG, "onChanged: cadence data repeated.");
@@ -167,21 +178,21 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
} }
((SensorDataCycling.Cadence) sensorData).compute(previous); ((SensorDataCycling.Cadence) sensorData).compute(previous);
} }
if (sensorData instanceof SensorDataCycling.Speed) { if (sensorData instanceof SensorDataCycling.DistanceSpeed) {
SensorDataCycling.Speed previous = sensorDataSet.getCyclingSpeed(); SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
Log.d(TAG, "previous " + previous + "; current" + sensorData); Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData.equals(previous)) { if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: speed data repeated."); Log.d(TAG, "onChanged: speed data repeated.");
return; return;
} }
((SensorDataCycling.Speed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(context)); ((SensorDataCycling.DistanceSpeed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(sharedPreferences, context));
} }
sensorDataSet.set(sensorData); sensorDataSet.set(sensorData);
} }
@Override @Override
public void onDisconnecting(SensorData sensorData) { public void onDisconnecting(SensorData<?> sensorData) {
sensorDataSet.remove(sensorData); sensorDataSet.remove(sensorData);
} }
} }
@@ -17,17 +17,19 @@
package de.dennisguse.opentracks.services.tasks; package de.dennisguse.opentracks.services.tasks;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioManager; import android.media.AudioManager;
import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener; import android.speech.tts.UtteranceProgressListener;
import android.util.Log; import android.util.Log;
import java.util.List; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.AnnouncementUtils; import de.dennisguse.opentracks.util.AnnouncementUtils;
@@ -102,6 +104,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
} }
}; };
private SharedPreferences sharedPreferences;
private TextToSpeech tts; private TextToSpeech tts;
// Response from TTS after its initialization // Response from TTS after its initialization
private int ttsInitStatus = TextToSpeech.ERROR; private int ttsInitStatus = TextToSpeech.ERROR;
@@ -112,12 +116,15 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
this.context = context; this.context = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
contentProviderUtils = new ContentProviderUtils(context); contentProviderUtils = new ContentProviderUtils(context);
} }
@Override @Override
public void start() { public void start() {
Log.d(TAG, "Start"); Log.d(TAG, "Start");
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
if (tts == null) { if (tts == null) {
tts = new TextToSpeech(context, status -> { tts = new TextToSpeech(context, status -> {
Log.i(TAG, "TextToSpeech initialized with status " + status); Log.i(TAG, "TextToSpeech initialized with status " + status);
@@ -164,14 +171,23 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
return; return;
} }
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context)); Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
String category = track != null ? track.getCategory() : ""; String category = track != null ? track.getCategory() : "";
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(track.getId()); //TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else.
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, (float) (PreferencesUtils.isMetricUnits(context) ? 1d * UnitConversions.KM_TO_M : 1d * UnitConversions.MI_TO_M)); 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(); IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, category, lastInterval); String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, isMetricUnits, isReportSpeed, lastInterval);
speakAnnouncement(announcement); speakAnnouncement(announcement);
} }
@@ -181,6 +197,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
tts.shutdown(); tts.shutdown();
tts = null; tts = null;
} }
sharedPreferences = null;
} }
private void onTtsReady() { private void onTtsReady() {
@@ -94,7 +94,7 @@ public class PeriodicTaskExecutor {
if (timerTaskExecutor == null) { if (timerTaskExecutor == null) {
timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService); timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService);
} }
timerTaskExecutor.scheduleTask(Duration.ofSeconds(taskFrequency)); timerTaskExecutor.scheduleTask(Duration.ofMinutes(taskFrequency));
} else { } else {
// For distance periodic task // For distance periodic task
calculateNextTaskDistance(); calculateNextTaskDistance();
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.settings; package de.dennisguse.opentracks.settings;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
@@ -48,7 +49,7 @@ public class ActivityTypePreference extends DialogPreference {
setDialogIcon(null); setDialogIcon(null);
setPersistent(true); 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); setSummaryProvider(summaryProvider);
} }
@@ -60,6 +61,8 @@ public class ActivityTypePreference extends DialogPreference {
public static class ActivityPreferenceDialog extends PreferenceDialogFragmentCompat { public static class ActivityPreferenceDialog extends PreferenceDialogFragmentCompat {
private SharedPreferences sharedPreferences;
private AutoCompleteTextView textView; private AutoCompleteTextView textView;
private ImageView iconView; private ImageView iconView;
@@ -77,9 +80,11 @@ public class ActivityTypePreference extends DialogPreference {
super.onBindDialogView(view); super.onBindDialogView(view);
final Context context = getActivity(); final Context context = getActivity();
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
textView = view.findViewById(R.id.activity_type_preference_text_view); textView = view.findViewById(R.id.activity_type_preference_text_view);
String category = PreferencesUtils.getDefaultActivity(context); String category = PreferencesUtils.getDefaultActivity(sharedPreferences, context);
textView.setText(category); textView.setText(category);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
textView.setAdapter(adapter); textView.setAdapter(adapter);
@@ -100,8 +105,14 @@ public class ActivityTypePreference extends DialogPreference {
updateIcon(TrackIconUtils.getIconValue(context, category)); updateIcon(TrackIconUtils.getIconValue(context, category));
} }
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
private void showIconSelectDialog() { private void showIconSelectDialog() {
String category = PreferencesUtils.getDefaultActivity(getActivity()); String category = PreferencesUtils.getDefaultActivity(sharedPreferences, getActivity());
ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category); ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
} }
@@ -110,7 +121,7 @@ public class ActivityTypePreference extends DialogPreference {
if (positiveResult) { if (positiveResult) {
String newDefaultActivity = textView.getText().toString(); String newDefaultActivity = textView.getText().toString();
if (getPreference().callChangeListener(newDefaultActivity)) { if (getPreference().callChangeListener(newDefaultActivity)) {
PreferencesUtils.setDefaultActivity(getActivity(), newDefaultActivity); PreferencesUtils.setDefaultActivity(sharedPreferences, getActivity(), newDefaultActivity);
HackUtils.invalidatePreference(getPreference()); HackUtils.invalidatePreference(getPreference());
} }
} }
@@ -91,7 +91,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
public static class PrefsFragment extends PreferenceFragmentCompat { 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)) { if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
getActivity().runOnUiThread(this::updateReset); getActivity().runOnUiThread(this::updateReset);
} }
@@ -99,7 +101,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
getActivity().runOnUiThread(this::updateUnits); getActivity().runOnUiThread(this::updateUnits);
} }
if (PreferencesUtils.isKey(getActivity(), R.string.night_mode_key, key)) { 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 @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
try { try {
setPreferencesFromResource(R.xml.settings, rootKey); setPreferencesFromResource(R.xml.settings, rootKey);
} catch (ClassCastException e) { } 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)); Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key));
instantExportDirectoryPreference.setSummaryProvider(preference -> { 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 //Use same value for not set as Androidx ListPreference and EditTextPreference
return directory != null ? directory.getName() : getString(R.string.not_set); return directory != null ? directory.getName() : getString(R.string.not_set);
}); });
@@ -132,7 +136,10 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
updateUnits(); updateUnits();
updateReset(); updateReset();
updateBluetooth(); updateBluetooth();
@@ -142,13 +149,13 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
private void updatePostWorkoutExport() { private void updatePostWorkoutExport() {
Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key)); Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key));
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext())); instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, getContext()));
} }
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
PreferencesUtils.getSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
} }
@Override @Override
@@ -176,6 +183,12 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
super.onDisplayPreferenceDialog(preference); super.onDisplayPreferenceDialog(preference);
} }
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
public void setDefaultActivity(String iconValue) { public void setDefaultActivity(String iconValue) {
if (activityPreferenceDialog != null) { if (activityPreferenceDialog != null) {
activityPreferenceDialog.updateUI(iconValue); activityPreferenceDialog.updateUI(iconValue);
@@ -183,7 +196,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
} }
private void updateReset() { 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)); Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
resetPreference.setEnabled(!isRecording); resetPreference.setEnabled(!isRecording);
@@ -196,7 +209,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
} }
private void updateUnits() { private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity()); boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key)); ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits)); voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
@@ -104,9 +104,14 @@ public class TrackStatisticsUpdater {
* Adds a trackPoint. * Adds a trackPoint.
* *
* @param trackPoint the trackPoint * @param trackPoint the trackPoint
* @param minRecordingDistance the min recording distance * @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) { if (!trackInitialized) {
trackStatistics.setStartTime(trackPoint.getTime()); trackStatistics.setStartTime(trackPoint.getTime());
trackInitialized = true; trackInitialized = true;
@@ -120,19 +125,26 @@ public class TrackStatisticsUpdater {
currentSegment.setStopTime(trackPoint.getTime()); currentSegment.setStopTime(trackPoint.getTime());
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime())); currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));
if (trackPoint.isSegmentStart() || trackPoint.isSegmentEnd()) { if (trackPoint.isSegmentStart()) {
if (trackPoint.isSegmentEnd()) { reset(trackPoint);
if (lastTrackPoint != null && lastMovingTrackPoint != null && lastTrackPoint != lastMovingTrackPoint) { return;
currentSegment.addTotalDistance(lastMovingTrackPoint.distanceTo(lastTrackPoint));
} }
}
trackStatistics.merge(currentSegment);
currentSegment.reset(trackPoint.getTime());
lastTrackPoint = null; // Process sensor data
lastMovingTrackPoint = null; if (trackPoint.hasElevationGain()) {
elevationBuffer_m.reset(); currentSegment.addTotalElevationGain(trackPoint.getElevationGain());
speedBuffer_ms.reset(); }
if (trackPoint.hasElevationLoss()) {
currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss());
}
if (trackPoint.hasSensorDistance()) {
currentSegment.addTotalDistance(trackPoint.getSensorDistance());
}
if (trackPoint.isSegmentEnd()) {
reset(trackPoint);
return; return;
} }
@@ -141,39 +153,30 @@ public class TrackStatisticsUpdater {
updateAbsoluteElevation(trackPoint.getAltitude()); 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) { if (lastTrackPoint == null || lastMovingTrackPoint == null) {
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
lastMovingTrackPoint = trackPoint; lastMovingTrackPoint = trackPoint;
return; return;
} }
double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint); if (!trackPoint.hasSensorDistance()) {
if (movingDistance < minRecordingDistance && !trackPoint.isMoving()) { // GPS-based distance/speed
float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
if (movingDistance < minGPSDistance && !trackPoint.isMoving()) {
speedBuffer_ms.reset(); speedBuffer_ms.reset();
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
return; return;
} }
// Update total distance
currentSegment.addTotalDistance(movingDistance);
}
Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
if (movingTime.isNegative()) { if (movingTime.isNegative()) {
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
return; return;
} }
// Update total distance
currentSegment.addTotalDistance(movingDistance);
// Update moving time // Update moving time
currentSegment.addMovingTime(movingTime); currentSegment.addMovingTime(movingTime);
@@ -186,6 +189,16 @@ public class TrackStatisticsUpdater {
lastMovingTrackPoint = trackPoint; 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) { public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
while (iterator.hasNext()) { while (iterator.hasNext()) {
TrackPoint location = iterator.next(); TrackPoint location = iterator.next();
@@ -224,20 +237,14 @@ public class TrackStatisticsUpdater {
/** /**
* Updates an elevation reading. * Updates an elevation reading.
*
* @param elevation the elevation
* @return the difference
*/ */
@VisibleForTesting private void updateAbsoluteElevation(double elevation) {
private double updateAbsoluteElevation(double elevation) {
// Update elevation using the smoothed average // Update elevation using the smoothed average
double oldAverage = elevationBuffer_m.getAverage(); double oldAverage = elevationBuffer_m.getAverage();
elevationBuffer_m.setNext(elevation); elevationBuffer_m.setNext(elevation);
double newAverage = elevationBuffer_m.getAverage(); double newAverage = elevationBuffer_m.getAverage();
currentSegment.updateElevationExtremities(newAverage); currentSegment.updateElevationExtremities(newAverage);
return newAverage - oldAverage;
} }
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) { private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
@@ -267,4 +274,11 @@ public class TrackStatisticsUpdater {
return true; 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.Activity;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.os.VibrationEffect; import android.os.VibrationEffect;
import android.os.Vibrator; import android.os.Vibrator;
@@ -150,7 +151,7 @@ public class ActivityUtils {
toast.show(); toast.show();
} }
public static void applyNightMode(Context context) { public static void applyNightMode(SharedPreferences sharedPreferences, Context context) {
AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode(context)); AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode(sharedPreferences, context));
} }
} }
@@ -15,9 +15,7 @@ public class AnnouncementUtils {
private AnnouncementUtils() { private AnnouncementUtils() {
} }
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, String category, @Nullable IntervalStatistics.Interval currentInterval) { public static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context, category);
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM; double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MPS_TO_KMH; double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MPS_TO_KMH;
double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0; 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); return context.getString(R.string.voice_total_distance_zero);
} }
if (!metricUnits) { if (!isMetricUnits) {
distance *= UnitConversions.KM_TO_MI; distance *= UnitConversions.KM_TO_MI;
distancePerTime *= UnitConversions.KM_TO_MI; distancePerTime *= UnitConversions.KM_TO_MI;
currentDistancePerTime *= UnitConversions.KM_TO_MI; currentDistancePerTime *= UnitConversions.KM_TO_MI;
@@ -35,15 +33,15 @@ public class AnnouncementUtils {
String rate; String rate;
String currentRate; String currentRate;
String currentRateMsg; String currentRateMsg;
if (reportSpeed) { if (isReportSpeed) {
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour; int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime); rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTime), currentDistancePerTime); currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTime), currentDistancePerTime);
currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate); currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate);
} else { } else {
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime; 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)); 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)); rate = context.getString(paceId, getAnnounceTime(context, time));
@@ -53,7 +51,7 @@ public class AnnouncementUtils {
currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate); 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); String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg; currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
@@ -65,8 +63,8 @@ public class AnnouncementUtils {
private static String getAnnounceTime(Context context, Duration duration) { private static String getAnnounceTime(Context context, Duration duration) {
String result = ""; String result = "";
int hours = (int) (duration.getSeconds() / (60 * 60)); int hours = (int) (duration.toHours());
int minutes = (int) (duration.getSeconds() / 60); int minutes = (int) (duration.toMinutes() % 60);
int seconds = (int) (duration.getSeconds() % 60); int seconds = (int) (duration.getSeconds() % 60);
if (hours != 0) { if (hours != 0) {
@@ -126,12 +126,12 @@ public class BluetoothUtils {
cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime); cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime);
} }
SensorDataCycling.Speed speed = null; SensorDataCycling.DistanceSpeed speed = null;
if (hasWheel && valueLength - index >= 4) { if (hasWheel && valueLength - index >= 4) {
int wheelCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); int wheelCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
index += 2; index += 2;
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s 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); return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed);
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.util;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
@@ -14,6 +15,8 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -26,9 +29,10 @@ public class ExportUtils {
private static final String TAG = ExportUtils.class.getSimpleName(); private static final String TAG = ExportUtils.class.getSimpleName();
public static void postWorkoutExport(Context context, Track track, ExportServiceResultReceiver resultReceiver) { public static void postWorkoutExport(Context context, Track track, ExportServiceResultReceiver resultReceiver) {
if (PreferencesUtils.shouldInstantExportAfterWorkout(context)) { SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context); if (PreferencesUtils.shouldInstantExportAfterWorkout(sharedPreferences, context)) {
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(context); TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, context);
ExportService.enqueue(context, resultReceiver, track.getId(), trackFileFormat, directory.getUri()); 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) { public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) {
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context); TrackExporter trackExporter = trackFileFormat.createTrackExporter(context);
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track.getId(), trackFileFormat, directory); Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory);
if (exportDocumentFileUri == null) { if (exportDocumentFileUri == null) {
Log.e(TAG, "Couldn't create document file for export"); Log.e(TAG, "Couldn't create document file for export");
return false; return false;
@@ -62,8 +66,10 @@ public class ExportUtils {
} }
} }
public static boolean isExportFileExists(Track.Id trackId, String trackFileFormatExtension, List<String> filesName) { public static boolean isExportFileExists(UUID uuuid, String trackFileFormatExtension, List<String> filesName) {
return filesName.contains(getExportFileNameByTrackId(trackId, trackFileFormatExtension)); 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) { public static List<String> getAllFiles(Context context, Uri directoryUri) {
@@ -82,8 +88,8 @@ public class ExportUtils {
return fileNames; return fileNames;
} }
private static Uri getExportDocumentFileUri(Context context, Track.Id trackId, TrackFileFormat trackFileFormat, DocumentFile directory) { private static Uri getExportDocumentFileUri(Context context, Track track, TrackFileFormat trackFileFormat, DocumentFile directory) {
String exportFileName = getExportFileNameByTrackId(trackId, trackFileFormat.getExtension()); String exportFileName = getExportFileNameForTrack(track, trackFileFormat.getExtension());
Uri exportDocumentFileUri = findFile(context, directory.getUri(), exportFileName); Uri exportDocumentFileUri = findFile(context, directory.getUri(), exportFileName);
if (exportDocumentFileUri == null) { if (exportDocumentFileUri == null) {
final DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), exportFileName); final DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), exportFileName);
@@ -94,8 +100,8 @@ public class ExportUtils {
return exportDocumentFileUri; return exportDocumentFileUri;
} }
private static String getExportFileNameByTrackId(Track.Id trackId, String trackFileFormatExtension) { private static String getExportFileNameForTrack(Track track, String trackFileFormatExtension) {
return trackId.getId() + "." + trackFileFormatExtension; return track.getUuid().toString().substring(0, 8) + "_" + track.getName() + "." + trackFileFormatExtension;
} }
private static Uri findFile(Context context, Uri directoryUri, String exportFileName) { 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.ClipData;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -73,11 +74,13 @@ public class IntentDashboardUtils {
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris); intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(context)); SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(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); intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
if (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); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -91,7 +91,7 @@ public class IntentUtils {
continue; 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); uris.add(uriAndMime.first);
mime = uriAndMime.second; mime = uriAndMime.second;
} }
@@ -42,37 +42,32 @@ public class PreferencesUtils {
private PreferencesUtils() { private PreferencesUtils() {
} }
@Deprecated //Should only be used to get a sharedPreference for more than one interaction!
public static SharedPreferences getSharedPreferences(Context context) { public static SharedPreferences getSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context); return PreferenceManager.getDefaultSharedPreferences(context);
} }
public static SharedPreferences register(Context context, SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
return sharedPreferences;
}
public static void unregister(Context context, SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) {
getSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Deprecated @Deprecated
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly. //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. //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; public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
@Deprecated //Use the TrackRecordingService @Deprecated //Use the TrackRecordingService
public static Track.Id getRecordingTrackId(Context context) { public static Track.Id getRecordingTrackId(Context context, SharedPreferences sharedPreferences) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
return new Track.Id(sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT)); return new Track.Id(sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT));
} }
public static String getDefaultActivity(Context context) { @Deprecated //Use the TrackRecordingService
return getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default)); public static Track.Id getRecordingTrackId(SharedPreferences sharedPreferences, Context context) {
return getRecordingTrackId(context, sharedPreferences);
} }
public static void setDefaultActivity(Context context, String newDefaultActivity) { public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
setString(context, R.string.default_activity_key, newDefaultActivity); 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);
} }
/** /**
@@ -96,48 +91,19 @@ public class PreferencesUtils {
return key == null || key.equals(getKey(context, keyId)); return key == null || key.equals(getKey(context, keyId));
} }
/** private static boolean getBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean defaultValue) {
* 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);
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue); return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
} }
private static boolean getBoolean(Context context, SharedPreferences sharedPreferences, 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. //TODO Don't use; this function is only to be used TrackRecordingService and will be removed.
@VisibleForTesting @VisibleForTesting
public static void setBoolean(Context context, int keyId, boolean value) { public static void setBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit(); Editor editor = sharedPreferences.edit();
editor.putBoolean(getKey(context, keyId), value); editor.putBoolean(getKey(context, keyId), value);
editor.apply(); editor.apply();
} }
/** private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
* 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);
try { try {
return sharedPreferences.getInt(getKey(context, keyId), defaultValue); return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
} catch (ClassCastException e) { } catch (ClassCastException e) {
@@ -153,82 +119,42 @@ public class PreferencesUtils {
} }
} }
/**
* Sets an integer preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
@VisibleForTesting @VisibleForTesting
public static void setInt(Context context, int keyId, int value) { public static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit(); Editor editor = sharedPreferences.edit();
editor.putInt(getKey(context, keyId), value); editor.putInt(getKey(context, keyId), value);
editor.apply(); 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. //TODO Don't use; this function is only to be used TrackRecordingService and will be removed.
@Deprecated @Deprecated
@VisibleForTesting @VisibleForTesting
public static void setLong(Context context, int keyId, long value) { public static void setLong(SharedPreferences sharedPreferences, Context context, int keyId, long value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit(); Editor editor = sharedPreferences.edit();
editor.putLong(getKey(context, keyId), value); editor.putLong(getKey(context, keyId), value);
editor.apply(); editor.apply();
} }
@Deprecated public static String getString(SharedPreferences sharedPreferences, Context context, int keyId, String defaultValue) {
public static String getString(Context context, int keyId, String defaultValue) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
return sharedPreferences.getString(getKey(context, keyId), defaultValue); return sharedPreferences.getString(getKey(context, keyId), defaultValue);
} }
public static String getString(Context context, SharedPreferences sharedPreferences, 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 @VisibleForTesting
public static void setString(Context context, int keyId, String value) { public static void setString(SharedPreferences sharedPreferences, Context context, int keyId, String value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit(); Editor editor = sharedPreferences.edit();
editor.putString(getKey(context, keyId), value); editor.putString(getKey(context, keyId), value);
editor.apply(); editor.apply();
} }
@Deprecated
public static boolean isMetricUnits(Context context) {
return isMetricUnits(getSharedPreferences(context), context);
}
public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) { public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) {
final String STATS_UNIT = context.getString(R.string.stats_units_default); final String STATS_UNIT = context.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(context, sharedPreferences, R.string.stats_units_key, STATS_UNIT)); return STATS_UNIT.equals(getString(sharedPreferences, context, R.string.stats_units_key, STATS_UNIT));
}
@Deprecated
public static boolean isReportSpeed(Context context, String category) {
return isReportSpeed(getSharedPreferences(context), context, category);
} }
public static boolean isReportSpeed(SharedPreferences sharedPreferences, 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); final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
String currentStatsRate = getString(context, sharedPreferences, R.string.stats_rate_key, STATS_RATE_DEFAULT); String currentStatsRate = getString(sharedPreferences, context, R.string.stats_rate_key, STATS_RATE_DEFAULT);
if (currentStatsRate.equals(getString(context, sharedPreferences, R.string.stats_rate_speed_or_pace_default, 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); return TrackIconUtils.isSpeedIcon(context, category);
} }
@@ -236,8 +162,8 @@ public class PreferencesUtils {
} }
@Deprecated //Use TrackRecordingService @Deprecated //Use TrackRecordingService
public static boolean isRecordingTrackPaused(Context context) { public static boolean isRecordingTrackPaused(SharedPreferences sharedPreferences, Context context) {
return getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context)); return getBoolean(sharedPreferences, context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
} }
@Deprecated //Use TrackRecordingService @Deprecated //Use TrackRecordingService
@@ -246,9 +172,9 @@ public class PreferencesUtils {
} }
@Deprecated //Use TrackRecordingService @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); 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) { private static String getBluetoothSensorAddressNone(Context context) {
@@ -259,83 +185,74 @@ public class PreferencesUtils {
return getBluetoothSensorAddressNone(context).equals(currentValue); return getBluetoothSensorAddressNone(context).equals(currentValue);
} }
public static String getBluetoothHeartRateSensorAddress(Context context) { public static String getBluetoothHeartRateSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone(context)); return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone(context));
} }
public static String getBluetoothCyclingCadenceSensorAddress(Context context) { public static String getBluetoothCyclingCadenceSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone(context)); return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone(context));
} }
public static String getBluetoothCyclingSpeedSensorAddress(Context context) { public static String getBluetoothCyclingSpeedSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(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)); 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) { public static String getBluetoothCyclingPowerSensorAddress(SharedPreferences sharedPreferences, Context context) {
return isBluetoothSensorAddressNone(context, getBluetoothCyclingPowerSensorAddress(context)); return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
} }
public static String getBluetoothCyclingPowerSensorAddress(Context context) { public static boolean shouldShowStatsOnLockscreen(SharedPreferences sharedPreferences, Context context) {
return getString(context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
}
public static boolean shouldShowStatsOnLockscreen(Context context) {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default); 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); 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); 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);
}
@Deprecated
public static boolean isShowStatsElevation(Context context) {
return isShowStatsElevation(getSharedPreferences(context), context);
} }
public static boolean isShowStatsElevation(SharedPreferences sharedPreferences, Context context) { public static boolean isShowStatsElevation(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_elevation_default); final boolean STATS_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_elevation_default);
return getBoolean(context, sharedPreferences, 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(SharedPreferences sharedPreferences, Context context) { public static boolean isStatsShowCoordinate(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_COORDINATE = context.getResources().getBoolean(R.bool.stats_show_coordinate_default); 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)); 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) { public static int getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) {
return getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context)); return getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
} }
public static int getRecordingDistanceIntervalDefault(Context context) { public static int getRecordingDistanceIntervalDefault(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default)); 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)); 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)); 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) { public static int getMinRecordingIntervalAdaptAccuracy(Context context) {
@@ -350,19 +267,19 @@ public class PreferencesUtils {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)); 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)); 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); 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) { public static TrackFileFormat getExportTrackFileFormat(SharedPreferences sharedPreferences, Context context) {
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(context, R.string.export_trackfileformat_default, null); final String TRACKFILEFORMAT_NAME_DEFAULT = getString(sharedPreferences, context, R.string.export_trackfileformat_default, null);
String trackFileFormatName = getString(context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT); String trackFileFormatName = getString(sharedPreferences, context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
try { try {
return TrackFileFormat.valueOf(trackFileFormatName); return TrackFileFormat.valueOf(trackFileFormatName);
} catch (Exception e) { } catch (Exception e) {
@@ -370,24 +287,24 @@ public class PreferencesUtils {
} }
} }
public static boolean getPreventReimportTracks(Context context) { public static boolean getPreventReimportTracks(SharedPreferences sharedPreferences, Context context) {
final boolean defaultValue = getBoolean(context, R.bool.import_prevent_reimport_default, false); final boolean defaultValue = getBoolean(sharedPreferences, context, R.bool.import_prevent_reimport_default, false);
return getBoolean(context, R.string.import_prevent_reimport_key, defaultValue); return getBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, defaultValue);
} }
/** /**
* @return {@link androidx.appcompat.app.AppCompatDelegate}.MODE_* * @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 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); return Integer.parseInt(value);
} }
@Deprecated //Use TrackRecordingService @Deprecated //Use TrackRecordingService
public static boolean isRecording(Context context) { public static boolean isRecording(SharedPreferences sharedPreferences, Context context) {
return isRecording(getRecordingTrackId(context)); return isRecording(getRecordingTrackId(context, sharedPreferences));
} }
@Deprecated @Deprecated
@@ -399,16 +316,19 @@ public class PreferencesUtils {
return recordingTrackId.getId() != RECORDING_TRACK_ID_DEFAULT; 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) { if (readAgain) {
// We want to really clear settings now. // We want to really clear settings now.
getSharedPreferences(context).edit().clear().commit(); sharedPreferences.edit().clear().commit();
} }
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain); PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
return sharedPreferences;
} }
public static DocumentFile getDefaultExportDirectoryUri(Context context) { public static DocumentFile getDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
String singleExportDirectorySettingsKey = getString(context, R.string.settings_default_export_directory_key, null); String singleExportDirectorySettingsKey = getString(sharedPreferences, context, R.string.settings_default_export_directory_key, null);
if (singleExportDirectorySettingsKey == null) { if (singleExportDirectorySettingsKey == null) {
return null; return null;
} }
@@ -420,12 +340,12 @@ public class PreferencesUtils {
return null; 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; 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) { public static boolean isDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
return getDefaultExportDirectoryUri(context) != null; return getDefaultExportDirectoryUri(sharedPreferences, context) != null;
} }
} }
@@ -41,8 +41,9 @@ public class TrackNameUtils {
private TrackNameUtils() { private TrackNameUtils() {
} }
//TODO Should not access sharedPreferences; trackName should be an ENUM.
public static String getTrackName(Context context, Track.Id trackId, Instant startTime) { 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))) { if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
return StringUtils.formatDateTime(context, startTime); return StringUtils.formatDateTime(context, startTime);
@@ -33,7 +33,7 @@ public class IntervalStatistics {
TrackPoint trackPoint = trackPoints.get(i); TrackPoint trackPoint = trackPoints.get(i);
if (trackPoint.hasLocation() && prevTrackPoint.hasLocation()) { if (trackPoint.hasLocation() && prevTrackPoint.hasLocation()) {
interval.distance_m += prevTrackPoint.distanceTo(trackPoint); interval.distance_m += prevTrackPoint.distanceToPrevious(trackPoint);
interval.time = interval.time.plus(Duration.between(prevTrackPoint.getTime(), trackPoint.getTime())); interval.time = interval.time.plus(Duration.between(prevTrackPoint.getTime(), trackPoint.getTime()));
interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0; interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0;
interval.loss_m += trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : 0; interval.loss_m += trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : 0;
@@ -19,9 +19,9 @@ public class SensorDataModel {
this.sensorValue = StringUtils.formatDecimal(sensorValue, 0); this.sensorValue = StringUtils.formatDecimal(sensorValue, 0);
} }
public SensorDataModel(SensorData sensorData) { public SensorDataModel(SensorData<Float> sensorData) {
this.sensorName = sensorData.getSensorNameOrAddress(); this.sensorName = sensorData.getSensorNameOrAddress();
this.sensorValue = sensorData.hasValue() && sensorData.isRecent() ? StringUtils.formatDecimal((float) sensorData.getValue(), 0) : null; this.sensorValue = sensorData.hasValue() && sensorData.isRecent() ? StringUtils.formatDecimal(sensorData.getValue(), 0) : null;
if (sensorData instanceof SensorDataHeartRate) { if (sensorData instanceof SensorDataHeartRate) {
this.labelId = R.string.sensor_state_heart_rate; this.labelId = R.string.sensor_state_heart_rate;
this.unitId = R.string.sensor_unit_beats_per_minute; this.unitId = R.string.sensor_unit_beats_per_minute;
+4 -3
View File
@@ -25,8 +25,9 @@
android:id="@+id/stats_sensor_value" android:id="@+id/stats_sensor_value"
style="@style/StatsLargeValue" style="@style/StatsLargeValue"
android:value="@string/value_unknown" android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@id/stats_sensor_unit"
app:layout_constraintTop_toBottomOf="@id/stats_sensor_sensor_value" app:layout_constraintTop_toBottomOf="@id/stats_sensor_sensor_value"
tools:text="90" /> tools:text="90" />
@@ -34,8 +35,8 @@
android:id="@+id/stats_sensor_unit" android:id="@+id/stats_sensor_unit"
style="@style/StatsUnit" style="@style/StatsUnit"
tools:text="@string/sensor_unit_beats_per_minute" tools:text="@string/sensor_unit_beats_per_minute"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@id/stats_sensor_value"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_sensor_value" /> app:layout_constraintBottom_toBottomOf="@id/stats_sensor_value" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
+183 -150
View File
@@ -30,91 +30,124 @@
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintGuide_end="8dp" /> app:layout_constraintGuide_end="8dp" />
<!-- Activity information: icon, name, description and start datetime -->
<ImageView
android:id="@+id/stats_activity_type_icon"
style="@style/IconStats"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="@id/stats_name_value"
tools:src="@drawable/ic_activity_bike_24dp" />
<TextView
android:id="@+id/stats_name_value"
style="@style/TextLargeStats"
android:layout_width="0dp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_activity_type_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Name of the activity" />
<TextView
android:id="@+id/stats_description_value"
style="@style/TextMediumStats"
android:layout_width="0dp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toStartOf="@id/stats_name_value"
app:layout_constraintTop_toBottomOf="@id/stats_name_value"
tools:text="Here will be a description that could be really long and it would show completely without problems." />
<TextView
android:id="@+id/stats_start_datetime_value"
style="@style/TextSmallStats"
android:layout_width="0dp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_description_barrier"
tools:text="Saturday 6th, 16:44" />
<!-- Barrier -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/stats_description_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="stats_activity_type_icon,stats_name_value,stats_description_value" />
<!-- Horizontal line -->
<View
android:id="@+id/stats_information_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_start_datetime_value" />
<!-- Distance --> <!-- Distance -->
<TextView <TextView
android:id="@+id/stats_distance_label" android:id="@+id/stats_distance_label"
style="@style/StatsLargeLabel" style="@style/StatsLargeLabel"
android:text="@string/stats_distance" android:text="@string/stats_distance"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline2" app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toBottomOf="@id/stats_information_horizontal_line" />
<TextView <TextView
android:id="@+id/stats_distance_value" android:id="@+id/stats_distance_value"
style="@style/StatsLargeValue" style="@style/StatsLargeValue"
android:value="@string/value_unknown" android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@+id/stats_distance_unit" app:layout_constraintEnd_toStartOf="@id/stats_distance_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_distance_label" app:layout_constraintTop_toBottomOf="@id/stats_distance_label"
tools:text="100" /> tools:text="100" />
<TextView <TextView
android:id="@+id/stats_distance_unit" android:id="@+id/stats_distance_unit"
style="@style/StatsUnit" style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_distance_value" app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
app:layout_constraintStart_toEndOf="@+id/stats_distance_value" app:layout_constraintStart_toEndOf="@id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@+id/guideline2" app:layout_constraintEnd_toStartOf="@id/guideline2"
tools:text="km" /> tools:text="km" />
<!-- Activity Type -->
<TextView
android:id="@+id/stats_activity_type_label"
style="@style/StatsLargeLabel"
android:text="@string/track_edit_activity_type_hint"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/stats_activity_type_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintBottom_toBottomOf="@+id/stats_distance_value"
app:layout_constraintTop_toTopOf="@+id/stats_distance_value"
app:tint="?android:textColorPrimary" />
<!-- Total time -->
<TextView
android:id="@+id/stats_total_time_label"
style="@style/StatsLargeLabel"
android:text="@string/stats_total_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/stats_distance_value" />
<TextView
android:id="@+id/stats_total_time_value"
style="@style/StatsLargeValue"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/stats_total_time_label"
tools:text="00:00:00" />
<!-- Moving time --> <!-- Moving time -->
<TextView <TextView
android:id="@+id/stats_moving_time_label" android:id="@+id/stats_moving_time_label"
style="@style/StatsLargeLabel" style="@style/StatsLargeLabel"
android:text="@string/stats_moving_time" android:text="@string/stats_moving_time"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@+id/stats_total_time_label" app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@+id/guideline2" app:layout_constraintTop_toBottomOf="@id/stats_information_horizontal_line" />
app:layout_constraintTop_toTopOf="@+id/stats_total_time_label"
app:layout_constraintVertical_bias="1.0" />
<TextView <TextView
android:id="@+id/stats_moving_time_value" android:id="@+id/stats_moving_time_value"
style="@style/StatsLargeValue" style="@style/StatsLargeValue"
android:value="@string/value_unknown" android:value="@string/value_unknown"
app:layout_constraintStart_toEndOf="@+id/guideline2" app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label" app:layout_constraintTop_toBottomOf="@id/stats_moving_time_label"
tools:text="00:00:00" />
<!-- Total time -->
<TextView
android:id="@+id/stats_total_time_label"
style="@style/StatsSmallLabel"
android:text="@string/stats_total_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_moving_time_value" />
<TextView
android:id="@+id/stats_total_time_value"
style="@style/StatsSmallValue"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_total_time_label"
tools:text="00:00:00" /> tools:text="00:00:00" />
<!-- Barrier --> <!-- Barrier -->
@@ -130,18 +163,74 @@
android:id="@+id/stats_speed_horizontal_line" android:id="@+id/stats_speed_horizontal_line"
style="@style/StatsHorizontalLine" style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/stats_time_barrier" /> app:layout_constraintTop_toBottomOf="@id/stats_time_barrier" />
<!-- Speed/Pace max -->
<TextView
android:id="@+id/stats_max_speed_label"
style="@style/StatsLargeLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
tools:text="Max. Speed" />
<TextView
android:id="@+id/stats_max_speed_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/stats_max_speed_unit"
app:layout_constraintTop_toBottomOf="@id/stats_max_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_max_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_max_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_max_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
tools:text="km/h" />
<!-- Avg. moving speed/pace -->
<TextView
android:id="@+id/stats_moving_speed_label"
style="@style/StatsLargeLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
tools:text="Avg. Moving Speed" />
<TextView
android:id="@+id/stats_moving_speed_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/stats_moving_speed_unit"
app:layout_constraintTop_toBottomOf="@id/stats_moving_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_moving_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_moving_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_moving_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
tools:text="km/h" />
<!-- Average speed/pace --> <!-- Average speed/pace -->
<TextView <TextView
android:id="@+id/stats_average_speed_label" android:id="@+id/stats_average_speed_label"
style="@style/StatsSmallLabel" style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline2" app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line" app:layout_constraintTop_toBottomOf="@id/stats_moving_speed_value"
tools:text="Avg. Speed" /> tools:text="Avg. Speed" />
<TextView <TextView
@@ -149,73 +238,17 @@
style="@style/StatsSmallValue" style="@style/StatsSmallValue"
android:value="@string/value_unknown" android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/guideline2" app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/stats_average_speed_unit" app:layout_constraintEnd_toStartOf="@id/stats_average_speed_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_average_speed_label" app:layout_constraintTop_toBottomOf="@id/stats_average_speed_label"
tools:text="30.00" /> tools:text="30.00" />
<TextView <TextView
android:id="@+id/stats_average_speed_unit" android:id="@+id/stats_average_speed_unit"
style="@style/StatsUnit" style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_average_speed_value" app:layout_constraintBottom_toBottomOf="@id/stats_average_speed_value"
app:layout_constraintStart_toEndOf="@+id/stats_average_speed_value" app:layout_constraintStart_toEndOf="@id/stats_average_speed_value"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
tools:text="km/h" />
<!-- Speed max -->
<TextView
android:id="@+id/stats_max_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
tools:text="Max. Speed" />
<TextView
android:id="@+id/stats_max_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toStartOf="@+id/stats_max_speed_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_max_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_max_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_max_speed_value"
app:layout_constraintStart_toEndOf="@+id/stats_max_speed_value"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
tools:text="km/h" />
<!-- Moving speed max -->
<TextView
android:id="@+id/stats_moving_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
tools:text="Max. Moving Speed" />
<TextView
android:id="@+id/stats_moving_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/stats_moving_speed_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_moving_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_moving_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_moving_speed_value"
app:layout_constraintStart_toEndOf="@+id/stats_moving_speed_value"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
tools:text="km/h" /> tools:text="km/h" />
<!-- Barrier --> <!-- Barrier -->
@@ -224,16 +257,16 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="bottom" app:barrierDirection="bottom"
app:constraint_referenced_ids="stats_moving_speed_value,stats_max_speed_value" /> app:constraint_referenced_ids="stats_max_speed_value,stats_moving_speed_value,stats_average_speed_value" />
<!-- Horizontal line --> <!-- Horizontal line -->
<View <View
android:id="@+id/stats_elevation_horizontal_line" android:id="@+id/stats_elevation_horizontal_line"
style="@style/StatsHorizontalLine" style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/stats_speed_barrier" /> app:layout_constraintTop_toBottomOf="@id/stats_speed_barrier" />
<!-- Elevation gain and loss --> <!-- Elevation gain and loss -->
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
@@ -244,56 +277,56 @@
<TextView <TextView
android:id="@+id/stats_elevation_gain_label" android:id="@+id/stats_elevation_gain_label"
style="@style/StatsSmallLabel" style="@style/StatsLargeLabel"
android:text="@string/stats_gain" android:text="@string/stats_gain"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline2" app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" /> app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
<TextView <TextView
android:id="@+id/stats_elevation_gain_value" android:id="@+id/stats_elevation_gain_value"
style="@style/StatsSmallValue" style="@style/StatsLargeValue"
android:value="@string/value_unknown" android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@+id/stats_elevation_gain_unit" app:layout_constraintEnd_toStartOf="@id/stats_elevation_gain_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_gain_label" app:layout_constraintTop_toBottomOf="@id/stats_elevation_gain_label"
tools:text="100" /> tools:text="100" />
<TextView <TextView
android:id="@+id/stats_elevation_gain_unit" android:id="@+id/stats_elevation_gain_unit"
style="@style/StatsUnit" style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_gain_value" app:layout_constraintBottom_toBottomOf="@id/stats_elevation_gain_value"
app:layout_constraintStart_toEndOf="@+id/stats_elevation_gain_value" app:layout_constraintStart_toEndOf="@id/stats_elevation_gain_value"
app:layout_constraintEnd_toStartOf="@+id/guideline2" app:layout_constraintEnd_toStartOf="@id/guideline2"
tools:text="m" /> tools:text="m" />
<TextView <TextView
android:id="@+id/stats_elevation_loss_label" android:id="@+id/stats_elevation_loss_label"
style="@style/StatsSmallLabel" style="@style/StatsLargeLabel"
android:text="@string/stats_loss" android:text="@string/stats_loss"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline2" app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" /> app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
<TextView <TextView
android:id="@+id/stats_elevation_loss_value" android:id="@+id/stats_elevation_loss_value"
style="@style/StatsSmallValue" style="@style/StatsLargeValue"
android:value="@string/value_unknown" android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/guideline2" app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/stats_elevation_loss_unit" app:layout_constraintEnd_toStartOf="@id/stats_elevation_loss_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_loss_label" app:layout_constraintTop_toBottomOf="@id/stats_elevation_loss_label"
tools:text="100" /> tools:text="100" />
<TextView <TextView
android:id="@+id/stats_elevation_loss_unit" android:id="@+id/stats_elevation_loss_unit"
style="@style/StatsUnit" style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_loss_value" app:layout_constraintBottom_toBottomOf="@id/stats_elevation_loss_value"
app:layout_constraintStart_toEndOf="@+id/stats_elevation_loss_value" app:layout_constraintStart_toEndOf="@id/stats_elevation_loss_value"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
tools:text="m" /> tools:text="m" />
<!-- Barrier --> <!-- Barrier -->
@@ -309,9 +342,9 @@
android:id="@+id/stats_end_horizontal_line" android:id="@+id/stats_end_horizontal_line"
style="@style/StatsHorizontalLine" style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_barrier" /> app:layout_constraintTop_toBottomOf="@id/stats_elevation_barrier" />
<!-- Sensors data --> <!-- Sensors data -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
+6 -6
View File
@@ -16,9 +16,9 @@ limitations under the License.
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/track_list_search" android:id="@+id/track_list_aggregated_stats"
android:title="@string/menu_search" android:icon="@drawable/ic_statistics_24dp"
app:actionViewClass="androidx.appcompat.widget.SearchView" android:title="@string/menu_aggregated_statistics"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<!-- Icon and title is overwritten by TrackListActivity --> <!-- Icon and title is overwritten by TrackListActivity -->
<!-- Hide icon as workaround for https://github.com/OpenTracksApp/OpenTracks/issues/200 --> <!-- Hide icon as workaround for https://github.com/OpenTracksApp/OpenTracks/issues/200 -->
@@ -29,9 +29,9 @@ limitations under the License.
android:visible="false" android:visible="false"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/track_list_aggregated_stats" android:id="@+id/track_list_search"
android:icon="@drawable/ic_statistics_24dp" android:title="@string/menu_search"
android:title="@string/menu_aggregated_statistics" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/track_list_markers" android:id="@+id/track_list_markers"
+96 -35
View File
@@ -34,43 +34,35 @@ limitations under the License.
<string name="help_track_map_content">" Brug det lille kortikon. <string name="help_track_map_content">" Brug det lille kortikon.
\n a) (live opdateringer) Installer &lt;a href=\"https://github.com/OpenTracksApp/OSMDashboard\"&gt; OSMDashboard&lt;/a&gt;. Det giver online eller offline kort via &lt;a href=\"https://www.openstreetmap.org\"&gt;OpenStreetMap&lt;/a&gt;. \n a) (live opdateringer) Installer &lt;a href=\"https://github.com/OpenTracksApp/OSMDashboard\"&gt; OSMDashboard&lt;/a&gt;. Det giver online eller offline kort via &lt;a href=\"https://www.openstreetmap.org\"&gt;OpenStreetMap&lt;/a&gt;.
\n Faktisk er OSMDashboard en separat app, der kun kan få adgang (midlertidigt) til dataene for spor, der skal vises. \n Faktisk er OSMDashboard en separat app, der kun kan få adgang (midlertidigt) til dataene for spor, der skal vises.
\n b) Via en normal kortapplikation (f.eks. &lt;a href=\"http://osmand.net/\"&gt; OsmAnd &lt;/a&gt; og &lt;a href=\"https://maps.me/\"&gt; MAPS.ME &lt; / a&gt;). Disse apps skal understøtte KMZ-filformatet (kun placeringer og tidsstempler). Faktisk skal en app understøtte &lt;i&gt; ACTION_VIEW &lt;/i&gt; med MIME &lt;i&gt; -applikation / vnd.google-earth.kmz &lt;/i&gt;. "</string> \n b) Via en normal kortapplikation (f.e., &lt;a href=\"http://osmand.net/\"&gt;OsmAnd&lt;/a&gt; og &lt;a href=\"https://maps.me/\"&gt;MAPS.ME&lt; / a&gt;). Disse apps skal understøtte KMZ fil formatet (kun placeringer og tidsstempler). Faktisk, skal en app understøtte &lt;i&gt;ACTION_VIEW&lt;/i&gt; med MIME &lt;i&gt;applikation/vnd.google-earth.kmz &lt;/i&gt;. "</string>
<string name="help_storage_pictures_title">Hvordan gemmer OpenTracks billeder, der blev taget som markører?</string> <string name="help_storage_pictures_title">Hvordan gemmer OpenTracks billeder, der blev taget som markører?</string>
<string name="help_storage_pictures_content">Disse gemmes i appens interne lager (dvs. kun tilgængelig inden for OpenTracks).</string> <string name="help_storage_pictures_content">Disse gemmes i appens interne lager (dvs. kun tilgængelig inden for OpenTracks).</string>
<string name="help_track_export_title">Hvordan kan jeg eksportere / dele indspillede numre?</string> <string name="help_track_export_title">Hvordan kan jeg eksportere / dele indspillede numre?</string>
<string name="help_track_export_content"> <string name="help_track_export_content"> Spor kan eksporteres fra indstillingerne som KMZ, KML eller GPX. KMZ / KML alle data; GPX kun placeringer. Spor, der deles via <i>delingsikonet</i> deles som KMZ. </string>
Spor kan eksporteres fra indstillingerne som KMZ, KML eller GPX.
KMZ / KML alle data; GPX kun placeringer.
Spor, der deles via <i>delingsikonet</i> deles som KMZ.</string>
<string name="help_data_google_play_services_title">Kræver/bruger OpenTracks Google Play Service?</string> <string name="help_data_google_play_services_title">Kræver/bruger OpenTracks Google Play Service?</string>
<string name="help_recording_title">Hvordan registrerer OpenTracks data?</string> <string name="help_recording_title">Hvordan registrerer OpenTracks data?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks bruger enhedens GPS-sensor til at registrere placeringen kontinuerligt. OpenTracks bruger enhedens GPS-sensor til at registrere placeringen kontinuerligt.
Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, højde), kan OpenTracks ikke gøre noget ved det.</string> Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, højde), kan OpenTracks ikke gøre noget ved det.</string>
<string name="help_data_storage_title">Hvordan gemmer OpenTracks sensordata?</string> <string name="help_data_storage_title">Hvordan gemmer OpenTracks sensordata?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content"> Sensordata gemmes pr. Optaget placering. Med hensyn til en pulssensor betyder det, at der kun er <i> en </i> måling af hjertefrekvensen lagret pr. Placering. Hvis du står stille eller er indendørs, registreres ingen sensordata (men de vil blive vist). </string>
Sensordata gemmes pr. Optaget placering.
Med hensyn til en pulssensor betyder det, at der kun er <i> en </i> måling af hjertefrekvensen lagret pr. Placering.
Hvis du står stille eller er indendørs, registreres ingen sensordata (men de vil blive vist).</string>
<string name="help_gps_troubleshooting_title">OpenTracks optager ikke placeringer?</string> <string name="help_gps_troubleshooting_title">OpenTracks optager ikke placeringer?</string>
<string name="help_gps_troubleshooting_content"> <string name="help_gps_troubleshooting_content"> Hvad du kan gøre:
Hvad du kan gøre:\n \n a) Kontroller, om GPS er aktiveret,
a) Kontroller, om GPS er aktiveret,\n \n b) forbedre GPS-modtagelse (f.eks. gå udendørs),
b) forbedre GPS-modtagelse (f.eks. gå udendørs), \n \n c) Kontroller, om du har appen <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> og
c) Kontroller, om du har appen <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> og \n \n d) deaktiver strømbesparelse.
d) deaktiver strømbesparelse. \n
\n \n
Hvis alt ikke hjælper, skal du rapportere en fejl.</string> \n Hvis alt ikke hjælper, skal du rapportere en fejl. </string>
<string name="help_support_title">Hvordan kan jeg støtte OpenTracks?</string> <string name="help_support_title">Hvordan kan jeg støtte OpenTracks?</string>
<string name="help_support_content"> <string name="help_support_content"> Du kan:
Du kan: \n \n a) sprede ordet om OpenTracks,
a) sprede ordet om OpenTracks, \n \n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> foreslå funktioner </a>,
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> foreslå funktioner </a>, \n \n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> rapportfejl </a>,
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> rapportfejl </a>, \n \n d) hjælp til softwareudvikling,
d) hjælp til softwareudvikling, \n \n e) køb i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play Store </a>,
e) køb i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play Store </a>, \n \n f) donere, og
f) donere, og \n \n g) gør hvad du synes hjælper OpenTracks. </string>
g) gør hvad du synes hjælper OpenTracks.</string>
<string name="open_track_using_dashboard">Åben med OpenTracks-Dashboard</string> <string name="open_track_using_dashboard">Åben med OpenTracks-Dashboard</string>
<string name="open_track_as_KMZ">Åben som KMZ</string> <string name="open_track_as_KMZ">Åben som KMZ</string>
<string name="open_track_as_GPX">Åben som GPX</string> <string name="open_track_as_GPX">Åben som GPX</string>
@@ -135,9 +127,9 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<string name="activity_type_trail_hiking">vandretur</string> <string name="activity_type_trail_hiking">vandretur</string>
<string name="activity_type_trail_running">løb på sti</string> <string name="activity_type_trail_running">løb på sti</string>
<string name="activity_type_train">tog</string> <string name="activity_type_train">tog</string>
<string name="activity_type_ultimate_frisbee">ultimate frisbee</string> <string name="activity_type_ultimate_frisbee">ultimativ frisbee</string>
<string name="activity_type_unknown">ukendt</string> <string name="activity_type_unknown">ukendt</string>
<string name="activity_type_wakeboarding">wakeboarding</string> <string name="activity_type_wakeboarding">vågneboarding</string>
<string name="activity_type_walking">til fods</string> <string name="activity_type_walking">til fods</string>
<string name="activity_type_water_skiing">vandski</string> <string name="activity_type_water_skiing">vandski</string>
<string name="activity_type_wind_surfing">windsurfing</string> <string name="activity_type_wind_surfing">windsurfing</string>
@@ -178,7 +170,7 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<string name="description_total_distance">Samlet afstand: %1$.2f km (%2$.1f mil)</string> <string name="description_total_distance">Samlet afstand: %1$.2f km (%2$.1f mil)</string>
<string name="description_total_time">Samlet tid: %1$s</string> <string name="description_total_time">Samlet tid: %1$s</string>
<string name="description_track_point">Punkt</string> <string name="description_track_point">Punkt</string>
<string name="description_track_segment">Segment</string> <string name="description_track_segment">segment</string>
<!-- Export --> <!-- Export -->
<string name="export_all_option">Alle som %1$s</string> <string name="export_all_option">Alle som %1$s</string>
<string name="export_all_title">Eksportér til eksternt lager</string> <string name="export_all_title">Eksportér til eksternt lager</string>
@@ -347,7 +339,7 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<string name="settings_reset_done">Alle indstillinger er blevet gendannet til standardværdierne</string> <string name="settings_reset_done">Alle indstillinger er blevet gendannet til standardværdierne</string>
<string name="settings_error_initial_values_restored">Beklager, men der opstod en fejl. Alle indstillinger er vendt tilbage til standardværdierne.</string> <string name="settings_error_initial_values_restored">Beklager, men der opstod en fejl. Alle indstillinger er vendt tilbage til standardværdierne.</string>
<!-- Settings Sensor --> <!-- Settings Sensor -->
<string name="settings_sensor">Sensor</string> <string name="settings_sensor">sensor</string>
<string name="settings_sensor_bluetooth">Bluetooth</string> <string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string> <string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connecting">Tilsluttet %1$s</string> <string name="settings_sensor_connecting">Tilsluttet %1$s</string>
@@ -360,8 +352,9 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<string name="settings_stats_units_title">Foretrukne enheder</string> <string name="settings_stats_units_title">Foretrukne enheder</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file">Del rutefil</string> <string name="share_track_share_file">Del rutefil</string>
<string name="share_track_share_file_body">Du kunne måske være interesseret i denne rute.\n\nDu kan bruge Google Earth eller anden <string name="share_track_share_file_body">Jeg tror, du måske er interesseret i dette nummer.
kortlægningssoftware til at se ruten.\n\n%1$s</string> \n
\n%1$s</string>
<string name="share_track_subject">Jeg vil gerne dele en rute med dig</string> <string name="share_track_subject">Jeg vil gerne dele en rute med dig</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">Gns. bevægelsestempo</string> <string name="stats_average_moving_pace">Gns. bevægelsestempo</string>
@@ -440,9 +433,9 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<string name="value_integer_meter_recommended">%1$d m (anbefalet)</string> <string name="value_integer_meter_recommended">%1$d m (anbefalet)</string>
<string name="value_integer_mile">%1$d mil</string> <string name="value_integer_mile">%1$d mil</string>
<string name="value_integer_mile_hour">%1$d mil/t</string> <string name="value_integer_mile_hour">%1$d mil/t</string>
<string name="value_integer_minute">%1$d min.</string> <string name="value_integer_minute">%1$d min</string>
<string name="value_integer_percent">%1$d %%</string> <string name="value_integer_percent">%1$d %%</string>
<string name="value_integer_second">%1$d sek.</string> <string name="value_integer_second">%1$d sek</string>
<string name="value_none">Ingen</string> <string name="value_none">Ingen</string>
<string name="value_off">Fra</string> <string name="value_off">Fra</string>
<string name="value_smallest">Mindste</string> <string name="value_smallest">Mindste</string>
@@ -533,13 +526,13 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<string name="waypoint_type_pothole">hul i vejen</string> <string name="waypoint_type_pothole">hul i vejen</string>
<string name="waypoint_type_railway_crossing">jernbaneoverskæring</string> <string name="waypoint_type_railway_crossing">jernbaneoverskæring</string>
<string name="waypoint_type_restaurant">restaurant</string> <string name="waypoint_type_restaurant">restaurant</string>
<string name="waypoint_type_shopping">shopping</string> <string name="waypoint_type_shopping">handle ind</string>
<string name="waypoint_type_sight">udsigt</string> <string name="waypoint_type_sight">udsigt</string>
<string name="waypoint_type_store">butik</string> <string name="waypoint_type_store">butik</string>
<string name="waypoint_type_swimming_pool">swimmingpool</string> <string name="waypoint_type_swimming_pool">swimmingpool</string>
<string name="waypoint_type_telephone">telefon</string> <string name="waypoint_type_telephone">telefon</string>
<string name="waypoint_type_theater">teater</string> <string name="waypoint_type_theater">teater</string>
<string name="waypoint_type_toilet">toilet</string> <string name="waypoint_type_toilet">toilette</string>
<string name="waypoint_type_tourist_information">turistinformation</string> <string name="waypoint_type_tourist_information">turistinformation</string>
<string name="waypoint_type_tree">træ</string> <string name="waypoint_type_tree">træ</string>
<string name="waypoint_type_tunnel">tunnel</string> <string name="waypoint_type_tunnel">tunnel</string>
@@ -549,4 +542,72 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<!-- Share image --> <!-- Share image -->
<string name="share_image_subject">Jeg vil gerne dele et billede med dig</string> <string name="share_image_subject">Jeg vil gerne dele et billede med dig</string>
<string name="share_image_body">Jeg tror, du måske er interesseret i dette billede.</string> <string name="share_image_body">Jeg tror, du måske er interesseret i dette billede.</string>
<string name="open_track_as_trackfileformat">Åbn som %1$s</string>
<string name="activity_type_escooter">elektrisk scooter</string>
<string name="generic_click_twice_cancel">Klik tilbage igen for at annullere</string>
<string name="value_float_kilometer_hour">%1$.2f km/t</string>
<string name="import_unable_to_import_file">Kunne ikke importere filen:%1$s</string>
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
<string name="import_prevent_reimport">Du har indstillet forhindre genimport af spor</string>
<string name="settings_default_export_uri_title">Spor eksportmappe</string>
<string name="instant_export_enabled_summary">Eksporter spor til lagring, når optagelsen er færdig</string>
<string name="export_progress_summary_new_msg">Nye spor eksporteret</string>
<string name="instant_export_enabled_title">Øjeblikkelig eksport efter træning</string>
<string name="toast_message_instant_track_was_exported">Sidste spor eksporteret</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_skip">Springe</string>
<string name="description_elevation_loss">Højdetab: %1$d m (%2$d ft)</string>
<string name="export_error_post_workout">Eksport efter træning mislykkedes. Kontroller eksportmappen.</string>
<string name="export_track_already_exists_msg">Sporet %1$s findes allerede i destinationsmappen.</string>
<string name="export_all_do_it_for_all">gør det for alle konflikter</string>
<string name="export_track_errors">Spor ikke eksporteret</string>
<string name="gps_disabled_msg">GPS deaktiveret</string>
<string name="gps_fixed_and_ready">GPS fast og klar</string>
<string name="import_no_kml_file_found">Ingen doc.kml-fil fundet i KMZ</string>
<string name="sensor_state_cadence_avg">Gennemsnitlig kadence</string>
<string name="sensor_state_cadence_max">Max kadence</string>
<string name="sensor_state_heart_rate_avg">Gns. Puls</string>
<string name="sensor_state_heart_rate_max">Maks. Puls</string>
<string name="sensor_state_power_avg">Gennemsnitlig strøm</string>
<string name="settings_default_trackfileformat">Eksport/del filformat</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Fuld skærm</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Under optagelse, vises på fuld skærm.</string>
<string name="settings_night_mode_title">UI Tema</string>
<string name="settings_night_mode_option_system">Ordning</string>
<string name="settings_night_mode_option_day">Dag</string>
<string name="settings_night_mode_option_night">Nat</string>
<string name="settings_sensor_wheel_circumference">Hjulets omkreds (mm)</string>
<string name="stats_split_interval">Split, divide</string>
<string name="stats_intervals">divides</string>
<string name="interval_list_empty_message">Der er ikke noget interval endnu</string>
<string name="aggregated_stats_empty_message">Optag dit første spor for at se aggregerede statistikker</string>
<string name="track_detail_intervals_tab">Intervaller</string>
<string name="value_float_mile_hour">%1$.2f mph</string>
<string name="voice_pace_lap">Omgangstid på %1$s</string>
<string name="voice_speed_lap">Omgangshastighed på %1$s</string>
<string name="hold_to_pause">Hold nede for at holde pause</string>
<string name="hold_to_stop">Hold for at stoppe</string>
<string name="settings_prevent_reimport_tracks_title">Forhindre genimport af spor</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Afsluttet med en fejl</item>
<item quantity="other">Afsluttet med %1$d fejl</item>
</plurals>
<string name="stats_loss">Tab</string>
<string name="export_progress_summary_overwrite_msg">Spor overskrevet</string>
<string name="export_progress_summary_skip_msg">Spor sprunget over</string>
<string name="export_progress_summary_errors_msg">Fejl</string>
<string name="generic_open_track">Åbn spor</string>
<string name="generic_completed">Afsluttet</string>
<string name="import_thread_interrupted">Tråden afbrudt</string>
<string name="import_unsupported_format">Ikke-understøttet filformat</string>
<string name="import_parser_error">Parserfejl:%1$s</string>
<string name="import_error_list_dialog_title">Filer ikke importeret</string>
<string name="import_progress_summary_ok_msg">Importerede filer</string>
<string name="import_progress_summary_exists_msg">Eksisterer allerede</string>
<string name="import_progress_summary_errors_msg">Fejl</string>
<string name="import_file_imported">Filen%1$s er importeret</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="generic_show_errors">Vis fejl</string>
<string name="generic_overwrite">Overskriv</string>
</resources> </resources>
+27 -29
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc. Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> --><resources>
<resources>
<string name="about_preference_title">Über OpenTracks</string> <string name="about_preference_title">Über OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> ist eine Sport-Tracking-Anwendung, die Ihre Privatsphäre vollständig respektiert.</string> <string name="about_description"><i>OpenTracks</i> ist eine Sport-Tracking-Anwendung, die Ihre Privatsphäre vollständig respektiert.</string>
<string name="about_url">Projekt-Website: <a href="%1$s">%1$s</a></string> <string name="about_url">Projekt-Website: <a href="%1$s">%1$s</a></string>
@@ -30,7 +30,6 @@ limitations under the License.
* Markierungen erstellen (inkl. Bilder)\n * Markierungen erstellen (inkl. Bilder)\n
* arbeitet vollständig offline\n * arbeitet vollständig offline\n
* Tracks auf einer Karte anzeigen (siehe unten)</string> * Tracks auf einer Karte anzeigen (siehe unten)</string>
<string name="help_track_map_title">Wie wird ein Track auf einer Karte angezeigt?</string> <string name="help_track_map_title">Wie wird ein Track auf einer Karte angezeigt?</string>
<string name="help_track_map_content"> <string name="help_track_map_content">
Verwende das kleine Kartensymbol.\n Verwende das kleine Kartensymbol.\n
@@ -44,39 +43,28 @@ b)
Über eine normale Kartenanwendung (z.B. <a href="http://osmand.net/">OsmAnd</a> und <a href="https://maps.me/">MAPS.ME</a>). Über eine normale Kartenanwendung (z.B. <a href="http://osmand.net/">OsmAnd</a> und <a href="https://maps.me/">MAPS.ME</a>).
Diese Anwendungen müssen das KMZ-Dateiformat unterstützen (nur Orte und Zeitstempel). Diese Anwendungen müssen das KMZ-Dateiformat unterstützen (nur Orte und Zeitstempel).
In der Tat muss eine App <i>ACTION_VIEW</i> mit MIME <i>application/vnd.google-earth.kmz</i> unterstützen.</string> In der Tat muss eine App <i>ACTION_VIEW</i> mit MIME <i>application/vnd.google-earth.kmz</i> unterstützen.</string>
<string name="help_storage_pictures_title">Wie speichert OpenTracks Bilder, die als Markierungen aufgenommen wurden?</string> <string name="help_storage_pictures_title">Wie speichert OpenTracks Bilder, die als Markierungen aufgenommen wurden?</string>
<string name="help_storage_pictures_content">Diese werden App-intern gespeichert (d.h. nur innerhalb von OpenTracks zugänglich).</string> <string name="help_storage_pictures_content">Diese werden App-intern gespeichert (d.h. nur innerhalb von OpenTracks zugänglich).</string>
<string name="help_track_export_title">Wie kann ich aufgezeichnete Tracks exportieren/teilen?</string> <string name="help_track_export_title">Wie kann ich aufgezeichnete Tracks exportieren/teilen?</string>
<string name="help_track_export_content"> <string name="help_track_export_content"> Tracks können aus den Einstellungen als KMZ, KML oder GPX exportiert werden. KMZ/KML alle Daten; GPX nur Standorte. Tracks, die über das Symbol <i>Teilen Icon</i> freigegeben werden, werden als KMZ freigegeben. </string>
Tracks können aus den Einstellungen als KMZ, KML oder GPX exportiert werden.
KMZ/KML alle Daten; GPX nur Standorte.
Tracks, die über das Symbol <i>Teilen Icon</i> freigegeben werden, werden als KMZ freigegeben.</string>
<string name="help_data_google_play_services_title">Benötigt/benutzt OpenTracks den Google Play Service?</string> <string name="help_data_google_play_services_title">Benötigt/benutzt OpenTracks den Google Play Service?</string>
<string name="help_recording_title">Wie zeichnet OpenTracks Daten auf?</string> <string name="help_recording_title">Wie zeichnet OpenTracks Daten auf?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks verwendet den GPS-Sensor des Geräts, um den Standort kontinuierlich aufzuzeichnen. OpenTracks verwendet den GPS-Sensor des Geräts, um den Standort kontinuierlich aufzuzeichnen.
Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), kann OpenTracks nichts dagegen unternehmen.</string> Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), kann OpenTracks nichts dagegen unternehmen.</string>
<string name="help_data_storage_title">Wie speichert OpenTracks Sensordaten?</string> <string name="help_data_storage_title">Wie speichert OpenTracks Sensordaten?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content">
Sensordaten werden pro aufgezeichneter Stelle gespeichert. Sensordaten werden pro aufgezeichneter Stelle gespeichert.
Bezogen auf einen Herzfrequenzsensor bedeutet dies, dass nur <i>eine</i>Herzfrequenzmessung pro Ort gespeichert wird. Bezogen auf einen Herzfrequenzsensor bedeutet dies, dass nur <i>eine</i>Herzfrequenzmessung pro Ort gespeichert wird.
Wenn Du still stehst oder du dich in geschlossenen Räumen aufhälst, werden keine Sensordaten aufgezeichnet (aber angezeigt).</string> Wenn Du still stehst oder du dich in geschlossenen Räumen aufhälst, werden keine Sensordaten aufgezeichnet (aber angezeigt).</string>
<string name="help_gps_troubleshooting_title">OpenTracks zeichnet keine Standorte auf?</string> <string name="help_gps_troubleshooting_title">OpenTracks zeichnet keine Standorte auf?</string>
<string name="help_gps_troubleshooting_content"> <string name="help_gps_troubleshooting_content"> Was Du tun kannst:
Was Du tun kannst:\n \n a) prüfen, ob GPS aktiviert ist,
a) prüfen, ob GPS aktiviert ist,\n \n b) den GPS-Empfang verbessern (z.B. ins Freie gehen),
b) den GPS-Empfang verbessern (z.B. ins Freie gehen),\n \n c) mit der <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>-App das GPS testen, und
c) prüfen, ob mit <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, und\n \n d) Energiesparen deaktivieren.
d) Energiesparen deaktivieren.\n
\n \n
Wenn alles nicht hilft, melde bitte einen Fehler.</string> \n Wenn das alles nicht hilft, melde bitte einen Fehler. </string>
<string name="help_support_title">Wie kann ich OpenTracks unterstützen?</string> <string name="help_support_title">Wie kann ich OpenTracks unterstützen?</string>
<string name="help_support_content"> <string name="help_support_content">
Du kannst:\n Du kannst:\n
@@ -87,12 +75,10 @@ d) Hilfe bei der Software-Entwicklung,\n
e) kaufen in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n e) kaufen in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n
f) spenden, und\n f) spenden, und\n
g) das tun, was Deiner Meinung nach OpenTracks hilft.</string> g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
<string name="open_track_using_dashboard">Öffne mithilfe des OpenTracks-Dashboard</string> <string name="open_track_using_dashboard">Öffne mithilfe des OpenTracks-Dashboard</string>
<string name="open_track_as_KMZ">Als KMZ öffnen</string> <string name="open_track_as_KMZ">Als KMZ öffnen</string>
<string name="open_track_as_GPX">Als GPX öffnen</string> <string name="open_track_as_GPX">Als GPX öffnen</string>
<string name="open_track_as_trackfileformat">Als %1$s öffnen</string> <string name="open_track_as_trackfileformat">Als %1$s öffnen</string>
<!-- Activity Type --> <!-- Activity Type -->
<string name="activity_type_airplane">Flugzeug</string> <string name="activity_type_airplane">Flugzeug</string>
<string name="activity_type_atv">Quad</string> <string name="activity_type_atv">Quad</string>
@@ -370,21 +356,20 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
<string name="settings_sensor_bluetooth_sensor">Bluetooth-Sensor</string> <string name="settings_sensor_bluetooth_sensor">Bluetooth-Sensor</string>
<string name="settings_sensor_connecting">Verbunden mit %1$s</string> <string name="settings_sensor_connecting">Verbunden mit %1$s</string>
<string name="settings_sensor_disconnected">Verbindung getrennt von %1$s</string> <string name="settings_sensor_disconnected">Verbindung getrennt von %1$s</string>
<!-- Settings Stats --> <!-- Settings Stats -->
<string name="settings_stats_coordinate">Breiten-/Längengrad</string> <string name="settings_stats_coordinate">Breiten-/Längengrad</string>
<string name="settings_stats_rate_title">Bevorzugtes Maß</string> <string name="settings_stats_rate_title">Bevorzugtes Maß</string>
<string name="settings_stats_units_imperial">Englisch (mi, ft)</string> <string name="settings_stats_units_imperial">Englisch (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisch (km, m)</string> <string name="settings_stats_units_metric">Metrisch (km, m)</string>
<string name="settings_stats_units_title">Bevorzugte Einheiten</string> <string name="settings_stats_units_title">Bevorzugte Einheiten</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file">Track-Datei teilen</string> <string name="share_track_share_file">Track-Datei teilen</string>
<string name="share_track_share_file_body">Dieser Track könnte dich interessieren.\n\nDu kannst ihn dir mit Google Earth oder einer anderen Kartierungssoftware ansehen.\n\n%1$s</string> <string name="share_track_share_file_body">Dieser Track könnte dich interessieren.\n\nDu kannst ihn dir mit Google Earth oder einer anderen Kartierungssoftware ansehen.\n\n%1$s</string>
<string name="share_track_subject">Ich möchte einen Track mit dir teilen</string> <string name="share_track_subject">Ich möchte einen Track mit dir teilen</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">Durchschnitt\n(Bewegung)</string> <string name="stats_average_moving_pace">Durchschnitt\n(Bewegung)</string>
<string name="stats_average_moving_speed">Durchschnitt\n(Bewegung)</string> <string name="stats_average_moving_speed">Durchschnittsgeshwindigkeit
\n(Bewegung)</string>
<string name="stats_average_pace">Durchschnitt</string> <string name="stats_average_pace">Durchschnitt</string>
<string name="stats_average_speed">Durchschnitt</string> <string name="stats_average_speed">Durchschnitt</string>
<string name="stats_elevation">Höhe</string> <string name="stats_elevation">Höhe</string>
@@ -394,7 +379,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
<string name="stats_latitude">Breitengrad</string> <string name="stats_latitude">Breitengrad</string>
<string name="stats_longitude">Längengrad</string> <string name="stats_longitude">Längengrad</string>
<string name="stats_max">Max.</string> <string name="stats_max">Max.</string>
<string name="stats_max_speed">Max.</string> <string name="stats_max_speed">Max. Geschwindigkeit</string>
<string name="stats_min">Min.</string> <string name="stats_min">Min.</string>
<string name="stats_moving_time">Bewegungszeit</string> <string name="stats_moving_time">Bewegungszeit</string>
<string name="stats_pace">Tempo</string> <string name="stats_pace">Tempo</string>
@@ -570,5 +555,18 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
<!-- Share image --> <!-- Share image -->
<string name="share_image_subject">Ich möchte gerne ein Bild mit Dir teilen</string> <string name="share_image_subject">Ich möchte gerne ein Bild mit Dir teilen</string>
<string name="share_image_body">Ich denke, dieses Bild könnte Dich interessieren.</string> <string name="share_image_body">Ich denke, dieses Bild könnte Dich interessieren.</string>
<string name="settings_night_mode_title">UI Thema</string>
<string name="settings_night_mode_option_system">System</string>
<string name="settings_night_mode_option_day">Tag</string>
<string name="settings_night_mode_option_night">Nacht</string>
<string name="settings_sensor_wheel_circumference">Radumfang (mm)</string>
<string name="stats_loss">Abstieg</string>
<string name="hold_to_stop">Halten zum Anhalten</string>
<string name="import_progress_summary_errors_msg">Fehler</string>
<string name="import_progress_summary_exists_msg">Schon vorhanden</string>
<string name="export_track_errors">Tracks werden nicht exportiert</string>
<string name="settings_default_export_uri_title">Track Export Verzeichnis</string>
<string name="import_progress_summary_ok_msg">Dateien importiert</string>
<string name="export_all_do_it_for_all">für alle Konflikte anwenden</string>
<string name="export_progress_summary_new_msg">Neue Tracks exportiert</string>
</resources> </resources>
+66 -57
View File
@@ -23,62 +23,45 @@ limitations under the License.
<string name="about_libraries">Bibliotecas</string> <string name="about_libraries">Bibliotecas</string>
<!-- Help --> <!-- Help -->
<string name="help_feature_title">OpenTracks tiene las siguientes características:</string> <string name="help_feature_title">OpenTracks tiene las siguientes características:</string>
<string name="help_feature_content"> <string name="help_feature_content">* crear pistas de GPS
* crear un seguimiento GPS * \n* utilizar un sensor de frecuencia cardíaca (solo Bluetooth LE)
* usa un sensor de ritmo cardíaco (sólo Bluetooth LE) * \n* Anuncios de voz (por tiempo o distancia)
* anuncios de voz (por tiempo o distancia)* \n* crear marcadores (incluidas imágenes)
* crear marcadores (incluyendo imágenes) * \n* funciona completamente sin conexión
* funciona completamente fuera de línea * \n* mostrar pistas en un mapa (ver más abajo)</string>
* mostrar las pistas en un mapa (ver abajo)</string>
<string name="help_track_map_title">¿Cómo mostrar una pista en un mapa?</string> <string name="help_track_map_title">¿Cómo mostrar una pista en un mapa?</string>
<string name="help_track_map_content"> <string name="help_track_map_content"> Usa el icono del mapa pequeño.
Usa el icono del mapa pequeño. \na) (actualizaciones en vivo) Instalar <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Proporciona mapas en línea o fuera de línea a través de <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
a) \n En realidad, OSMDashboard es una aplicación independiente que sólo puede acceder (temporalmente) a los datos de las pist(as) que se van a mostrar.
(actualizaciones en vivo) Instalar <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. \n b) A través de una aplicación de mapas normal (por ejemplo, <a href="http://osmand.net/">OsmAnd</a> y <a href="https://maps.me/">MAPS.ME</a>). Estas aplicaciones necesitan soportar el formato de archivo KMZ (sólo las ubicaciones y las marcas de tiempo). De hecho, una aplicación necesita soportar <i>ACTION_VIEW</i> con MIME <i>application/vnd.google-earth.kmz</i>. </string>
Proporciona mapas en línea o fuera de línea a través de <a href="https://www.openstreetmap.org">OpenStreetMap</a>.\n
En realidad, OSMDashboard es una aplicación independiente que sólo puede acceder (temporalmente) a los datos de las pist(as) que se van a mostrar.
\n
b)
A través de una aplicación de mapas normal (por ejemplo, <a href="http://osmand.net/">OsmAnd</a> y <a href="https://maps.me/">MAPS.ME</a>).
Estas aplicaciones necesitan soportar el formato de archivo KMZ (sólo las ubicaciones y las marcas de tiempo).
De hecho, una aplicación necesita soportar <i>ACTION_VIEW</i> con MIME <i>application/vnd.google-earth.kmz</i>.</string>
<string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las fotos que fueron tomadas como marcadores?</string> <string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las fotos que fueron tomadas como marcadores?</string>
<string name="help_storage_pictures_content">Estos se almacenan en el almacenamiento interno de la aplicación (es decir, sólo accesible dentro de OpenTracks).</string> <string name="help_storage_pictures_content">Estos se almacenan en el almacenamiento interno de la aplicación (es decir, sólo accesible dentro de OpenTracks).</string>
<string name="help_track_export_title">¿Cómo puedo exportar/compartir las pistas grabadas?</string> <string name="help_track_export_title">¿Cómo puedo exportar/compartir las pistas grabadas?</string>
<string name="help_track_export_content"> <string name="help_track_export_content"> Las pistas pueden ser exportadas desde los ajustes como KMZ, KML o GPX. KMZ/KML todos los datos; GPX sólo las ubicaciones. Las pistas compartidas a través del <i> icono de compartir </i> se comparten como KMZ. </string>
Las pistas pueden ser exportadas desde los ajustes como KMZ, KML o GPX.
KMZ/KML todos los datos; GPX sólo las ubicaciones.
Las pistas compartidas a través del <i> icono de compartir </i> se comparten como KMZ.</string>
<string name="help_data_google_play_services_title">¿Requiere OpenTracks usar el servicio de Google Play?</string> <string name="help_data_google_play_services_title">¿Requiere OpenTracks usar el servicio de Google Play?</string>
<string name="help_recording_title">¿Cómo registra OpenTracks los datos?</string> <string name="help_recording_title">¿Cómo registra OpenTracks los datos?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks utiliza el sensor GPS del dispositivo para registrar la ubicación de forma continua. OpenTracks utiliza el sensor GPS del dispositivo para registrar la ubicación de forma continua.
Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocidad, elevación), OpenTracks no puede hacer nada al respecto.</string> Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocidad, elevación), OpenTracks no puede hacer nada al respecto.</string>
<string name="help_data_storage_title">¿Cómo almacena OpenTracks los datos de los sensores?</string> <string name="help_data_storage_title">¿Cómo almacena OpenTracks los datos de los sensores?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content"> Los datos de los sensores se almacenan por cada ubicación registrada. En términos de un sensor de frecuencia cardíaca, esto significa que sólo <i>uno</i> la medición de la frecuencia cardíaca se almacena por ubicación. Si está parado o en el interior, no se registrarán los datos del sensor (pero se mostrarán). </string>
Los datos de los sensores se almacenan por cada ubicación registrada.
En términos de un sensor de frecuencia cardíaca, esto significa que sólo <i>uno</i> la medición de la frecuencia cardíaca se almacena por ubicación.
Si está parado o en el interior, no se registrarán los datos del sensor (pero se mostrarán).</string>
<string name="help_gps_troubleshooting_title">OpenTracks no está grabando las localizaciones?</string> <string name="help_gps_troubleshooting_title">OpenTracks no está grabando las localizaciones?</string>
<string name="help_gps_troubleshooting_content"> <string name="help_gps_troubleshooting_content"> Lo que puedes hacer:
Lo que puedes hacer... \na) compruebe si el GPS está habilitado,
a) Comprobar si el GPS está activado. \nb) mejorar la recepción del GPS (por ejemplo, salir al aire libre),
b) mejorar la recepción del GPS (por ejemplo, ir al exterior),\n \nc) verifique con la aplicación <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a>, y
c) comprueba si con <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, y\n \nd) desactivar el ahorro de energía.
d) desactivar el ahorro de energía.
\n \n
Si todo no ayuda, por favor informe de un error.</string> \nSi todo no ayuda, informe un error. </string>
<string name="help_support_title">¿Cómo puedo apoyar OpenTracks?</string> <string name="help_support_title">¿Cómo puedo apoyar OpenTracks?</string>
<string name="help_support_content"> <string name="help_support_content"> Usted puede:
Puedes... \n a) difundir la existencia de OpenTracks,
a) difundir la palabra sobre OpenTracks,... \n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">proponer características</a>,
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">proponer características</a>,\n \n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">reportar errores</a>,
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">report bugs</a>,\n \n d) ayudar en el desarrollo del software,
d) ayudar en el desarrollo de software,\n \n e) comprar en <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
e) comprar en <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n \n f) donar, y
f) donar, y \n g) hacer lo que creas que ayuda a OpenTracks. </string>
g) haz lo que creas que ayude a OpenTracks.</string>
<!-- Activity Type --> <!-- Activity Type -->
<string name="activity_type_airplane">avión</string> <string name="activity_type_airplane">avión</string>
<string name="activity_type_atv">vehículos todoterreno</string> <string name="activity_type_atv">vehículos todoterreno</string>
@@ -94,8 +77,8 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="activity_type_driving">conducir</string> <string name="activity_type_driving">conducir</string>
<string name="activity_type_driving_bus">ruta en autobús</string> <string name="activity_type_driving_bus">ruta en autobús</string>
<string name="activity_type_driving_car">ruta en automóvil</string> <string name="activity_type_driving_car">ruta en automóvil</string>
<string name="activity_type_ferry">ferry</string> <string name="activity_type_ferry">balsa</string>
<string name="activity_type_frisbee">frisbee</string> <string name="activity_type_frisbee">Friso</string>
<string name="activity_type_gliding">vuelo sin motor</string> <string name="activity_type_gliding">vuelo sin motor</string>
<string name="activity_type_hang_gliding">vuelo en ala delta</string> <string name="activity_type_hang_gliding">vuelo en ala delta</string>
<string name="activity_type_helicopter">helicóptero</string> <string name="activity_type_helicopter">helicóptero</string>
@@ -301,14 +284,14 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<!-- Search --> <!-- Search -->
<string name="search_global_description">Recorridos y marcadores</string> <string name="search_global_description">Recorridos y marcadores</string>
<string name="search_list_empty_message">No hay rutas ni marcadores que coincidan con tu búsqueda</string> <string name="search_list_empty_message">No hay rutas ni marcadores que coincidan con tu búsqueda</string>
<string name="search_list_marker_track_location">En la ruta \"%1$s\"</string> <string name="search_list_marker_track_location">En la ruta \"%1$s\".</string>
<!-- Sensor State --> <!-- Sensor State -->
<string name="sensor_state_battery">Nivel de batería</string> <string name="sensor_state_battery">Nivel de batería</string>
<string name="sensor_state_cadence">Cadencia</string> <string name="sensor_state_cadence">Cadencia</string>
<string name="sensor_state_cadence_value">%1$d rpm</string> <string name="sensor_state_cadence_value">%1$d rym</string>
<string name="sensor_state_disconnected">Desconectado</string> <string name="sensor_state_disconnected">Desconectado</string>
<string name="sensor_state_heart_rate">Frecuencia cardíaca</string> <string name="sensor_state_heart_rate">Frecuencia cardíaca</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string> <string name="sensor_state_heart_rate_value">%1$d ppm</string>
<string name="sensor_state_power">Potencia</string> <string name="sensor_state_power">Potencia</string>
<string name="sensor_state_power_value">%1$d W</string> <string name="sensor_state_power_value">%1$d W</string>
<string name="sensor_not_known">Sensor %1$s es desconocido. Por favor, compruebe la configuración.</string> <string name="sensor_not_known">Sensor %1$s es desconocido. Por favor, compruebe la configuración.</string>
@@ -330,15 +313,15 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="settings_recording">Grabación</string> <string name="settings_recording">Grabación</string>
<string name="settings_recording_auto_resume_track_timeout_always_summary">Reanudar siempre una grabación después de reiniciar</string> <string name="settings_recording_auto_resume_track_timeout_always_summary">Reanudar siempre una grabación después de reiniciar</string>
<string name="settings_recording_auto_resume_track_timeout_never_summary">No reanudar nunca una grabación después de reiniciar</string> <string name="settings_recording_auto_resume_track_timeout_never_summary">No reanudar nunca una grabación después de reiniciar</string>
<string name="settings_recording_auto_resume_track_timeout_summary">Esta opción permite reanudar una grabación después de reiniciar si la última ubicación grabada se encuentra dentro de los últimos %1$s.</string> <string name="settings_recording_auto_resume_track_timeout_summary">Esta opción permite reanudar una grabación después de reiniciar si la última ubicación grabada se encuentra dentro de los últimos %1$s</string>
<string name="settings_recording_auto_resume_track_timeout_title">Reanudación automática</string> <string name="settings_recording_auto_resume_track_timeout_title">Reanudación automática</string>
<string name="settings_recording_default_activity_title">Actividad predeterminada</string> <string name="settings_recording_default_activity_title">Actividad predeterminada</string>
<string name="settings_recording_location_frequency_summary">%1$s entre ubicaciones grabadas</string> <string name="settings_recording_location_frequency_summary">%1$s entre ubicaciones grabadas</string>
<string name="settings_recording_max_recording_distance_summary">Esta opción permite crear un nuevo segmento si la distancia entre dos ubicaciones es superior a %1$s.</string> <string name="settings_recording_max_recording_distance_summary">Esta opción permite crear un nuevo segmento si la distancia entre dos ubicaciones es superior a %1$s</string>
<string name="settings_recording_max_recording_distance_title">Distancia máxima</string> <string name="settings_recording_max_recording_distance_title">Distancia máxima</string>
<string name="settings_recording_min_recording_distance_title">Intervalo de distancia</string> <string name="settings_recording_min_recording_distance_title">Intervalo de distancia</string>
<string name="settings_recording_min_recording_interval_title">Intervalo de tiempo</string> <string name="settings_recording_min_recording_interval_title">Intervalo de tiempo</string>
<string name="settings_recording_min_required_accuracy_summary">Esta opción permite ignorar la ubicación si su precisión es inferior a %1$s.</string> <string name="settings_recording_min_required_accuracy_summary">Esta opción permite ignorar la ubicación si su precisión es inferior a %1$s</string>
<string name="settings_recording_min_required_accuracy_title">Exactitud del GPS</string> <string name="settings_recording_min_required_accuracy_title">Exactitud del GPS</string>
<string name="settings_recording_track_name_date_iso_8601_option">Fecha (ISO 8601)</string> <string name="settings_recording_track_name_date_iso_8601_option">Fecha (ISO 8601)</string>
<string name="settings_recording_track_name_date_local_option">Fecha (local)</string> <string name="settings_recording_track_name_date_local_option">Fecha (local)</string>
@@ -351,7 +334,7 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="settings_reset_done">Se han restablecido los valores predeterminados de todas las opciones de configuración</string> <string name="settings_reset_done">Se han restablecido los valores predeterminados de todas las opciones de configuración</string>
<string name="settings_error_initial_values_restored">Lo siento, pero ha habido un error. Todos los ajustes se han vuelto a los valores predeterminados.</string> <string name="settings_error_initial_values_restored">Lo siento, pero ha habido un error. Todos los ajustes se han vuelto a los valores predeterminados.</string>
<!-- Settings Sensor --> <!-- Settings Sensor -->
<string name="settings_sensor">Sensor</string> <string name="settings_sensor">Objetivo</string>
<string name="settings_sensor_bluetooth">Bluetooth</string> <string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string> <string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
<string name="settings_sensor_connecting">Conectado a %1$s</string> <string name="settings_sensor_connecting">Conectado a %1$s</string>
@@ -364,7 +347,9 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="settings_stats_units_title">Unidades preferidas</string> <string name="settings_stats_units_title">Unidades preferidas</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file">Compartir el archivo de la ruta</string> <string name="share_track_share_file">Compartir el archivo de la ruta</string>
<string name="share_track_share_file_body">Creo que esta ruta te podría interesar.\n\nPara ver esta ruta, usa Google Earth u otro software de mapas.\n\n%1$s</string> <string name="share_track_share_file_body">Creo que esta ruta te podría interesar.
\n
\n%1$s</string>
<string name="share_track_subject">Me gustaría compartir una ruta contigo</string> <string name="share_track_subject">Me gustaría compartir una ruta contigo</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">Ritmo de movimiento medio</string> <string name="stats_average_moving_pace">Ritmo de movimiento medio</string>
@@ -512,7 +497,7 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="waypoint_type_convenience_store">tienda de barrio</string> <string name="waypoint_type_convenience_store">tienda de barrio</string>
<string name="waypoint_type_drinking_water">agua potable</string> <string name="waypoint_type_drinking_water">agua potable</string>
<string name="waypoint_type_falling_rocks">desprendimiento de rocas</string> <string name="waypoint_type_falling_rocks">desprendimiento de rocas</string>
<string name="waypoint_type_ferry">ferry</string> <string name="waypoint_type_ferry">barco</string>
<string name="waypoint_type_fire_place">chimenea</string> <string name="waypoint_type_fire_place">chimenea</string>
<string name="waypoint_type_fire_station">parque de bomberos</string> <string name="waypoint_type_fire_station">parque de bomberos</string>
<string name="waypoint_type_fishing">enclave pesquero</string> <string name="waypoint_type_fishing">enclave pesquero</string>
@@ -574,13 +559,13 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="settings_night_mode_option_day">Claro</string> <string name="settings_night_mode_option_day">Claro</string>
<string name="settings_night_mode_option_night">Oscuro</string> <string name="settings_night_mode_option_night">Oscuro</string>
<string name="settings_night_mode_title">Elegir un tema</string> <string name="settings_night_mode_title">Elegir un tema</string>
<string name="settings_sensor_wheel_circumference">Circunferencia de rueda (mm)</string> <string name="settings_sensor_wheel_circumference">Perímetro de la rueda (mm)</string>
<string name="stats_intervals">Intervalos</string> <string name="stats_intervals">Intervalos</string>
<string name="stats_split_interval">Intervalo</string> <string name="stats_split_interval">Intervalo</string>
<string name="track_detail_intervals_tab">Intervalos</string> <string name="track_detail_intervals_tab">Intervalos</string>
<string name="aggregated_stats_empty_message">Graba tu primera pista para ver estadísticas agregadas</string> <string name="aggregated_stats_empty_message">Graba tu primera pista para ver estadísticas agregadas</string>
<string name="voice_pace_lap">Tiempo de la vuelta %1$s</string> <string name="voice_pace_lap">Tiempo de la vuelta %1$s</string>
<string name="hold_to_pause">Mantén pulsado para pausar la grabación</string> <string name="hold_to_pause">Mantener en pausa</string>
<string name="instant_export_enabled_title">Exportación automática tras grabación</string> <string name="instant_export_enabled_title">Exportación automática tras grabación</string>
<string name="settings_default_export_uri_title">Carpeta de pistas exportadas</string> <string name="settings_default_export_uri_title">Carpeta de pistas exportadas</string>
<string name="open_track_using_dashboard">Abrir con OpenTracks-Dashboard</string> <string name="open_track_using_dashboard">Abrir con OpenTracks-Dashboard</string>
@@ -596,7 +581,31 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Pantalla completa</string> <string name="settings_recording_fullscreen_on_while_recording_title">Pantalla completa</string>
<string name="interval_list_empty_message">No hay intervalos todavía</string> <string name="interval_list_empty_message">No hay intervalos todavía</string>
<string name="voice_speed_lap">Velocidad de la vuelta %1$s</string> <string name="voice_speed_lap">Velocidad de la vuelta %1$s</string>
<string name="hold_to_stop">Mantén pulsado para terminar la grabación</string> <string name="hold_to_stop">Mantener para parar</string>
<string name="toast_message_instant_track_was_exported">Última pista exportada</string> <string name="toast_message_instant_track_was_exported">Última pista exportada</string>
<string name="instant_export_enabled_summary">Exportar pista después de terminar la grabación</string> <string name="instant_export_enabled_summary">Exportar pista después de terminar la grabación</string>
<string name="stats_loss">Pérdida</string>
<string name="settings_prevent_reimport_tracks_title">Impedir la reimportación de pistas</string>
<string name="sensor_state_power_avg">Potencia media</string>
<string name="sensor_state_heart_rate_max">Frecuencia cardíaca máxima</string>
<string name="sensor_state_heart_rate_avg">Ritmo cardíaco medio</string>
<string name="export_error_post_workout">La exportación de post-entrenamiento ha fallado, por favor compruebe el directorio de exportación.</string>
<string name="export_progress_summary_new_msg">Nuevas pistas exportadas</string>
<string name="import_thread_interrupted">Hilo interrumpido</string>
<string name="import_progress_summary_errors_msg">Errores</string>
<string name="import_progress_summary_ok_msg">Archivos importados</string>
<string name="description_elevation_loss">Pérdida de altura: %1$d m (%2$d pies)</string>
<string name="value_float_mile_hour">%1$.2f mph</string>
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
<string name="sensor_state_cadence_max">Cadencia máxima</string>
<string name="sensor_state_cadence_avg">Cadencia media</string>
<string name="value_float_kilometer_hour">%1$.2f kp/h</string>
<string name="export_progress_summary_overwrite_msg">Pistas sobrescritas</string>
<string name="export_progress_summary_skip_msg">Pistas omitidas</string>
<string name="export_progress_summary_errors_msg">Errores</string>
<string name="generic_open_track">Pista abierta</string>
<string name="import_progress_summary_exists_msg">Ya existen</string>
<string name="import_no_kml_file_found">No se ha encontrado ningún archivo doc.kml en KMZ</string>
<string name="import_prevent_reimport">Ha establecido impedir la reimportación de la pista</string>
</resources> </resources>
+109 -63
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc. Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> --><resources>
<resources>
<string name="about_preference_title">Umbes OpenTracks</string> <string name="about_preference_title">Umbes OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> on spordi jälgimise rakendus, mis austab täielikult teie privaatsust.</string> <string name="about_description"><i>OpenTracks</i> on spordi jälgimise rakendus, mis austab täielikult teie privaatsust.</string>
<string name="about_url">Projekti veebileht: <a href="%1$s">%1$s</a></string> <string name="about_url">Projekti veebileht: <a href="%1$s">%1$s</a></string>
@@ -23,69 +23,45 @@ limitations under the License.
<string name="about_libraries">Raamatukogud</string> <string name="about_libraries">Raamatukogud</string>
<!-- Help --> <!-- Help -->
<string name="help_feature_title">OpenTracks sellel on järgmised omadused:</string> <string name="help_feature_title">OpenTracks sellel on järgmised omadused:</string>
<string name="help_feature_content"> <string name="help_feature_content">* loo GPS rajad
* GPS-radade loomine \ n \n* kasutage südame löögisageduse andurit ( ainult Bluetooth LE)
* kasutage pulsisensorit (ainult Bluetooth LE) \ n \n* häälteated (aja või vahemaa järgi)
* häälteated (aja või vahemaa järgi) \ n \n* loo markerid (sh. digikuvat)
* loo markerid (koos piltidega) \ n \n* töötab täiesti offline
* töötab täiesti võrguühenduseta \ n \n* näita lugusid kaardil (vt allpool)</string>
* näidata radu kaardil (vt allpool)</string>
<string name="help_track_map_title">Kuidas näidata pala kaardil?</string> <string name="help_track_map_title">Kuidas näidata pala kaardil?</string>
<string name="help_track_map_content"> <string name="help_track_map_content"> Kasutage väikest kaardisümbolit.
Kasutage väikest kaardiikooni. \n \n a) (reaalajas uuendused) Installige <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. See pakub online või offline-kaarte <a href="https://www.openstreetmap.org">OpenStreetMap</a> kaudu.
a) \n Tegelikult on OSMDashboard eraldi rakendus, mis saab ainult (ajutiselt) juurdepääsu näidatavate rada(de) andmetele.
(reaalajas värskendused) Installige <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. \n b) Tavalise kaardirakenduse kaudu (nt <a href="http://osmand.net/">OsmAnd</a> ja <a href="https://maps.me/">MAPS.ME</a>). Need rakendused peavad toetama KMZ-failiformaati (ainult asukohad ja ajatemplid). Tegelikult peab rakendus toetama <i>ACTION_VIEW</i> koos MIME <i>application/vnd.google-earth.kmz</i>-ga. </string>
See pakub võrgu- või võrguühenduseta kaarte <a href="https://www.openstreetmap.org">OpenStreetMap</a> kaudu.\n
Tegelikult on OSMDashboard eraldiseisev rakendus, mis pääseb juurde (ajutiselt) ainult kuvatavate rada(de) andmetele.
\n
b) „Tavalise” kaardirakenduse (nt <a href="http://osmand.net/">OsmAnd</a> ja <a href="https://maps.me/">MAPS.ME</a>.
Need rakendused peavad toetama KMZ-failivormingut (ainult asukohad ja ajatemplid).
Tegelikult peab rakendus toetama rakendust MIME <i>application/vnd.google-earth.</i> <i>ACTION_VIEW</i>.</string>
<string name="help_storage_pictures_title">Kuidas OpenTracks salvestab markeritena tehtud pilte?</string> <string name="help_storage_pictures_title">Kuidas OpenTracks salvestab markeritena tehtud pilte?</string>
<string name="help_storage_pictures_content">Neid hoitakse rakenduse sisemises salvestusruumis (st pääseb juurde ainult OpenTracks is).</string> <string name="help_storage_pictures_content">Neid hoitakse rakenduse sisemises salvestusruumis (st pääseb juurde ainult OpenTracks is).</string>
<string name="help_track_export_title">Kuidas saab salvestatud lugusid eksportida / jagada?</string> <string name="help_track_export_title">Kuidas saab salvestatud lugusid eksportida / jagada?</string>
<string name="help_track_export_content"> <string name="help_track_export_content"> Lugusid saab seadetest eksportida KMZ-, KML- või GPX-vormingus. KMZ / KML kõik andmed; Ainult GPX-i asukohad. Radasid, mis on jagatud <i> jagamise ikooni </i> kaudu, jagatakse KMZ-na. </string>
Lugusid saab seadetest eksportida KMZ-, KML- või GPX-vormingus.
KMZ / KML kõik andmed; Ainult GPX-i asukohad.
Radasid, mis on jagatud <i> jagamise ikooni </i> kaudu, jagatakse KMZ-na.</string>
<string name="help_data_google_play_services_title">Kas OpenTracks nõuab / kasutab Google Play teenust?</string> <string name="help_data_google_play_services_title">Kas OpenTracks nõuab / kasutab Google Play teenust?</string>
<string name="help_recording_title">Kuidas OpenTracks andmeid registreerib?</string> <string name="help_recording_title">Kuidas OpenTracks andmeid registreerib?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks kasutab seadme GPS-andurit asukoha pidevaks registreerimiseks. OpenTracks kasutab seadme GPS-andurit asukoha pidevaks registreerimiseks.
Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei saa OpenTracks sellega midagi teha.</string> Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei saa OpenTracks sellega midagi teha.</string>
<string name="help_data_storage_title">Kuidas OpenTracks sensori andmeid salvestab?</string> <string name="help_data_storage_title">Kuidas OpenTracks sensori andmeid salvestab?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content"> Anduri andmed salvestatakse salvestatud asukoha kohta. Südame löögisageduse anduri puhul tähendab see, et asukoha kohta salvestatakse ainult <i>üks</i> südame löögisageduse mõõtmine. Kui seisate paigal või siseruumides, siis anduri andmeid ei registreerita (kuid need näidatakse). </string>
Anduri andmed salvestatakse salvestatud asukoha kohta.
Südame löögisageduse anduri puhul tähendab see, et asukoha kohta salvestatakse ainult <i>üks</i> südame löögisageduse mõõtmine.
Kui seisate paigal või siseruumides, siis anduri andmeid ei registreerita (kuid need näidatakse).</string>
<string name="help_gps_troubleshooting_title">OpenTracks ei salvesta asukohti?</string> <string name="help_gps_troubleshooting_title">OpenTracks ei salvesta asukohti?</string>
<string name="help_gps_troubleshooting_content"> <string name="help_gps_troubleshooting_content"> Mida saate teha:
Mida saate teha: \ n \na) kontrollige, kas GPS on lubatud,
a) kontrollige, kas GPS on lubatud, \ n \nb) parandada GPS vastuvõtt (NT minna õues),
b) parandage GPS-vastuvõttu (nt käige õues), \ n \nc) kontrollige <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid /">GPSTest</a> app ja
c) kontrollige, kas rakendusega <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> ja \ n \nd) keelata energiasäästu.
d) keelake energiasääst. \ n
\n \n
Kui kõik ei aita, teatage veast.</string> \nKui kõik ei aita, palun teatage veast. </string>
<string name="help_support_title">Kuidas OpenTracks it toetada?</string> <string name="help_support_title">Kuidas OpenTracks it toetada?</string>
<string name="help_support_content"> <string name="help_support_content"> Saate:
Saate: \ n \n a) levitada OpenTracks\'i kohta sõna,
a) levitage sõna OpenTracks kohta, \ n \n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ettepanekuid teha</a>,
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> funktsioonide pakkumine </a>, \ n \n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">raportida vigadest</a>,
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> teatage vigadest </a>, \ n \n d) aidata tarkvara arendamisel,
d) abi tarkvara arendamisel, \ n \n e) osta <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store\'is</a>,
e) ostke sisse <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play pood </a>, \ n \n f) annetada ja
f) annetama ja \ n \n g) teha kõike, mis teie arvates aitab OpenTracks\'ile kaasa. </string>
g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<!-- Activity Type --> <!-- Activity Type -->
<string name="activity_type_airplane">lennuk</string> <string name="activity_type_airplane">lennuk</string>
<string name="activity_type_atv">ATV</string> <string name="activity_type_atv">ATV</string>
@@ -147,7 +123,7 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<string name="activity_type_trail_hiking">matkarajal matkamine</string> <string name="activity_type_trail_hiking">matkarajal matkamine</string>
<string name="activity_type_trail_running">maastikujooks</string> <string name="activity_type_trail_running">maastikujooks</string>
<string name="activity_type_train">rong</string> <string name="activity_type_train">rong</string>
<string name="activity_type_ultimate_frisbee">ultimate frisbee</string> <string name="activity_type_ultimate_frisbee">ülim frisbee</string>
<string name="activity_type_unknown">tundmatu</string> <string name="activity_type_unknown">tundmatu</string>
<string name="activity_type_wakeboarding">veelauaga sõitmine</string> <string name="activity_type_wakeboarding">veelauaga sõitmine</string>
<string name="activity_type_walking">kõndimine</string> <string name="activity_type_walking">kõndimine</string>
@@ -239,7 +215,7 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<!-- Image --> <!-- Image -->
<string name="image_application">Rakendus</string> <string name="image_application">Rakendus</string>
<string name="image_arrow">Nool</string> <string name="image_arrow">Nool</string>
<string name="image_marker">Marker</string> <string name="image_marker">Mark</string>
<string name="image_marker_count">Markerite arv</string> <string name="image_marker_count">Markerite arv</string>
<string name="image_my_location">Minu asukoht</string> <string name="image_my_location">Minu asukoht</string>
<string name="image_pause">Peata</string> <string name="image_pause">Peata</string>
@@ -267,7 +243,7 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<string name="marker_delete_multiple_confirm_message">Valitud markerid kustutatakse seadmest jäädavalt</string> <string name="marker_delete_multiple_confirm_message">Valitud markerid kustutatakse seadmest jäädavalt</string>
<string name="marker_label_end">%1$s (Lõpp)</string> <string name="marker_label_end">%1$s (Lõpp)</string>
<string name="marker_label_start">%1$s (Algus)</string> <string name="marker_label_start">%1$s (Algus)</string>
<string name="marker_name_format">Marker %1$d</string> <string name="marker_name_format">Mark %1$d</string>
<string name="marker_split_name_format">Jaotus %1$d</string> <string name="marker_split_name_format">Jaotus %1$d</string>
<string name="marker_title">%1$d/%2$d</string> <string name="marker_title">%1$d/%2$d</string>
<!-- Marker Detail --> <!-- Marker Detail -->
@@ -311,7 +287,7 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<!-- Sensor State --> <!-- Sensor State -->
<string name="sensor_state_battery">Aku tase</string> <string name="sensor_state_battery">Aku tase</string>
<string name="sensor_state_cadence">Rütm</string> <string name="sensor_state_cadence">Rütm</string>
<string name="sensor_state_cadence_value">%1$d rpm</string> <string name="sensor_state_cadence_value">%1$d p/min</string>
<string name="sensor_state_disconnected">Ühendus on katkestatud</string> <string name="sensor_state_disconnected">Ühendus on katkestatud</string>
<string name="sensor_state_heart_rate">Pulsisagedus</string> <string name="sensor_state_heart_rate">Pulsisagedus</string>
<string name="sensor_state_heart_rate_value">%1$d lööki minutis</string> <string name="sensor_state_heart_rate_value">%1$d lööki minutis</string>
@@ -340,7 +316,7 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<string name="settings_recording_auto_resume_track_timeout_title">Automaatne jätkamine</string> <string name="settings_recording_auto_resume_track_timeout_title">Automaatne jätkamine</string>
<string name="settings_recording_default_activity_title">Tegevuse vaiketüüp</string> <string name="settings_recording_default_activity_title">Tegevuse vaiketüüp</string>
<string name="settings_recording_location_frequency_summary">%1$s salvestatud asukohtade vahel</string> <string name="settings_recording_location_frequency_summary">%1$s salvestatud asukohtade vahel</string>
<string name="settings_recording_max_recording_distance_summary">Kui vahemaa kahe asukoha vahel on üle %1$s, luuakse uus lõik.</string> <string name="settings_recording_max_recording_distance_summary">Kui vahemaa kahe asukoha vahel on üle %1$s, luuakse uus lõik</string>
<string name="settings_recording_max_recording_distance_title">Max vahemaa</string> <string name="settings_recording_max_recording_distance_title">Max vahemaa</string>
<string name="settings_recording_min_recording_distance_title">Salvestamiskauguse intervall</string> <string name="settings_recording_min_recording_distance_title">Salvestamiskauguse intervall</string>
<string name="settings_recording_min_recording_interval_title">Salvestamisaja intervall</string> <string name="settings_recording_min_recording_interval_title">Salvestamisaja intervall</string>
@@ -362,17 +338,17 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<string name="settings_sensor_bluetooth_sensor">Bluetoothi andur</string> <string name="settings_sensor_bluetooth_sensor">Bluetoothi andur</string>
<string name="settings_sensor_connecting">Ühendatud anduriga %1$s</string> <string name="settings_sensor_connecting">Ühendatud anduriga %1$s</string>
<string name="settings_sensor_disconnected">Lahti ühendatud %1$s</string> <string name="settings_sensor_disconnected">Lahti ühendatud %1$s</string>
<!-- Settings Stats --> <!-- Settings Stats -->
<string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</string> <string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</string>
<string name="settings_stats_rate_title">Eelistatud määr</string> <string name="settings_stats_rate_title">Eelistatud määr</string>
<string name="settings_stats_units_imperial">Inglise mõõtühikud (miili, jalga)</string> <string name="settings_stats_units_imperial">Inglise mõõtühikud (miili, jalga)</string>
<string name="settings_stats_units_metric">Meetermõõdustik (km, m)</string> <string name="settings_stats_units_metric">Meetermõõdustik (km, m)</string>
<string name="settings_stats_units_title">Eelistatud ühikud</string> <string name="settings_stats_units_title">Eelistatud ühikud</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file">Jagage rajafaili</string> <string name="share_track_share_file">Jagage rajafaili</string>
<string name="share_track_share_file_body">Teile võib see rada huvi pakkuda.\n\nRaja vaatamiseks kasutage rakendust Google Earth või muud kaardistamistarkvara.\n\n%1$s</string> <string name="share_track_share_file_body">Teile võib see rada huvi pakkuda.
\n
\n%1$s</string>
<string name="share_track_subject">Ma soovin teiega rada jagada</string> <string name="share_track_subject">Ma soovin teiega rada jagada</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">Keskmine liikumistempo</string> <string name="stats_average_moving_pace">Keskmine liikumistempo</string>
@@ -553,12 +529,82 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<string name="waypoint_type_toilet">tualett</string> <string name="waypoint_type_toilet">tualett</string>
<string name="waypoint_type_tourist_information">turismiinfo</string> <string name="waypoint_type_tourist_information">turismiinfo</string>
<string name="waypoint_type_tree">puu</string> <string name="waypoint_type_tree">puu</string>
<string name="waypoint_type_tunnel">tunnel</string> <string name="waypoint_type_tunnel">tunnelit</string>
<string name="waypoint_type_waterfall">juga</string> <string name="waypoint_type_waterfall">juga</string>
<string name="waypoint_type_water_fountain">purskkaev</string> <string name="waypoint_type_water_fountain">purskkaev</string>
<string name="waypoint_type_waypoint">teekonnapunkt</string> <string name="waypoint_type_waypoint">teekonnapunkt</string>
<!-- Share image --> <!-- Share image -->
<string name="share_image_subject">Tahaksin teiega pilti jagada</string> <string name="share_image_subject">Tahaksin teiega pilti jagada</string>
<string name="share_image_body">Ma arvan, et see pilt võiks teid huvitada.</string> <string name="share_image_body">Ma arvan, et see pilt võiks teid huvitada.</string>
<string name="open_track_as_trackfileformat">Ava kui %1$s</string>
<string name="open_track_using_dashboard">Open kasutades OpenTracks-Dashboard</string>
<string name="activity_type_escooter">eskuuter</string>
<string name="open_track_as_GPX">Avatud kui GPX</string>
<string name="open_track_as_KMZ">Avatud kui KMZ</string>
<string name="description_elevation_loss">Kõrguse kadu: %1$d m (%2$d ft)</string>
<string name="export_all_do_it_for_all">tehke seda kõigi konfliktide jaoks</string>
<string name="export_error_post_workout">Treeningujärgne eksport nurjus, palun kontrolli ekspordikataloogi.</string>
<string name="import_thread_interrupted">Teema katkestatud</string>
<string name="import_parser_error">Parseri viga: %1$s</string>
<string name="import_unable_to_import_file">Faili importimine nurjus: %1$s</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Lõpetatud ühe veaga</item>
<item quantity="other">Lõpetatud %1$d vigadega</item>
</plurals>
<string name="gps_fixed_and_ready">GPS fikseeritud ja valmis</string>
<string name="import_prevent_reimport">Olete määranud vältida re-import track</string>
<string name="generic_overwrite">Ülekirjutamine</string>
<string name="export_progress_summary_overwrite_msg">Ülekirjutatud lood</string>
<string name="export_track_errors">Palasid ei ekspordita</string>
<string name="export_track_already_exists_msg">Rada %1$s on sihtkataloogis juba olemas.</string>
<string name="aggregated_stats_empty_message">Salvestage oma esimene pala, et näha koondatud statistikat</string>
<string name="toast_message_instant_track_was_exported">Viimane eksporditud pala</string>
<string name="voice_pace_lap">Ringiaeg %1$s</string>
<string name="settings_prevent_reimport_tracks_title">Palade reimportimise vältimine</string>
<string name="settings_night_mode_option_night">Öö</string>
<string name="settings_night_mode_option_day">Päev</string>
<string name="settings_night_mode_option_system">Süsteem</string>
<string name="settings_night_mode_title">UI teema</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Salvestamise ajal esitada täisekraanil.</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Täisekraan</string>
<string name="settings_default_trackfileformat">Failivormingu eksport/jagamine</string>
<string name="sensor_state_power_avg">Avg Võimsus</string>
<string name="sensor_state_heart_rate_max">Maksimaalne pulss</string>
<string name="sensor_state_heart_rate_avg">Keskmine pulsisagedus</string>
<string name="sensor_state_cadence_max">Max kadentsus</string>
<string name="sensor_state_cadence_avg">Keskmine kadentsus</string>
<string name="settings_sensor_wheel_circumference">Ratta ümbermõõt (mm)</string>
<string name="stats_loss">Kahjum</string>
<string name="track_detail_intervals_tab">Intervallid</string>
<string name="stats_split_interval">Jagatud intervall</string>
<string name="stats_intervals">Intervallid</string>
<string name="interval_list_empty_message">Ei ole ühtegi intervalli veel</string>
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
<string name="value_float_mile_hour">%1$.2f mph</string>
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
<string name="voice_speed_lap">Ringi kiirus %1$s</string>
<string name="hold_to_stop">Hoidke lõpetada</string>
<string name="hold_to_pause">Hoidke pausi</string>
<string name="instant_export_enabled_summary">Ekspordi pala salvestusruumi pärast salvestamise lõpetamist</string>
<string name="settings_default_export_uri_title">Track ekspordi kataloog</string>
<string name="instant_export_enabled_title">Vahetu treeningujärgne eksport</string>
<string name="export_progress_summary_errors_msg">Viga</string>
<string name="export_progress_summary_skip_msg">Palad vahele</string>
<string name="export_progress_summary_new_msg">Uued palad eksporditud</string>
<string name="generic_open_track">Avatud rada</string>
<string name="generic_completed">Lõpetanud</string>
<string name="generic_click_twice_cancel">Tühistamiseks klõpsake uuesti</string>
<string name="gps_disabled_msg">GPS keelatud</string>
<string name="import_unsupported_format">Toetamata failivorming</string>
<string name="import_no_kml_file_found">Ei, doktor.KMZ-is leitud KML fail</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="import_error_list_dialog_title">Failid, mida ei impordita</string>
<string name="import_file_imported">Fail %1$s imporditud</string>
<string name="import_progress_summary_ok_msg">Imporditud failid</string>
<string name="import_progress_summary_exists_msg">Juba olemas</string>
<string name="generic_show_errors">Vigade Näitamine</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_skip">Jäta</string>
<string name="import_progress_summary_errors_msg">Viga</string>
</resources> </resources>
+106 -54
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc. Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,13 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> --><resources>
<resources>
<string name="about_preference_title">OpenTracks-i buruz</string> <string name="about_preference_title">OpenTracks-i buruz</string>
<string name="about_description"><i> OpenTracks </i> kirol jarraipena egiteko aplikazioa da, zure pribatutasuna erabat errespetatzen duena.</string> <string name="about_description"><i> OpenTracks </i> kirol jarraipena egiteko aplikazioa da, zure pribatutasuna erabat errespetatzen duena.</string>
<string name="about_url">Proiektuaren web orria: <a href="%1$s">%1$s</a></string> <string name="about_url">Proiektuaren web orria: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Bertsioaren izena: %1$s</string> <string name="about_version_name">Bertsioaren izena: %1$s</string>
<string name="about_version_code">Bertsio kodea: %1$d</string> <string name="about_version_code">Bertsio kodea: %1$d</string>
<string name="about_history">OpenTracks <a href="https://code.google.com/archive/p/mytracks/"> <i> Google \ n MyTracks </i> </a> (horixe utzi zen 2016an).</string> <string name="about_history">OpenTracks <a href="https://code.google.com/archive/p/mytracks/"> <i> Google n MyTracks </i> </a> (horixe utzi zen 2016an).</string>
<string name="about_libraries">Liburutegiak</string> <string name="about_libraries">Liburutegiak</string>
<!-- Help --> <!-- Help -->
<string name="help_feature_title">OpenTracks ek ezaugarri hauek ditu:</string> <string name="help_feature_title">OpenTracks ek ezaugarri hauek ditu:</string>
@@ -30,60 +30,39 @@ limitations under the License.
* Sortu markatzaileak (irudiak barne)\n * Sortu markatzaileak (irudiak barne)\n
* guztiz lineaz kanpo funtzionatzen du\n * guztiz lineaz kanpo funtzionatzen du\n
* Erakutsi ibilbideak mapa batean (ikusi azpian)</string> * Erakutsi ibilbideak mapa batean (ikusi azpian)</string>
<string name="help_track_map_title">Nola erakutsi pista batean mapa batean?</string> <string name="help_track_map_title">Nola erakutsi pista batean mapa batean?</string>
<string name="help_track_map_content"> <string name="help_track_map_content"> Erabili maparen ikono txikia.
Erabili maparen ikono txikia.\n \n a) (zuzeneko eguneratzeak) Instalatu <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Lineako edo lineaz kanpoko mapak eskaintzen ditu <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
a) (zuzeneko eguneratzeak) Instalatu <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. \n Egia esan, OSMDashboard erakundea da erakutsiko diren pista (k) datuetara sartzeko soilik (aldi baterako) sar daitekeena.
Lineako edo lineaz kanpoko mapak eskaintzen ditu <a href="https://www.openstreetmap.org">OpenStreetMap</a>.\n \n b) Mapa aplikazio normal baten bidez (adibidez, <a href="http://osmand.net/">OsmAnd</a> eta <a href="https://maps.me/">MAPS.ME</a>). Aplikazio horiek KMZ fitxategi formatua onartzen dute (kokapenak eta timestamps soilik). Izan ere, aplikazio batek <i>ACTION_VIEW</i> onartzen du MIME <i>application/vnd.google-earth.kmz</i> rekin. </string>
Egia esan, OSMDashboard erakundea da erakutsiko diren pista (k) datuetara sartzeko soilik (aldi baterako) sar daitekeena.\n
b) Mapa aplikazio normal baten bidez (adibidez, <a href="http://osmand.net/">OsmAnd</a> eta <a href="https://maps.me/">MAPS.ME</a>).
Aplikazio horiek KMZ fitxategi formatua onartzen dute (kokapenak eta timestamps soilik).
Izan ere, aplikazio batek <i>ACTION_VIEW</i> onartzen du MIME <i>application/vnd.google-earth.kmz</i> rekin.</string>
<string name="help_storage_pictures_title">Nola gordetzen du OpenTracks markatzaile gisa ateratako argazkiak?</string> <string name="help_storage_pictures_title">Nola gordetzen du OpenTracks markatzaile gisa ateratako argazkiak?</string>
<string name="help_storage_pictures_content">Aplikazioaren barne biltegian gordetzen dira (hau da, OpenTracks en soilik eskura daiteke).</string> <string name="help_storage_pictures_content">Aplikazioaren barne biltegian gordetzen dira (hau da, OpenTracks en soilik eskura daiteke).</string>
<string name="help_track_export_title">Nola esportatu/berdinak diren ibilbideak grabatutako ibilbideak?</string> <string name="help_track_export_title">Nola esportatu/berdinak diren ibilbideak grabatutako ibilbideak?</string>
<string name="help_track_export_content"> <string name="help_track_export_content"> Pistak Ezarpenetatik KMZ, KML edo GPX gisa esportatu daitezke. KMZ / KML datu guztiak; GPX kokapenak soilik. <i> Partekatutako ikonoa </i> bidez partekatutako ibilbideak KMZ gisa partekatzen dira. </string>
Pistak Ezarpenetatik KMZ, KML edo GPX gisa esportatu daitezke.
KMZ / KML datu guztiak; GPX kokapenak soilik.
<i> Partekatutako ikonoa </i> bidez partekatutako ibilbideak KMZ gisa partekatzen dira.</string>
<string name="help_data_google_play_services_title">OpenTracks-ek Google Play zerbitzua erabiltzea eskatzen al du?</string> <string name="help_data_google_play_services_title">OpenTracks-ek Google Play zerbitzua erabiltzea eskatzen al du?</string>
<string name="help_recording_title">Nola grabatzen ditu OpenTracks datuak?</string> <string name="help_recording_title">Nola grabatzen ditu OpenTracks datuak?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks gailuaren GPS sentsorea erabiltzen du kokapena etengabe grabatzeko. OpenTracks gailuaren GPS sentsorea erabiltzen du kokapena etengabe grabatzeko.
GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), OpenTracks-ek ezin du ezer egin.</string> GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), OpenTracks-ek ezin du ezer egin.</string>
<string name="help_data_storage_title">Nola gordetzen du OpenTracks ek sentsoreen datuak?</string> <string name="help_data_storage_title">Nola gordetzen du OpenTracks ek sentsoreen datuak?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content"> Sentsoreen datuak grabatutako kokapen bakoitzeko gordetzen dira. Bihotz-maiztasuneko sentsoreari dagokionez, horrek esan nahi du <i> bat </i> bihotz-tasaren neurketa kokapen bakoitzean gordetzen dela. Geldirik egon edo barrualdean bazaude, ez da sentsoreen daturik grabatuko (baina erakutsiko da). </string>
Sentsoreen datuak grabatutako kokapen bakoitzeko gordetzen dira.
Bihotz-maiztasuneko sentsoreari dagokionez, horrek esan nahi du <i> bat </i> bihotz-tasaren neurketa kokapen bakoitzean gordetzen dela.
Geldirik egon edo barrualdean bazaude, ez da sentsoreen daturik grabatuko (baina erakutsiko da).</string>
<string name="help_gps_troubleshooting_title">OpenTracks ez dira kokapenak grabatzen?</string> <string name="help_gps_troubleshooting_title">OpenTracks ez dira kokapenak grabatzen?</string>
<string name="help_gps_troubleshooting_content"> <string name="help_gps_troubleshooting_content"> Zer egin dezakezu:
Zer egin dezakezu: \n \n a) egiaztatu GPSa gaituta dagoen ala ez,
a) egiaztatu GPSa gaituta dagoen ala ez, \n \n b) GPS harrera hobetu (adibidez, kanpora joan),
b) GPS harrera hobetu (adibidez, kanpora joan), \n \n c) egiaztatu <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> aplikazioarekin eta
c) egiaztatu <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> aplikazioarekin eta \n \n d) desgaitu energia aurreztea.
d) desgaitu energia aurreztea. \n
\n \n
Dena laguntzen ez baduzu, jakinarazi akats bat.</string> \n Dena laguntzen ez baduzu, jakinarazi akats bat. </string>
<string name="help_support_title">Nola onartzen dut OpenTracks?</string> <string name="help_support_title">Nola onartzen dut OpenTracks?</string>
<string name="help_support_content"> <string name="help_support_content"> Ahal izango duzu:
Aukera hau: \ n \n a) buruz hitz zabaltzeko OpenTracks,
a) zabaldu OpenTracks-i buruzko hitza, \ n \n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">proposatzen ezaugarriak</a>,
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> proposatu ezaugarriak </a>, \ n \n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">txostena bugs</a>,
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> txostenen akatsak </a>, \ n \n d) laguntza software garapena,
d) softwarearen garapenean laguntzeko, \ n \n e) erosi <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Denda</a>,
e) erosi <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play Store </a>, \ n \n f) eman, eta
f) eman, eta \ n \n g) ez edozein dela uste duzu laguntzen OpenTracks. </string>
g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<!-- Activity Type --> <!-- Activity Type -->
<string name="activity_type_airplane">hegazkina</string> <string name="activity_type_airplane">hegazkina</string>
<string name="activity_type_atv">Lurreko ibilgailu guztiak</string> <string name="activity_type_atv">Lurreko ibilgailu guztiak</string>
@@ -257,7 +236,7 @@ g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<!-- Marker --> <!-- Marker -->
<string name="marker_add_error">Ezin da markatzailea txertatu. Ez dago GPS seinalerik. Saiatu berriro.</string> <string name="marker_add_error">Ezin da markatzailea txertatu. Ez dago GPS seinalerik. Saiatu berriro.</string>
<string name="marker_add_success">Markatzailea txertatu da</string> <string name="marker_add_success">Markatzailea txertatu da</string>
<string name="marker_add_canceled">Bertan behera utzi da; ez da markatzailea gehitu.</string> <string name="marker_add_canceled">Bertan behera utzi da; ez da markatzailea gehitu</string>
<string name="marker_add_photo_canceled">Ezeztatu, ez da argazki-markarik gehitu</string> <string name="marker_add_photo_canceled">Ezeztatu, ez da argazki-markarik gehitu</string>
<string name="marker_delete_all_confirm_message">Ibilbideko markatzaile guztiak behin betiko ezabatuko dira gailutik</string> <string name="marker_delete_all_confirm_message">Ibilbideko markatzaile guztiak behin betiko ezabatuko dira gailutik</string>
<string name="marker_delete_one_confirm_message">Markatzailea behin betiko ezabatuko da gailutik</string> <string name="marker_delete_one_confirm_message">Markatzailea behin betiko ezabatuko da gailutik</string>
@@ -309,16 +288,16 @@ g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<!-- Sensor State --> <!-- Sensor State -->
<string name="sensor_state_battery">Bateria maila</string> <string name="sensor_state_battery">Bateria maila</string>
<string name="sensor_state_cadence">Erritmoa</string> <string name="sensor_state_cadence">Erritmoa</string>
<string name="sensor_state_cadence_value">%1$d rpm</string> <string name="sensor_state_cadence_value">%1$d mbi</string>
<string name="sensor_state_disconnected">Deskonektatuta</string> <string name="sensor_state_disconnected">Deskonektatuta</string>
<string name="sensor_state_heart_rate">Maiztasun kardiakoa</string> <string name="sensor_state_heart_rate">Maiztasun kardiakoa</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string> <string name="sensor_state_heart_rate_value">%1$d mbb</string>
<string name="sensor_state_power">Potentzia</string> <string name="sensor_state_power">Potentzia</string>
<string name="sensor_state_power_value">%1$d W</string> <string name="sensor_state_power_value">%1$d W</string>
<string name="sensor_not_known">Sensor %1$s ezezaguna da. Egiaztatu ezarpenak.</string> <string name="sensor_not_known">Sensor %1$s ezezaguna da. Egiaztatu ezarpenak.</string>
<string name="sensor_could_not_scan">Ezin izan da Bluetooth gailuak bilatu (%1$i errorea).</string> <string name="sensor_could_not_scan">Ezin izan da Bluetooth gailuak bilatu (%1$i errorea).</string>
<string name="bluetooth_disabled">Mesedez, aktibatu Bluetooth.</string> <string name="bluetooth_disabled">Mesedez, aktibatu Bluetooth.</string>
<string name="settings_not_while_recording">Ez dago erabilgarri ibilbidea grabatzen ari den bitartean.</string> <string name="settings_not_while_recording">Ez dago erabilgarri ibilbidea grabatzen ari den bitartean</string>
<string name="settings_show">Erakutsi</string> <string name="settings_show">Erakutsi</string>
<!-- Settings Advanced --> <!-- Settings Advanced -->
<string name="settings_advanced">Aurreratua</string> <string name="settings_advanced">Aurreratua</string>
@@ -360,17 +339,17 @@ g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<string name="settings_sensor_bluetooth_sensor">Bluetooth sentsorea</string> <string name="settings_sensor_bluetooth_sensor">Bluetooth sentsorea</string>
<string name="settings_sensor_connecting">Honekin konektatuta: %1$s</string> <string name="settings_sensor_connecting">Honekin konektatuta: %1$s</string>
<string name="settings_sensor_disconnected">Deskonektatuta %1$s</string> <string name="settings_sensor_disconnected">Deskonektatuta %1$s</string>
<!-- Settings Stats --> <!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitudea/Longitudea</string> <string name="settings_stats_coordinate">Latitudea/Longitudea</string>
<string name="settings_stats_rate_title">Erritmo hobetsia</string> <string name="settings_stats_rate_title">Erritmo hobetsia</string>
<string name="settings_stats_units_imperial">Inperiala (mi, ft)</string> <string name="settings_stats_units_imperial">Inperiala (mi, ft)</string>
<string name="settings_stats_units_metric">Metrikoa (km, m)</string> <string name="settings_stats_units_metric">Metrikoa (km, m)</string>
<string name="settings_stats_units_title">Unitate hobetsiak</string> <string name="settings_stats_units_title">Unitate hobetsiak</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file">Partekatu ibilbide-fitxategia</string> <string name="share_track_share_file">Partekatu ibilbide-fitxategia</string>
<string name="share_track_share_file_body">Ibilbidea interesgarria gerta dakizukeela uste dut.\n\nIbilbidea ikusteko, erabili Google Earth edo beste kartografia-softwareren bat.\n\n%1$s</string> <string name="share_track_share_file_body">Ibilbidea interesgarria gerta dakizukeela uste dut.
\n
\n%1$s</string>
<string name="share_track_subject">Bide bat partekatu nahi nuke zurekin</string> <string name="share_track_subject">Bide bat partekatu nahi nuke zurekin</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">Batez besteko pausoa, mugitzerakoan</string> <string name="stats_average_moving_pace">Batez besteko pausoa, mugitzerakoan</string>
@@ -489,7 +468,7 @@ g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<string name="voice_speed_miles_per_hour_other">%1$.1f milia orduko</string> <string name="voice_speed_miles_per_hour_other">%1$.1f milia orduko</string>
<string name="voice_speed_miles_per_hour_two">2 milia orduko</string> <string name="voice_speed_miles_per_hour_two">2 milia orduko</string>
<string name="voice_speed_miles_per_hour_zero">0 milia orduko</string> <string name="voice_speed_miles_per_hour_zero">0 milia orduko</string>
<string name="voice_template">OpenTracks. Distantzia: %1$s. Denbora: %2$s. Abiadura: %3$s.</string> <string name="voice_template">OpenTracks. Distantzia: %1$s. Denbora: %2$s. Abiadura: %3$s</string>
<string name="voice_total_distance_kilometers_few">distantzia, guztira: %1$.2f kilometro</string> <string name="voice_total_distance_kilometers_few">distantzia, guztira: %1$.2f kilometro</string>
<string name="voice_total_distance_kilometers_many">distantzia, guztira: %1$.2f kilometro</string> <string name="voice_total_distance_kilometers_many">distantzia, guztira: %1$.2f kilometro</string>
<string name="voice_total_distance_kilometers_one">distantzia, guztira: 1 kilometro</string> <string name="voice_total_distance_kilometers_one">distantzia, guztira: 1 kilometro</string>
@@ -558,5 +537,78 @@ g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<!-- Share image --> <!-- Share image -->
<string name="share_image_subject">Irudi bat zurekin partekatu nahi nuke</string> <string name="share_image_subject">Irudi bat zurekin partekatu nahi nuke</string>
<string name="share_image_body">Irudi hau interesatuko zaizuela uste dut.</string> <string name="share_image_body">Irudi hau interesatuko zaizuela uste dut.</string>
<string name="open_track_as_KMZ">Ireki gisa KMZ</string>
<string name="open_track_using_dashboard">Ireki erabiliz OpenTracks-Arbel</string>
<string name="open_track_as_trackfileformat">Ireki gisa %1$s</string>
<string name="activity_type_escooter">eskooterra</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Grabatzen ari zaren bitartean, pantaila osoan ikusi.</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Pantaila osoa</string>
<string name="value_float_mile_hour">%1$.2f mph</string>
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
<string name="settings_default_export_uri_title">Track esportazio-direktorioa</string>
<string name="instant_export_enabled_title">Berehalako post-entrenamendu esportatu</string>
<string name="instant_export_enabled_summary">Esportazio-pista biltegiratze ondoren grabaketa amaitu da</string>
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
<string name="interval_list_empty_message">Ez dira edozein tarte oraindik</string>
<string name="aggregated_stats_empty_message">Grabatu zure lehen pista ikusteko batzen estatistikak</string>
<string name="open_track_as_GPX">Ireki gisa GPX</string>
<string name="description_elevation_loss">Kota galera: %1$d m (%2$d ft)</string>
<string name="export_error_post_workout">Post-entrenamendu esportazio huts egin du, mesedez, egiaztatu esportazio direktorioa.</string>
<string name="export_progress_summary_new_msg">Pista berriak esportatutako</string>
<string name="export_progress_summary_overwrite_msg">Ibilbideak gainidatzi</string>
<string name="generic_overwrite">Gainidatzi</string>
<string name="generic_separator_done_total">/</string>
<string name="settings_sensor_wheel_circumference">Gurpila zirkunferentzia (mm)</string>
<string name="sensor_state_heart_rate_max">Max Bihotzean tasa</string>
<string name="import_thread_interrupted">Haria eten</string>
<string name="import_unsupported_format">Onartzen ez den fitxategi-formatua</string>
<string name="import_progress_summary_ok_msg">Fitxategiak inportatu</string>
<string name="export_track_already_exists_msg">Pista %1$s dagoeneko existitzen helmugara direktorioa.</string>
<string name="export_all_do_it_for_all">ez da gatazka guztiak</string>
<string name="export_track_errors">Ibilbideak ez esportatutako</string>
<string name="settings_prevent_reimport_tracks_title">Saihesteko re-inportazio ibilbideak</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Osatu bat errorea</item>
<item quantity="other">Bukatu %1$d akatsak</item>
</plurals>
<string name="sensor_state_heart_rate_avg">Avg Bihotzean tasa</string>
<string name="sensor_state_cadence_max">Max Kadentzia</string>
<string name="sensor_state_cadence_avg">Avg Kadentzia</string>
<string name="settings_default_trackfileformat">Export/partekatzea fitxategi-formatua</string>
<string name="settings_night_mode_title">UI Gaia</string>
<string name="settings_night_mode_option_system">Sistema</string>
<string name="settings_night_mode_option_day">Egun</string>
<string name="settings_night_mode_option_night">Gauean</string>
<string name="sensor_state_power_avg">Avg Boterea</string>
<string name="track_detail_intervals_tab">Tarteak</string>
<string name="stats_split_interval">Zatitu tarte</string>
<string name="stats_loss">Galera</string>
<string name="stats_intervals">Tarteak</string>
<string name="voice_pace_lap">Itzulian-denbora %1$s</string>
<string name="voice_speed_lap">Itzulian abiadura %1$s</string>
<string name="toast_message_instant_track_was_exported">Azken pista esportatutako</string>
<string name="hold_to_pause">Eutsi eteteko</string>
<string name="hold_to_stop">Eutsi gelditzeko</string>
<string name="export_progress_summary_skip_msg">Ibilbideak saltatu</string>
<string name="export_progress_summary_errors_msg">Akatsak</string>
<string name="generic_open_track">Ireki Track</string>
<string name="generic_skip">Saltatzeko</string>
<string name="generic_show_errors">Erakutsi Akatsak</string>
<string name="generic_completed">Bukatu</string>
<string name="generic_click_twice_cancel">Egin klik berriro bertan behera uzteko</string>
<string name="gps_disabled_msg">GPS egokitua</string>
<string name="import_progress_summary_errors_msg">Akatsak</string>
<string name="import_parser_error">Analizatzailea errorea: %1$s</string>
<string name="import_unable_to_import_file">Ezin izan da inportazio fitxategia: %1$s</string>
<string name="import_no_kml_file_found">Ez doc.kml fitxategia aurkitu KMZ</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="import_progress_summary_exists_msg">Dagoeneko existitzen da</string>
<string name="import_prevent_reimport">Ezarri duzu saihesteko re-import track</string>
<string name="import_file_imported">File %1$s inportatutako</string>
<string name="import_error_list_dialog_title">Fitxategiak ez inportatutako</string>
<string name="gps_fixed_and_ready">GPS konpondu eta prest</string>
<string name="track_distance_notification">Distantzia: %1$s</string>
<string name="track_speed_notification">Abiadura: %1$s</string>
<string name="track_pace_notification">Erritmoa: %1$s</string>
</resources> </resources>
+48 -19
View File
@@ -38,20 +38,14 @@ limitations under the License.
<string name="help_storage_pictures_title">Miten OpenTracks tallentaa merkkeihin liittyen otetut kuvat?</string> <string name="help_storage_pictures_title">Miten OpenTracks tallentaa merkkeihin liittyen otetut kuvat?</string>
<string name="help_storage_pictures_content">Ne säilytetään sovelluksen sisäisessä tallennustilassa (eli ne näkyvät vain OpenTracksin sisällä).</string> <string name="help_storage_pictures_content">Ne säilytetään sovelluksen sisäisessä tallennustilassa (eli ne näkyvät vain OpenTracksin sisällä).</string>
<string name="help_track_export_title">Miten vien tai jaan tallennettuja reittejä?</string> <string name="help_track_export_title">Miten vien tai jaan tallennettuja reittejä?</string>
<string name="help_track_export_content"> <string name="help_track_export_content"> Jäljet voi viedä asetuksista KMZ, KML tai GPX -muodossa. KMZ/KML sisältävät kaiken; GPX vain sijainnit. <i>Jakokuvakkeella</i> jaetut jäljet jaetaan KMZ-muodossa. </string>
Jäljet voi viedä asetuksista KMZ, KML tai GPX -muodossa.
KMZ/KML sisältävät kaiken; GPX vain sijainnit.
<i>Jakokuvakkeella</i> jaetut jäljet jaetaan KMZ-muodossa.</string>
<string name="help_data_google_play_services_title">Käyttääkö tai vaatiiko OpenTracks Google Play -palveluita?</string> <string name="help_data_google_play_services_title">Käyttääkö tai vaatiiko OpenTracks Google Play -palveluita?</string>
<string name="help_recording_title">Miten OpenTracks tallentaa tietoja?</string> <string name="help_recording_title">Miten OpenTracks tallentaa tietoja?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks käyttää laitteen GPS:ää paikkatietojen jatkuvaan tallentamiseen. OpenTracks käyttää laitteen GPS:ää paikkatietojen jatkuvaan tallentamiseen.
Jos GPS raportoi epätarkkoja tietoja (sijainti, nopeus, korkeus) OpenTracks ei voi tehdä sille mitään.</string> Jos GPS raportoi epätarkkoja tietoja (sijainti, nopeus, korkeus) OpenTracks ei voi tehdä sille mitään.</string>
<string name="help_data_storage_title">Miten OpenTracks tallentaa anturidatan?</string> <string name="help_data_storage_title">Miten OpenTracks tallentaa anturidatan?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content"> Anturidata tallennetaan jokaiselle paikkapisteelle. Esimerkiksi sykemittarista tallennetaan vain <i>yksi</i> sykemittaus tallennettua paikkaa kohden. Jos olet paikallaan tai sisätiloissa, mitään anturidataa ei tallenneta (mutta se näytetään). </string>
Anturidata tallennetaan jokaiselle paikkapisteelle.
Esimerkiksi sykemittarista tallennetaan vain <i>yksi</i> sykemittaus tallennettua paikkaa kohden.
Jos olet paikallaan tai sisätiloissa, mitään anturidataa ei tallenneta (mutta se näytetään).</string>
<string name="help_gps_troubleshooting_title">Miksi OpenTracks ei tallenna sijaintia?</string> <string name="help_gps_troubleshooting_title">Miksi OpenTracks ei tallenna sijaintia?</string>
<string name="help_gps_troubleshooting_content"> Mitä voit tehdä: <string name="help_gps_troubleshooting_content"> Mitä voit tehdä:
\na) tarkista, onko GPS käytössä, \na) tarkista, onko GPS käytössä,
@@ -61,15 +55,13 @@ limitations under the License.
\n \n
\nJos kaikki ei auta, ilmoita virheestä. </string> \nJos kaikki ei auta, ilmoita virheestä. </string>
<string name="help_support_title">Miten voin tukea OpenTracksin kehitystä?</string> <string name="help_support_title">Miten voin tukea OpenTracksin kehitystä?</string>
<string name="help_support_content"> <string name="help_support_content"> Voit:
Voit:\n \n a) levittää sanaa OpenTracksista,
a) levittää sanaa OpenTracksista,\n \n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ehdottaa ominaisuuksia</a>,
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ehdottaa ominaisuuksia</a>,\n \n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ilmoittaa ohjelmistovirheistä</a>,
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ilmoittaa ohjelmistovirheistä</a>,\n \n d) auttaa ohjelmiston kehittämisessä,
d) auttaa ohjelmiston kehittämisessä,\n \n e) ostaa sovelluksen <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play -kaupasta</a>,
e) ostaa sovelluksen <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play -kaupasta</a>,\n \n f) lahjoittaa ja g) tehdä mikä mielestäsi edistäisi OpenTracksia. </string>
f) lahjoittaa ja
g) tehdä mikä mielestäsi edistäisi OpenTracksia.</string>
<string name="open_track_using_dashboard">Avaa OpenTracks-Dashboardilla</string> <string name="open_track_using_dashboard">Avaa OpenTracks-Dashboardilla</string>
<string name="open_track_as_KMZ">Avaa KMZ-muodossa</string> <string name="open_track_as_KMZ">Avaa KMZ-muodossa</string>
<string name="open_track_as_GPX">Avaa GPX-muodossa</string> <string name="open_track_as_GPX">Avaa GPX-muodossa</string>
@@ -90,7 +82,7 @@ limitations under the License.
<string name="activity_type_driving_bus">bussilla ajo</string> <string name="activity_type_driving_bus">bussilla ajo</string>
<string name="activity_type_driving_car">autolla ajo</string> <string name="activity_type_driving_car">autolla ajo</string>
<string name="activity_type_ferry">lautta</string> <string name="activity_type_ferry">lautta</string>
<string name="activity_type_frisbee">frisbee</string> <string name="activity_type_frisbee">liitokiekko</string>
<string name="activity_type_gliding">liitely</string> <string name="activity_type_gliding">liitely</string>
<string name="activity_type_hang_gliding">riippuliito</string> <string name="activity_type_hang_gliding">riippuliito</string>
<string name="activity_type_helicopter">helikopteri</string> <string name="activity_type_helicopter">helikopteri</string>
@@ -568,7 +560,7 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
<string name="instant_export_enabled_summary">Vie raita varastoon tallennuksen jälkeen on valmis</string> <string name="instant_export_enabled_summary">Vie raita varastoon tallennuksen jälkeen on valmis</string>
<string name="voice_speed_lap">Kierrosnopeus %1$s</string> <string name="voice_speed_lap">Kierrosnopeus %1$s</string>
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string> <string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
<string name="settings_sensor_wheel_circumference">Pyörän koko (mm)</string> <string name="settings_sensor_wheel_circumference">Pyörän ympärysmitta (mm)</string>
<string name="stats_intervals">Välein</string> <string name="stats_intervals">Välein</string>
<string name="import_thread_interrupted">Lanka keskeytetty</string> <string name="import_thread_interrupted">Lanka keskeytetty</string>
<string name="import_unsupported_format">Tukematon tiedostomuoto</string> <string name="import_unsupported_format">Tukematon tiedostomuoto</string>
@@ -582,4 +574,41 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
<string name="import_progress_summary_ok_msg">Tuodut tiedostot</string> <string name="import_progress_summary_ok_msg">Tuodut tiedostot</string>
<string name="import_progress_summary_exists_msg">Jo olemassa</string> <string name="import_progress_summary_exists_msg">Jo olemassa</string>
<string name="import_progress_summary_errors_msg">Virhe</string> <string name="import_progress_summary_errors_msg">Virhe</string>
<string name="generic_click_twice_cancel">Peruuta uudelleen napsauttamalla</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Kokoruutu</string>
<string name="interval_list_empty_message">Ei ole mitään intervallia vielä</string>
<string name="export_error_post_workout">Treenin jälkeinen vienti epäonnistui, tarkista vientihakemisto.</string>
<string name="export_progress_summary_overwrite_msg">Kappaleet ylikirjoitettu</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Täydennetty yhdellä virheellä</item>
<item quantity="other">Täydennetty %1$d virheillä</item>
</plurals>
<string name="export_track_already_exists_msg">Kappale %1$s on jo olemassa kohdehakemistossa.</string>
<string name="export_all_do_it_for_all">tee se kaikkien konfliktien vuoksi</string>
<string name="export_track_errors">Kappaleita ei viety</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Tallennuksen aikana, läsnä koko näytön.</string>
<string name="settings_night_mode_title">UI-teema</string>
<string name="settings_night_mode_option_system">Järjestelmä</string>
<string name="settings_night_mode_option_day">Päivä</string>
<string name="settings_night_mode_option_night">Yö</string>
<string name="export_progress_summary_new_msg">Uusia kappaleita viety</string>
<string name="export_progress_summary_skip_msg">Kappaleet ohitettu</string>
<string name="export_progress_summary_errors_msg">Virhe</string>
<string name="generic_overwrite">Korvata</string>
<string name="generic_skip">Ohittaa</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">Näytä Virheet</string>
<string name="gps_disabled_msg">GPS pois käytöstä</string>
<string name="generic_completed">Täydellinen</string>
<string name="sensor_state_power_avg">Avg Teho</string>
<string name="description_elevation_loss">Korkeussijaintitappio: %1$d m (%2$d ft)</string>
<string name="sensor_state_heart_rate_max">Maksimisyke</string>
<string name="sensor_state_heart_rate_avg">Avg syke</string>
<string name="value_float_kilometer_hour">%1$.2f k/t</string>
<string name="value_pace_mile">%1$d:%2$02d min/mailia</string>
<string name="activity_type_escooter">eskootteri</string>
<string name="sensor_state_cadence_avg">Keskim.Poljinnopeus</string>
<string name="sensor_state_cadence_max">Suurin Poljinnopeus</string>
<string name="value_float_mile_hour">%1$.2f mt</string>
<string name="generic_open_track">Avaa Raita</string>
</resources> </resources>
+17 -5
View File
@@ -73,7 +73,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="activity_type_driving_bus">guidare autobus</string> <string name="activity_type_driving_bus">guidare autobus</string>
<string name="activity_type_driving_car">guidare auto</string> <string name="activity_type_driving_car">guidare auto</string>
<string name="activity_type_ferry">traghetto</string> <string name="activity_type_ferry">traghetto</string>
<string name="activity_type_frisbee">frisbee</string> <string name="activity_type_frisbee">di frisbee</string>
<string name="activity_type_gliding">aliante</string> <string name="activity_type_gliding">aliante</string>
<string name="activity_type_hang_gliding">deltaplano</string> <string name="activity_type_hang_gliding">deltaplano</string>
<string name="activity_type_helicopter">elicottero</string> <string name="activity_type_helicopter">elicottero</string>
@@ -106,7 +106,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="activity_type_skate_boarding">skateboarding</string> <string name="activity_type_skate_boarding">skateboarding</string>
<string name="activity_type_skating">pattinaggio</string> <string name="activity_type_skating">pattinaggio</string>
<string name="activity_type_skiing">sci</string> <string name="activity_type_skiing">sci</string>
<string name="activity_type_sky_jumping">sky jumping</string> <string name="activity_type_sky_jumping">salto nel cielo</string>
<string name="activity_type_sled">slitta</string> <string name="activity_type_sled">slitta</string>
<string name="activity_type_snow_boarding">snowboard</string> <string name="activity_type_snow_boarding">snowboard</string>
<string name="activity_type_snow_shoeing">racchette da neve</string> <string name="activity_type_snow_shoeing">racchette da neve</string>
@@ -118,7 +118,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="activity_type_trail_hiking">escursionismo su sentiero</string> <string name="activity_type_trail_hiking">escursionismo su sentiero</string>
<string name="activity_type_trail_running">corsa campestre</string> <string name="activity_type_trail_running">corsa campestre</string>
<string name="activity_type_train">treno</string> <string name="activity_type_train">treno</string>
<string name="activity_type_ultimate_frisbee">ultimate frisbee</string> <string name="activity_type_ultimate_frisbee">frisbee finale</string>
<string name="activity_type_unknown">sconosciuto</string> <string name="activity_type_unknown">sconosciuto</string>
<string name="activity_type_wakeboarding">wakeboard</string> <string name="activity_type_wakeboarding">wakeboard</string>
<string name="activity_type_walking">camminare</string> <string name="activity_type_walking">camminare</string>
@@ -459,7 +459,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="waypoint_type_atm">Bancomat</string> <string name="waypoint_type_atm">Bancomat</string>
<string name="waypoint_type_bank">banca</string> <string name="waypoint_type_bank">banca</string>
<string name="waypoint_type_bar">bar</string> <string name="waypoint_type_bar">bar</string>
<string name="waypoint_type_barbecue">barbecue</string> <string name="waypoint_type_barbecue">grigliata</string>
<string name="waypoint_type_building">edificio</string> <string name="waypoint_type_building">edificio</string>
<string name="waypoint_type_bus_stop">fermata dell\'autobus</string> <string name="waypoint_type_bus_stop">fermata dell\'autobus</string>
<string name="waypoint_type_cafe">caffè</string> <string name="waypoint_type_cafe">caffè</string>
@@ -503,7 +503,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="waypoint_type_toilet">toilette</string> <string name="waypoint_type_toilet">toilette</string>
<string name="waypoint_type_tourist_information">informazioni turistiche</string> <string name="waypoint_type_tourist_information">informazioni turistiche</string>
<string name="waypoint_type_tree">.</string> <string name="waypoint_type_tree">.</string>
<string name="waypoint_type_tunnel">tunnel</string> <string name="waypoint_type_tunnel">galleria</string>
<string name="waypoint_type_waterfall">cascata</string> <string name="waypoint_type_waterfall">cascata</string>
<string name="waypoint_type_water_fountain">fontana ad acqua</string> <string name="waypoint_type_water_fountain">fontana ad acqua</string>
<string name="waypoint_type_waypoint">tappa</string> <string name="waypoint_type_waypoint">tappa</string>
@@ -603,4 +603,16 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="import_progress_summary_ok_msg">File importati</string> <string name="import_progress_summary_ok_msg">File importati</string>
<string name="import_progress_summary_exists_msg">Esiste già</string> <string name="import_progress_summary_exists_msg">Esiste già</string>
<string name="import_progress_summary_errors_msg">Errore</string> <string name="import_progress_summary_errors_msg">Errore</string>
<string name="sensor_state_power_avg">Potenza media</string>
<string name="sensor_state_heart_rate_max">Frequenza cardiaca max</string>
<string name="value_pace_kilometer">%1$d:%2$02d min/cm</string>
<string name="value_float_kilometer_hour">%1$.2f cp/s</string>
<string name="stats_loss">Perdita</string>
<string name="settings_sensor_wheel_circumference">Circonferenza della ruota (mm)</string>
<string name="description_elevation_loss">Perdita di quota: %1$d m (%2$d ft)</string>
<string name="sensor_state_heart_rate_avg">Tasso cardiaco medio</string>
<string name="sensor_state_cadence_max">Cadenza max</string>
<string name="sensor_state_cadence_avg">Cadenza media</string>
<string name="value_float_mile_hour">%1$.2f mpo</string>
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
</resources> </resources>
+3
View File
@@ -603,4 +603,7 @@ limitations under the License.
<string name="sensor_state_heart_rate_max">Maks puls</string> <string name="sensor_state_heart_rate_max">Maks puls</string>
<string name="sensor_state_power_avg">Gjennomsnittlig effekt</string> <string name="sensor_state_power_avg">Gjennomsnittlig effekt</string>
<string name="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string> <string name="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string>
<string name="track_distance_notification">Distanse: %1$s</string>
<string name="track_speed_notification">Hastighet: %1$s</string>
<string name="track_pace_notification">Tempo: %1$s</string>
</resources> </resources>
+114 -70
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc. Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> --><resources>
<resources>
<string name="about_preference_title">Over OpenTracks</string> <string name="about_preference_title">Over OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> is een sporttrackingapplicatie die uw privacy volledig respecteert.</string> <string name="about_description"><i>OpenTracks</i> is een sporttrackingapplicatie die uw privacy volledig respecteert.</string>
<string name="about_url">Project webpagina: <a href="%1$s">%1$s</a></string> <string name="about_url">Project webpagina: <a href="%1$s">%1$s</a></string>
@@ -23,71 +23,45 @@ limitations under the License.
<string name="about_libraries">Bibliotheken</string> <string name="about_libraries">Bibliotheken</string>
<!-- Help --> <!-- Help -->
<string name="help_feature_title">OpenTracks heeft de volgende kenmerken:</string> <string name="help_feature_title">OpenTracks heeft de volgende kenmerken:</string>
<string name="help_feature_content"> <string name="help_feature_content">* GPS tracks maken
* creëer GPS tracks \n * een hartslagsensor gebruiken (alleen Bluetooth LE)
* gebruik een hartslagsensor (alleen Bluetooth LE)... \n * spraakaankondigingen (op tijd of afstand)
* spraakaankondigingen (op tijd of afstand)... \n * markers maken (incl. foto\'s)
* creëer markers (incl. fotos) \n * werkt volledig offline
* werkt volledig offline \n * toon tracks op een kaart (zie hieronder)</string>
* toon tracks op een kaart (zie hieronder)</string>
<string name="help_track_map_title">Hoe toon je een spoor op een kaart?</string> <string name="help_track_map_title">Hoe toon je een spoor op een kaart?</string>
<string name="help_track_map_content"> <string name="help_track_map_content"> Gebruik het kleine kaartpictogram.
Gebruik het kleine kaarticoontje. \n a) (live updates) Installeer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Het biedt online of offline kaarten via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
a) \n Eigenlijk is OSMDashboard een aparte app die alleen (tijdelijk) toegang heeft tot de gegevens van de track(s) die getoond gaan worden.
(live updates) Installeer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. \n b) Via een normale kaartapplicatie (bijv. <a href="http://osmand.net/">OsmAnd</a> en <a href="https://maps.me/">MAPS.ME</a>). Deze apps moeten het KMZ-bestandsformaat ondersteunen (alleen locaties en tijdstempels). In feite moet een app <i>ACTION_VIEW</i> ondersteunen met MIME <i>application/vnd.google-earth.kmz</i>. </string>
Het biedt online of offline kaarten via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.\n
Eigenlijk is OSMDashboard een aparte app die alleen (tijdelijk) toegang heeft tot de gegevens van track(s) die getoond gaan worden.
\n
b)
Via een normale kaarttoepassing (bijv. <a href="http://osmand.net/">OsmAnd</a> en <a href="https://maps.me/">MAPS.ME</a>).
Deze app moet het KMZ-bestandsformaat ondersteunen (alleen locaties en tijdstempels).
In feite moet een app <i>ACTION_VIEW</i> ondersteunen met MIME <i>application/vnd.google-earth.kmz</i>.</string>
<string name="help_storage_pictures_title">Hoe slaat OpenTracks fotos op die als markers zijn genomen?</string> <string name="help_storage_pictures_title">Hoe slaat OpenTracks fotos op die als markers zijn genomen?</string>
<string name="help_storage_pictures_content">Deze worden opgeslagen in de interne opslag van de app (d.w.z. alleen toegankelijk binnen OpenTracks).</string> <string name="help_storage_pictures_content">Deze worden opgeslagen in de interne opslag van de app (d.w.z. alleen toegankelijk binnen OpenTracks).</string>
<string name="help_track_export_title">Hoe kan ik opgenomen tracks exporteren/delen?</string> <string name="help_track_export_title">Hoe kan ik opgenomen tracks exporteren/delen?</string>
<string name="help_track_export_content"> <string name="help_track_export_content"> Tracks kunnen worden geëxporteerd vanuit de instellingen als KMZ, KML of GPX. KMZ/KML alle gegevens; GPX alleen locaties. Tracks die worden gedeeld via het <i>share-icoontje</i> worden gedeeld als KMZ </string>
Tracks kunnen worden geëxporteerd vanuit de instellingen als KMZ, KML of GPX.
KMZ/KML alle gegevens; GPX alleen locaties.
Tracks die worden gedeeld via het <i>share-icoontje</i> worden gedeeld als KMZ.</string>
<string name="help_data_google_play_services_title">Heeft OpenTracks Google Play Service nodig/gebruikt?</string> <string name="help_data_google_play_services_title">Heeft OpenTracks Google Play Service nodig/gebruikt?</string>
<string name="help_recording_title">Hoe legt OpenTracks gegevens vast?</string> <string name="help_recording_title">Hoe legt OpenTracks gegevens vast?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks gebruikt de GPS-sensor van het apparaat om de locatie continu te registreren. OpenTracks gebruikt de GPS-sensor van het apparaat om de locatie continu te registreren.
Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid, hoogte), kan OpenTracks daar niets aan doen.</string> Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid, hoogte), kan OpenTracks daar niets aan doen.</string>
<string name="help_data_storage_title">Hoe slaat OpenTracks sensorgegevens op?</string> <string name="help_data_storage_title">Hoe slaat OpenTracks sensorgegevens op?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content"> De sensorgegevens worden per opgenomen locatie opgeslagen. In termen van een hartslagsensor betekent dit dat slechts <i>een</i> hartslagmeting per locatie wordt opgeslagen. Als u stilstaat of binnenshuis bent, worden er geen sensorgegevens geregistreerd (maar wel getoond) </string>
De sensorgegevens worden per opgenomen locatie opgeslagen.
In termen van een hartslagsensor betekent dit dat slechts <i>een</i> hartslagmeting per locatie wordt opgeslagen.
Als u stilstaat of binnenshuis bent, worden er geen sensorgegevens geregistreerd (maar wel getoond).</string>
<string name="help_gps_troubleshooting_title">OpenTracks neemt geen locaties op?</string> <string name="help_gps_troubleshooting_title">OpenTracks neemt geen locaties op?</string>
<string name="help_gps_troubleshooting_content"> <string name="help_gps_troubleshooting_content"> Wat u kunt doen:
Wat kun je doen? \n a) Controleer of GPS is ingeschakeld,
a) controleer of de GPS is ingeschakeld. \n b) verbeter de GPS-ontvangst (ga bijvoorbeeld naar buiten),
b) het verbeteren van de GPS-ontvangst (bijv. naar buiten gaan),\n \n c) controleren met <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, en
c) controleer of met <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, en \n d) schakel energiebesparing uit.
d) schakel stroombesparing uit.
\n \n
Als alles niet helpt, meldt u dan alstublieft een bug.</string> \n Als alles niet helpt, meld dan een bug. </string>
<string name="help_support_title">Hoe kan ik OpenTracks ondersteunen?</string> <string name="help_support_title">Hoe kan ik OpenTracks ondersteunen?</string>
<string name="help_support_content"> <string name="help_support_content"> U kunt:
Je kunt... \n a) het woord verspreiden over OpenTracks,
a) het woord verspreiden over OpenTracks, en... \n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">functies voorstellen</a>,
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">voorstellen van functies</a>,\n \n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">bugs melden</a>,
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">melding van bugs</a>,\n \n d) helpen bij de ontwikkeling van software,
d) hulp bij de ontwikkeling van software. \n e) kopen in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
e) koop in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n \n f) doneren, en
f) doneert u, en \n g) doe wat u denkt dat OpenTracks helpt. </string>
g) doe wat je denkt dat OpenTracks helpt.</string>
<!-- Activity Type --> <!-- Activity Type -->
<string name="activity_type_airplane">vliegtuig</string> <string name="activity_type_airplane">vliegtuig</string>
<string name="activity_type_atv">quad</string> <string name="activity_type_atv">quad</string>
@@ -104,7 +78,7 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="activity_type_driving_bus">bus besturen</string> <string name="activity_type_driving_bus">bus besturen</string>
<string name="activity_type_driving_car">autorijden</string> <string name="activity_type_driving_car">autorijden</string>
<string name="activity_type_ferry">veerboot</string> <string name="activity_type_ferry">veerboot</string>
<string name="activity_type_frisbee">frisbee</string> <string name="activity_type_frisbee">Frisbee</string>
<string name="activity_type_gliding">zweefvliegen</string> <string name="activity_type_gliding">zweefvliegen</string>
<string name="activity_type_hang_gliding">deltavliegen</string> <string name="activity_type_hang_gliding">deltavliegen</string>
<string name="activity_type_helicopter">helikopter</string> <string name="activity_type_helicopter">helikopter</string>
@@ -149,7 +123,7 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="activity_type_trail_hiking">trektocht (pad)</string> <string name="activity_type_trail_hiking">trektocht (pad)</string>
<string name="activity_type_trail_running">trailrunning</string> <string name="activity_type_trail_running">trailrunning</string>
<string name="activity_type_train">trein</string> <string name="activity_type_train">trein</string>
<string name="activity_type_ultimate_frisbee">ultimate frisbee</string> <string name="activity_type_ultimate_frisbee">ultieme frisbee</string>
<string name="activity_type_unknown">onbekend</string> <string name="activity_type_unknown">onbekend</string>
<string name="activity_type_wakeboarding">wakeboarden</string> <string name="activity_type_wakeboarding">wakeboarden</string>
<string name="activity_type_walking">wandelen</string> <string name="activity_type_walking">wandelen</string>
@@ -185,14 +159,14 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="description_sensor_cadence">Cadans (tpm)</string> <string name="description_sensor_cadence">Cadans (tpm)</string>
<string name="description_sensor_heart_rate">Hartslag (spm)</string> <string name="description_sensor_heart_rate">Hartslag (spm)</string>
<string name="description_sensor_power">Vermogen (W)</string> <string name="description_sensor_power">Vermogen (W)</string>
<string name="description_default_speed_or_pace">By activity type</string> <string name="description_default_speed_or_pace">Op type activiteit</string>
<string name="description_speed_imperial">Snelheid (mi/u)</string> <string name="description_speed_imperial">Snelheid (mi/u)</string>
<string name="description_speed_metric">Snelheid (km/u)</string> <string name="description_speed_metric">Snelheid (km/u)</string>
<string name="description_time">Tijd</string> <string name="description_time">Tijd</string>
<string name="description_total_distance">Totale afstand: %1$.2f km (%2$.1f mi)</string> <string name="description_total_distance">Totale afstand: %1$.2f km (%2$.1f mi)</string>
<string name="description_total_time">Totale tijd: %1$s</string> <string name="description_total_time">Totale tijd: %1$s</string>
<string name="description_track_point">Punt</string> <string name="description_track_point">Punt</string>
<string name="description_track_segment">Segment</string> <string name="description_track_segment">segment</string>
<!-- Export --> <!-- Export -->
<string name="export_all_option">Alles als %1$s</string> <string name="export_all_option">Alles als %1$s</string>
<string name="export_all_title">Exporteren naar externe opslag</string> <string name="export_all_title">Exporteren naar externe opslag</string>
@@ -331,7 +305,7 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="settings_chart_by_time">Op tijd</string> <string name="settings_chart_by_time">Op tijd</string>
<string name="settings_chart_x_axis">X-as</string> <string name="settings_chart_x_axis">X-as</string>
<!-- Settings Recording --> <!-- Settings Recording -->
<string name="settings_recording_show_on_lockscreen_while_recording_title">Toon de statistieken op het slotscherm.</string> <string name="settings_recording_show_on_lockscreen_while_recording_title">Toon de statistieken op het slotscherm</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Toon tijdens de opname de statistieken zonder het apparaat te ontgrendelen.</string> <string name="settings_recording_show_on_lockscreen_while_recording_summary">Toon tijdens de opname de statistieken zonder het apparaat te ontgrendelen.</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Houd het scherm aan</string> <string name="settings_recording_keep_screen_on_while_recording_title">Houd het scherm aan</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Houd tijdens de opname het scherm aan.</string> <string name="settings_recording_keep_screen_on_while_recording_summary">Houd tijdens de opname het scherm aan.</string>
@@ -359,22 +333,22 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="settings_reset_done">Voor alle instellingen zijn de standaardwaarden teruggezet</string> <string name="settings_reset_done">Voor alle instellingen zijn de standaardwaarden teruggezet</string>
<string name="settings_error_initial_values_restored">Sorry, maar er is een fout gemaakt. Alle instellingen zijn teruggezet naar de standaardwaarden.</string> <string name="settings_error_initial_values_restored">Sorry, maar er is een fout gemaakt. Alle instellingen zijn teruggezet naar de standaardwaarden.</string>
<!-- Settings Sensor --> <!-- Settings Sensor -->
<string name="settings_sensor">Sensor</string> <string name="settings_sensor">sensor</string>
<string name="settings_sensor_bluetooth">Bluetooth</string> <string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string> <string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connecting">Verbonden met %1$s</string> <string name="settings_sensor_connecting">Verbonden met %1$s</string>
<string name="settings_sensor_disconnected">Ontkoppeld van %1$s</string> <string name="settings_sensor_disconnected">Ontkoppeld van %1$s</string>
<!-- Settings Stats --> <!-- Settings Stats -->
<string name="settings_stats_coordinate">Breedte-/lengtegraad</string> <string name="settings_stats_coordinate">Breedte-/lengtegraad</string>
<string name="settings_stats_rate_title">Voorkeursverhouding</string> <string name="settings_stats_rate_title">Voorkeursverhouding</string>
<string name="settings_stats_units_imperial">Engels (mi, ft)</string> <string name="settings_stats_units_imperial">Engels (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisch (km, m)</string> <string name="settings_stats_units_metric">Metrisch (km, m)</string>
<string name="settings_stats_units_title">Voorkeurseenheden</string> <string name="settings_stats_units_title">Voorkeurseenheden</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file">Routebestand delen</string> <string name="share_track_share_file">Routebestand delen</string>
<string name="share_track_share_file_body">Ik denk dat u misschien wel geïnteresseerd bent in deze route.\n\nAls u deze route wilt weergeven, kunt u Google Earth of andere kaartsoftware gebruiken.\n\n%1$s</string> <string name="share_track_share_file_body">Ik denk dat u misschien wel geïnteresseerd bent in deze route.
\n
\n%1$s</string>
<string name="share_track_subject">Ik wil graag een route met u delen</string> <string name="share_track_subject">Ik wil graag een route met u delen</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">Gem. verplaatsingstempo</string> <string name="stats_average_moving_pace">Gem. verplaatsingstempo</string>
@@ -473,7 +447,7 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="voice_minutes_other">%1$d minuten</string> <string name="voice_minutes_other">%1$d minuten</string>
<string name="voice_minutes_two">2 minuten</string> <string name="voice_minutes_two">2 minuten</string>
<string name="voice_minutes_zero">0 minuten</string> <string name="voice_minutes_zero">0 minuten</string>
<string name="voice_pace_per_kilometer">%1$s per kilometer</string> <string name="voice_pace_per_kilometer">%1$s te kilometer</string>
<string name="voice_pace_per_mile">%1$s per mijl</string> <string name="voice_pace_per_mile">%1$s per mijl</string>
<string name="voice_seconds_few">%1$d seconden</string> <string name="voice_seconds_few">%1$d seconden</string>
<string name="voice_seconds_many">%1$d seconden</string> <string name="voice_seconds_many">%1$d seconden</string>
@@ -511,7 +485,7 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="waypoint_type_atm">Pinautomaat</string> <string name="waypoint_type_atm">Pinautomaat</string>
<string name="waypoint_type_bank">bank</string> <string name="waypoint_type_bank">bank</string>
<string name="waypoint_type_bar">bar</string> <string name="waypoint_type_bar">bar</string>
<string name="waypoint_type_barbecue">barbecue</string> <string name="waypoint_type_barbecue">barbecueën</string>
<string name="waypoint_type_building">gebouw</string> <string name="waypoint_type_building">gebouw</string>
<string name="waypoint_type_bus_stop">bushalte</string> <string name="waypoint_type_bus_stop">bushalte</string>
<string name="waypoint_type_cafe">café</string> <string name="waypoint_type_cafe">café</string>
@@ -551,16 +525,86 @@ g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="waypoint_type_store">winkel</string> <string name="waypoint_type_store">winkel</string>
<string name="waypoint_type_swimming_pool">zwembad</string> <string name="waypoint_type_swimming_pool">zwembad</string>
<string name="waypoint_type_telephone">telefoon</string> <string name="waypoint_type_telephone">telefoon</string>
<string name="waypoint_type_theater">theater</string> <string name="waypoint_type_theater">schouwburg</string>
<string name="waypoint_type_toilet">toilet</string> <string name="waypoint_type_toilet">restroom</string>
<string name="waypoint_type_tourist_information">toeristische informatie</string> <string name="waypoint_type_tourist_information">toeristische informatie</string>
<string name="waypoint_type_tree">boom</string> <string name="waypoint_type_tree">boom</string>
<string name="waypoint_type_tunnel">tunnel</string> <string name="waypoint_type_tunnel">schacht</string>
<string name="waypoint_type_waterfall">waterval</string> <string name="waypoint_type_waterfall">waterval</string>
<string name="waypoint_type_water_fountain">waterfontein</string> <string name="waypoint_type_water_fountain">waterfontein</string>
<string name="waypoint_type_waypoint">routepunt</string> <string name="waypoint_type_waypoint">routepunt</string>
<!-- Share image --> <!-- Share image -->
<string name="share_image_subject">Ik wil graag een beeld met u delen</string> <string name="share_image_subject">Ik wil graag een beeld met u delen</string>
<string name="share_image_body">Ik denk dat je misschien geïnteresseerd bent in dit beeld.</string> <string name="share_image_body">Ik denk dat je misschien geïnteresseerd bent in dit beeld.</string>
<string name="export_track_already_exists_msg">De track %1$s bestaat al in de doelmap.</string>
<string name="export_track_errors">Tracks niet geëxporteerd</string>
<string name="description_elevation_loss">Hoogteverlies: %1$d m (%2$d ft)</string>
<string name="export_error_post_workout">Post-workout export mislukt, controleer de export map.</string>
<string name="open_track_using_dashboard">Openen met behulp van OpenTracks-Dashboard</string>
<string name="open_track_as_KMZ">Openen als KMZ</string>
<string name="open_track_as_GPX">Openen als GPX</string>
<string name="open_track_as_trackfileformat">Openen als %1$s</string>
<string name="activity_type_escooter">scooter</string>
<string name="export_all_do_it_for_all">doe het voor alle conflicten</string>
<string name="export_progress_summary_new_msg">Nieuwe sporen geëxporteerd</string>
<string name="generic_click_twice_cancel">Klik nogmaals terug om te annuleren</string>
<string name="settings_default_export_uri_title">Spoor export map</string>
<string name="import_thread_interrupted">Draad onderbroken</string>
<string name="import_no_kml_file_found">Geen doc.kml bestand gevonden in KMZ</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Volledig scherm</string>
<string name="gps_fixed_and_ready">GPS vast en klaar</string>
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
<string name="aggregated_stats_empty_message">Neem uw eerste track op om geaggregeerde statistieken te zien</string>
<string name="track_detail_intervals_tab">Intervallen</string>
<string name="settings_sensor_wheel_circumference">Wielomtrek (mm)</string>
<string name="settings_prevent_reimport_tracks_title">Voorkom herinvoer van tracks</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Voltooid met één fout</item>
<item quantity="other">Afgerond met %1$d fouten</item>
</plurals>
<string name="settings_recording_fullscreen_on_while_recording_summary">Tijdens opname, aanwezig in volledig scherm.</string>
<string name="settings_default_trackfileformat">Bestandsformaat exporteren/delen</string>
<string name="sensor_state_power_avg">Gem. Vermogen</string>
<string name="sensor_state_heart_rate_max">Max. Hartslag</string>
<string name="sensor_state_heart_rate_avg">Gem. Hartslag</string>
<string name="sensor_state_cadence_avg">Avg cadans</string>
<string name="sensor_state_cadence_max">Max. cadans</string>
<string name="settings_night_mode_option_night">Nacht</string>
<string name="settings_night_mode_option_day">Dag</string>
<string name="settings_night_mode_option_system">Systeem</string>
<string name="settings_night_mode_title">UI Thema</string>
<string name="stats_loss">Verlies</string>
<string name="value_float_mile_hour">%1$.2f mph</string>
<string name="interval_list_empty_message">Er zijn nog geen intervallen</string>
<string name="stats_split_interval">Gesplitst interval</string>
<string name="stats_intervals">Intervallen</string>
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
<string name="voice_pace_lap">Rondetijd van %1$s</string>
<string name="voice_speed_lap">Ronde snelheid van %1$s</string>
<string name="instant_export_enabled_title">Directe export na de training</string>
<string name="instant_export_enabled_summary">Exporteer track naar opslag nadat opname is voltooid</string>
<string name="toast_message_instant_track_was_exported">Laatste geëxporteerde track</string>
<string name="hold_to_pause">Vasthouden om te pauzeren</string>
<string name="export_progress_summary_skip_msg">Tracks overgeslagen</string>
<string name="export_progress_summary_overwrite_msg">Tracks overschreven</string>
<string name="export_progress_summary_errors_msg">Fouten</string>
<string name="generic_open_track">Open spoor</string>
<string name="generic_overwrite">Overschrijven</string>
<string name="generic_skip">Sla over.</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">Fouten tonen</string>
<string name="generic_completed">Voltooid</string>
<string name="import_unsupported_format">Niet-ondersteund bestandsformaat</string>
<string name="import_error_list_dialog_title">Bestanden niet geïmporteerd</string>
<string name="import_prevent_reimport">Jij hebt ingesteld voorkom her-import spoor</string>
<string name="import_progress_summary_ok_msg">Ingevoerde bestanden</string>
<string name="import_progress_summary_exists_msg">Bestaat reeds</string>
<string name="import_progress_summary_errors_msg">Fouten</string>
<string name="gps_disabled_msg">GPS uitgeschakeld</string>
<string name="import_parser_error">Parser fout: %1$s</string>
<string name="import_unable_to_import_file">Kan bestand niet importeren: %1$s</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="import_file_imported">Bestand %1$s geimporteerd</string>
<string name="hold_to_stop">Hou vast om te stoppen</string>
</resources> </resources>

Some files were not shown because too many files have changed in this diff Show More