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
* Garmin Speed Sensor 2
* Has updatable firmware that requires an account for garmin.com
* This sensor reports speed data as cadence.
A workaround is in place.
* This sensor sometimes counts backwards (unknown software version).
A workaround is in place.
* Wahoo Speed (Model: WFRPMSPD)
### Cadence
+4 -4
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.android.tools.build:gradle:4.1.3'
}
}
@@ -51,8 +51,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 3890
versionName "v3.15.3"
versionCode 3941
versionName "v3.16.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -86,7 +86,7 @@ android {
}
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'
implementation 'androidx.appcompat:appcompat:1.2.0'
+1 -11
View File
@@ -2,17 +2,7 @@
interface TrackExporter {
}
class KmzTrackExporter implements TrackExporter
KmzTrackExporter o-- FileTrackExporter
class FileTrackExporter implements TrackExporter {
/' One track only '/
}
FileTrackExporter o-- TrackWriter
interface TrackWriter {
}
class GpxTrackWriter implements TrackWriter
class KmlTrackWriter implements TrackWriter
class KmlTrackExporter implements TrackExporter
interface TrackImporter {
}
+28 -4
View File
@@ -1,8 +1,32 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
<xs:element name="trackuuid"
type="xs:string" />
<xsd:element name="trackuuid"
type="xsd:string">
<xsd:annotation>
<xsd:documentation xml:lang="en">
A unique track identifier stored as UUID.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xs:schema>
<xsd:element name="loss"
type="xsd:float">
<xsd:annotation>
<xsd:documentation xml:lang="en">
The lost elevation in meters from the previous to the current TrackPoint.
Only used in GPX.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="gain"
type="xsd:string">
<xsd:annotation>
<xsd:documentation xml:lang="en">
The gained elevation in meters from the previous to the current TrackPoint.
Only used in GPX.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
@@ -0,0 +1,4 @@
v3.15.4: OpenTracks
Bugfix:
- Voice annoucements interval should be in minutes (introduced in v3.15.0)
@@ -0,0 +1,13 @@
v3.16.0: OpenTracks
Changes:
- Cycling: get distance from BLE speed sensor
- Reworked UI presented while recording
- UI performance optimizations
- Exported filenames: <First 32bits UUID>_<track's name>.<extension>
- Import multiple files via Intent (e.g., from Gadgetbridge)
- GPX: export power data
- Updated translations
Bugfix:
- Voice announcement use minutes correctly
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.FileUtils;
public class TestDataUtil {
@@ -150,4 +151,13 @@ public class TestDataUtil {
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, 0.0, 0, trackPoint, photoUrl);
}
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null);
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
while (trackPointIterator.hasNext()) {
trackPoints.add(trackPointIterator.next());
}
return trackPoints;
}
}
@@ -7,8 +7,8 @@ import java.util.List;
public class TestSensorDataUtil {
private List<TrackPoint> trackPointList = new ArrayList<>();
private List<SensorData> sensorDataList = new ArrayList<>();
private final List<TrackPoint> trackPointList = new ArrayList<>();
private final List<SensorData> sensorDataList = new ArrayList<>();
public void add(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type));
@@ -33,7 +33,7 @@ public class TestSensorDataUtil {
}
public SensorDataStats computeStats() {
if (sensorDataList == null || sensorDataList.size() <= 1) {
if (sensorDataList.size() <= 1) {
return null;
}
@@ -49,9 +49,9 @@ public class TestSensorDataUtil {
if (dataPrev.type != TrackPoint.Type.SEGMENT_START_MANUAL) {
timeElapsed = dataCurrent.type != TrackPoint.Type.SEGMENT_START_MANUAL ? dataCurrent.time.getEpochSecond() - dataPrev.time.getEpochSecond() : 0;
stats.avgHr += (dataPrev.hr * timeElapsed);
stats.maxHr = dataPrev.hr > stats.maxHr ? dataPrev.hr : stats.maxHr;
stats.maxHr = Math.max(dataPrev.hr, stats.maxHr);
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);
movingTime += timeElapsed;
@@ -67,11 +67,11 @@ public class TestSensorDataUtil {
}
private static class SensorData {
Instant time;
float hr;
float cadence;
float power;
TrackPoint.Type type;
final Instant time;
final float hr;
final float cadence;
final float power;
final TrackPoint.Type type;
public SensorData(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
this.time = time;
@@ -94,29 +94,22 @@ public class CustomContentProviderUtilsTest {
@Test
public void testLocationIterator_noPoints() {
testIterator(new Track.Id(1), 0, 1);
testIterator(new Track.Id(1), 0);
}
@Test
public void testLocationIterator_noBatchAscending() {
testIterator(new Track.Id(1), 50, 100);
testIterator(new Track.Id(2), 50, 50);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(new Track.Id(1), 50, 11);
testIterator(new Track.Id(2), 50, 25);
public void testLocationIterator_noAscending() {
testIterator(new Track.Id(1), 50);
testIterator(new Track.Id(2), 50);
}
@Test
public void testLocationIterator_largeTrack() {
testIterator(new Track.Id(1), 20000, 2000);
testIterator(new Track.Id(1), 20000);
}
private void testIterator(Track.Id trackId, int numPoints, int batchSize) {
long lastPointId = initializeTrack(trackId, numPoints);
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
private void testIterator(Track.Id trackId, int numPoints) {
TrackPoint.Id lastPointId = initializeTrack(trackId, numPoints);
List<TrackPoint> locations = new ArrayList<>(numPoints);
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
while (it.hasNext()) {
@@ -124,13 +117,13 @@ public class CustomContentProviderUtilsTest {
assertNotNull(trackPoint);
locations.add(trackPoint);
// Make sure the IDs are returned in the right order.
assertEquals(lastPointId - numPoints + locations.size(), trackPoint.getId().getId());
assertEquals(lastPointId.getId() - numPoints + locations.size(), trackPoint.getId().getId());
}
assertEquals(numPoints, locations.size());
}
}
private long initializeTrack(Track.Id id, int numPoints) {
private TrackPoint.Id initializeTrack(Track.Id id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id.getId());
@@ -150,17 +143,17 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
// Load all inserted trackPoints.
long lastPointId = -1;
TrackPoint.Id lastPointId = null;
int counter = 0;
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) {
while (it.hasNext()) {
TrackPoint trackPoint = it.next();
lastPointId = trackPoint.getId().getId();
lastPointId = trackPoint.getId();
counter++;
}
}
assertTrue(numPoints == 0 || lastPointId > 0);
assertTrue(numPoints == 0 || lastPointId.getId() > 0);
assertEquals(numPoints, counter);
return lastPointId;
@@ -770,9 +763,9 @@ public class CustomContentProviderUtilsTest {
// when / then
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId);
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
}
/**
@@ -829,7 +822,7 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
}
@Test
@@ -847,7 +840,7 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
// then
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrue(trackPoints.get(10).hasHeartRate());
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
@@ -879,7 +872,7 @@ public class CustomContentProviderUtilsTest {
.map(TrackPoint.Id::new).collect(Collectors.toList());
// when
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8), 5);
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8));
// then
assertEquals(2, cursor.getCount());
@@ -96,34 +96,36 @@ public class SensorDataCyclingTest {
@Test
public void compute_speed() {
// given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016);
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
// when
current.compute(previous, 2150);
// then
assertEquals(1.20, current.getValue(), 0.01);
assertEquals(1.20, current.getValue().distance_m, 2150);
assertEquals(1.20, current.getValue().speed_mps, 0.01);
}
@Test
public void compute_speed_rollOverCount() {
// given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, 2000);
// then
assertEquals(2, current.getValue(), 0.01);
assertEquals(1.20, current.getValue().distance_m, 2000);
assertEquals(2, current.getValue().speed_mps, 0.01);
}
@Test
public void equals_speed_with_no_data() {
// given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress");
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
@@ -2,7 +2,7 @@ package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.content.SharedPreferences;
import android.os.Looper;
import android.util.Log;
@@ -23,6 +23,7 @@ import org.junit.runners.JUnit4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -31,6 +32,7 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -69,6 +71,7 @@ public class ExportImportTest {
}
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
@@ -89,17 +92,17 @@ public class ExportImportTest {
trackId = service.startNewTrack();
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50, 5), 0);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, 5), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, 5), 0);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
service.pauseCurrentTrack();
service.resumeCurrentTrack();
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, 5), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, 5), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, 5), 0);
service.endCurrentTrack();
track = contentProviderUtils.getTrack(trackId);
@@ -109,7 +112,7 @@ public class ExportImportTest {
contentProviderUtils.updateTrack(track);
track = contentProviderUtils.getTrack(trackId);
trackPoints = contentProviderUtils.getTrackPoints(trackId);
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
markers = contentProviderUtils.getMarkers(trackId);
assertEquals(10, trackPoints.size());
assertEquals(2, markers.size());
@@ -148,7 +151,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -161,10 +164,10 @@ public class ExportImportTest {
assertEquals(track.getUuid(), importedTrack.getUuid());
// 2. trackpoints
assertTrackpoints(trackPoints, false, false, false, false, false);
assertTrackpoints(trackPoints, false, false, false, false, false, false);
// 3. trackstatistics
assertTrackStatistics(false, false);
assertTrackStatistics(false, false, false);
// 4. markers
assertMarkers();
@@ -187,7 +190,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -199,10 +202,10 @@ public class ExportImportTest {
assertEquals(track.getIcon(), importedTrack.getIcon());
// 2. trackpoints
assertTrackpoints(trackPoints, true, true, true, true, true);
assertTrackpoints(trackPoints, true, true, true, true, true, true);
// 2. trackstatistics
assertTrackStatistics(false, true);
assertTrackStatistics(false, true, true);
// 4. markers
assertMarkers();
@@ -212,7 +215,7 @@ public class ExportImportTest {
@Test(expected = ImportAlreadyExistsException.class)
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
@@ -225,45 +228,13 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNull(importedTrack);
}
@Ignore
@LargeTest
@Test
public void kmz_only_track() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@Ignore
@LargeTest
@Test
public void kmz_with_trackdetail() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@Ignore
@LargeTest
@Test
public void kmz_with_trackdetail_and_sensordata() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@Ignore
@LargeTest
@Test
public void kmz_with_trackdetail_and_sensordata_and_pictures() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void gpx() {
@@ -281,7 +252,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -301,10 +272,10 @@ public class ExportImportTest {
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
assertTrackpoints(trackPointsWithCoordinates, false, true, true, true, true);
assertTrackpoints(trackPointsWithCoordinates, true, true, true, true, true, false);
// 3. trackstatistics
assertTrackStatistics(true, true);
assertTrackStatistics(true, true, false);
// 4. markers
assertMarkers();
@@ -314,7 +285,7 @@ public class ExportImportTest {
@Test(expected = ImportAlreadyExistsException.class)
public void gpx_duplicate_trackUUID() {
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
@@ -327,7 +298,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -354,9 +325,8 @@ public class ExportImportTest {
}
}
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss) {
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss, boolean verifyDistance) {
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(trackPoints.size(), importedTrackPoints.size());
for (int i = 0; i < trackPoints.size(); i++) {
@@ -391,13 +361,13 @@ public class ExportImportTest {
}
if (verifyHeartrate) {
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
assertEquals("" + trackPoint, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
}
if (verifyCadence) {
assertEquals(trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
assertEquals("" + trackPoint, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
}
if (verifyPower) {
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
assertEquals("" + trackPoint, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
}
if (verifyElevationGain) {
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
@@ -405,10 +375,13 @@ public class ExportImportTest {
if (verifyElevationLoss) {
assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01);
}
if (verifyDistance) {
assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance(), 0.01);
}
}
}
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss) {
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss, boolean verifyDistance) {
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack.getTrackStatistics());
@@ -426,7 +399,9 @@ public class ExportImportTest {
assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
if (verifyDistance) {
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
}
// Speed
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed(), 0.01);
@@ -446,20 +421,15 @@ public class ExportImportTest {
}
}
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, long speed, long altitude, float elevationGain, float heartRate, float cyclingCadence, float power) {
Location location = new Location("");
location.setTime(time);
location.setLongitude(longitude);
location.setLatitude(latitude);
location.setAccuracy(accuracy);
location.setAltitude(altitude);
location.setSpeed(speed);
TrackPoint tp = new TrackPoint(location);
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float elevationGain, float heartRate, float cyclingCadence, float power, float distance) {
TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time));
tp.setAccuracy(accuracy);
tp.setSpeed(speed);
tp.setHeartRate_bpm(heartRate);
tp.setCyclingCadence_rpm(cyclingCadence);
tp.setPower(power);
tp.setElevationGain(elevationGain);
tp.setSensorDistance(distance);
return tp;
}
}
@@ -14,6 +14,7 @@ import org.junit.runners.JUnit4;
import java.io.InputStream;
import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -54,7 +55,7 @@ public class LegacyImportTest {
// when
// 1. import
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -69,7 +70,7 @@ public class LegacyImportTest {
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
// 3. trackpoints
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
// first 3 trackpoints
@@ -95,7 +96,7 @@ public class LegacyImportTest {
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
// when
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
}
/**
@@ -110,7 +111,7 @@ public class LegacyImportTest {
// when
// 1. import
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -122,7 +123,7 @@ public class LegacyImportTest {
assertEquals("UNKNOWN", importedTrack.getIcon());
// 3. trackpoints
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
// first segment
@@ -12,6 +12,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
@@ -25,7 +26,10 @@ public class TrackRecordingServiceNotificationManagerTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Mock
private TrackPoint locationMock;
private TrackPoint trackPointMock;
@Mock
private TrackStatistics trackStatisticsMock;
@Mock
private NotificationCompat.Builder notificationCompatBuilder;
@@ -35,18 +39,20 @@ public class TrackRecordingServiceNotificationManagerTest {
@Test
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
when(locationMock.hasAccuracy()).thenReturn(true);
when(locationMock.getAccuracy()).thenReturn(999f);
when(trackPointMock.hasAccuracy()).thenReturn(true);
when(trackPointMock.getAccuracy()).thenReturn(999f);
when(trackStatisticsMock.getTotalDistance()).thenReturn(0d);
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
subject.setMetricUnits(true);
// when
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, locationMock, 1000);
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 1000);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
// then
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
@@ -45,6 +45,7 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -84,6 +85,7 @@ public class TrackRecordingServiceTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
@@ -102,7 +104,6 @@ public class TrackRecordingServiceTest {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
// Ensure that the database is empty before every test
@@ -190,7 +191,7 @@ public class TrackRecordingServiceTest {
Track.Id trackId = service.startNewTrack();
// then
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(1, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
@@ -207,7 +208,7 @@ public class TrackRecordingServiceTest {
service.endCurrentTrack();
// then
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(2, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
@@ -225,7 +226,7 @@ public class TrackRecordingServiceTest {
service.pauseCurrentTrack();
// then
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
//when
service.resumeTrack(trackId);
@@ -234,7 +235,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(3, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
@@ -250,7 +251,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording());
service.endCurrentTrack();
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
// when
service.resumeTrack(trackId);
@@ -260,7 +261,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(4, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
@@ -277,7 +278,7 @@ public class TrackRecordingServiceTest {
assertTrue(service.isRecording());
service.pauseCurrentTrack();
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
// when
service.endCurrentTrack();
@@ -286,7 +287,7 @@ public class TrackRecordingServiceTest {
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(2, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
@@ -302,7 +303,7 @@ public class TrackRecordingServiceTest {
// when
// Just set recording track to a bogus value.
// Make sure that the service will not start recording and will clear the bogus track.
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, 123L);
// then
assertFalse(service.isRecording());
@@ -325,7 +326,7 @@ public class TrackRecordingServiceTest {
// then
assertNull(newTrackId);
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
assertEquals(trackId, service.getRecordingTrackId());
}
@@ -341,7 +342,7 @@ public class TrackRecordingServiceTest {
service.endCurrentTrack();
// then
assertFalse(PreferencesUtils.isRecording(context));
assertFalse(PreferencesUtils.isRecording(sharedPreferences, context));
assertNull(service.getRecordingTrackId());
}
@@ -388,8 +389,8 @@ public class TrackRecordingServiceTest {
assertNotNull(track.getId());
contentProviderUtils.insertTrack(track);
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, !isRecording);
}
// NOTE: Do not use to create a track that is currently recording.
@@ -422,7 +423,6 @@ public class TrackRecordingServiceTest {
location.setTime(time);
location.setBearing(3.0f);
TrackPoint trackPoint = new TrackPoint(location);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
trackRecordingService.newTrackPoint(trackPoint, 50);
}
}
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -104,7 +105,7 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(8, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -138,7 +139,7 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(4, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -185,7 +186,7 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
@@ -214,7 +215,7 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(6, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -260,7 +261,7 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(8, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -292,7 +293,7 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(7, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
@@ -53,6 +53,7 @@ public class TrackRecordingServiceTestLooper {
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
@@ -77,7 +78,6 @@ public class TrackRecordingServiceTestLooper {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply();
// Ensure that the database is empty before every test
@@ -99,28 +99,28 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, 500);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, -1);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, 1);
fullRecordingSession();
}
@@ -128,14 +128,14 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, 50);
fullRecordingSession();
}
@@ -143,42 +143,42 @@ public class TrackRecordingServiceTestLooper {
@Test
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, 2);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingInterval() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, 2);
fullRecordingSession();
}
@@ -186,7 +186,7 @@ public class TrackRecordingServiceTestLooper {
@Test
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
fullRecordingSession();
}
@@ -209,7 +209,7 @@ public class TrackRecordingServiceTestLooper {
Track track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
assertEquals(trackId, track.getId());
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
assertEquals(trackId, service.getRecordingTrackId());
// Insert a few points, markers and statistics.
@@ -223,7 +223,7 @@ public class TrackRecordingServiceTestLooper {
location.setTime(startTime + i * 10000);
location.setBearing(3.0f);
TrackPoint trackPoint = new TrackPoint(location);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
service.newTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) {
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import org.junit.After;
import org.junit.Before;
@@ -25,6 +26,9 @@ public class HandlerServerTest {
@Mock
private LocationHandler locationHandler;
@Mock
private SharedPreferences sharedPreferences;
private HandlerServer subject;
@Before
@@ -41,10 +45,10 @@ public class HandlerServerTest {
@Test
public void onSharedPreferenceChanged() {
// when
subject.onSharedPreferenceChanged(null, null, null);
subject.onSharedPreferenceChanged(context, sharedPreferences, null);
// then
verify(locationHandler).onSharedPreferenceChanged(null, null, null);
verify(locationHandler).onSharedPreferenceChanged(context, sharedPreferences, null);
}
@Test
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
public class LocationHandlerTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@Mock
private HandlerServer handlerServer;
@@ -43,11 +44,10 @@ public class LocationHandlerTest {
@Before
public void setUp() {
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key));
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.min_recording_interval_key));
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
locationHandler.onStart(context);
}
@@ -85,7 +85,7 @@ public class LocationHandlerTest {
@Test
public void testOnLocationChanged_poorAccuracy() {
// given
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
// when
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
@@ -1,27 +1,36 @@
package de.dennisguse.opentracks.stats;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class TrackStatisticsUpdaterTest {
private static final int GPS_DISTANCE = 50;
@Test
public void addTrackPoint() {
public void addTrackPoint_TestingTrack() {
// given
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
// when
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
data.trackPoints.forEach(it -> updater.addTrackPoint(it, 50));
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE));
// then
TrackStatistics statistics = updater.getTrackStatistics();
TrackStatistics statistics = subject.getTrackStatistics();
assertEquals(85.35, statistics.getTotalDistance(), 0.01);
assertEquals(Duration.ofMillis(13999), statistics.getTotalTime());
assertEquals(Duration.ofSeconds(6), statistics.getMovingTime());
@@ -35,4 +44,122 @@ public class TrackStatisticsUpdaterTest {
assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01);
assertEquals(6.566, statistics.getAverageSpeed(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_not_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
tp3.setSpeed(5f);
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
tp2.setSpeed(5f);
TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000));
tp2.setSpeed(5f);
TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000));
tp2.setSpeed(5f);
tp4.setSensorDistance(5f);
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
tp5.setSensorDistance(10f);
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(110.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
// when
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp5, GPS_DISTANCE);
// then
assertEquals(125.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_not_moving_and_sensor_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
TrackPoint tp4 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(4000));
tp4.setSensorDistance(5f);
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
tp5.setSensorDistance(10f);
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
// when
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp5, GPS_DISTANCE);
// then
assertEquals(15, subject.getTrackStatistics().getTotalDistance(), 0.01);
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_not_moving() {
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_moving() {
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_not_moving_and_sensor_speed() {
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() {
}
}
@@ -11,7 +11,6 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
@@ -27,30 +26,26 @@ public class AnnouncementUtilsTest {
@Test
public void getAnnouncement_metric() {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(Duration.ofMillis(600000));
stats.setMovingTime(Duration.ofMillis(300000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null);
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
// then
assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
}
@Test
public void getAnnouncement_withInterval_metric() {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(Duration.ofMillis(600000));
stats.setMovingTime(Duration.ofMillis(300000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000f);
@@ -58,12 +53,10 @@ public class AnnouncementUtilsTest {
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000);
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
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", lastInterval);
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
// 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
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertNull(sensor.getSpeed());
assertNull(sensor.getDistanceSpeed());
}
@Test
@@ -60,7 +60,7 @@ public class BluetoothUtilsTest {
// then
assertNull(sensor.getCadence());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
}
@Test
@@ -73,7 +73,7 @@ public class BluetoothUtilsTest {
// then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
}
@Test
@@ -0,0 +1,60 @@
package de.dennisguse.opentracks.util;
import junit.framework.TestCase;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ExportUtilsTest extends TestCase {
@Test
public void testIsExportFileExists_exists() {
// given
UUID uuid = UUID.randomUUID();
String format = "kmz";
List<String> fileNames = new ArrayList<>();
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
fileNames.add(uuid.toString().substring(0, 8) + "_other name name.kmz");
// when
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
// then
assertTrue(exists);
}
@Test
public void testIsExportFileExists_not_exists1() {
// given
UUID uuid = UUID.randomUUID();
String format = "gpx";
List<String> fileNames = new ArrayList<>();
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_name.gpx");
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
// when
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
// then
assertFalse(exists);
}
@Test
public void testIsExportFileExists_not_exists2() {
// given
UUID uuid = UUID.randomUUID();
String format = "kmz";
List<String> fileNames = new ArrayList<>();
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
// when
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
// then
assertFalse(exists);
}
}
@@ -22,12 +22,14 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_ok() {
// given
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
@@ -36,12 +38,13 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_invalid() {
// given
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -50,12 +53,13 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_noValue() {
// given
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -46,12 +47,14 @@ public class TrackNameUtilsTest {
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
/**
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
*/
@Test
public void testTrackName_date_local() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
@@ -60,7 +63,7 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
@@ -70,7 +73,7 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_number() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
}
+5
View File
@@ -73,11 +73,13 @@ limitations under the License.
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/gpx+xml" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/gpx" />
</intent-filter>
@@ -135,6 +137,7 @@ limitations under the License.
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kmz" />
</intent-filter>
@@ -192,6 +195,8 @@ limitations under the License.
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kml+xml" />
</intent-filter>
@@ -66,7 +66,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
public void onConfirmDeleteDone(Track.Id... trackIds) {
boolean stopRecording = false;
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(PreferencesUtils.getSharedPreferences(this), this);
for (Track.Id trackId : trackIds) {
if (trackId.equals(recordingTrackId)) {
stopRecording = true;
@@ -66,12 +66,12 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, MarkerListActivity.this);
}
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, MarkerListActivity.this);
}
if (key != null) {
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
@@ -138,7 +138,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onStart() {
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
}
@Override
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks;
import android.app.Application;
import android.content.SharedPreferences;
import android.os.StrictMode;
import android.util.Log;
@@ -27,31 +28,24 @@ public class Startup extends Application {
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
//Set default values of preferences on first start.
PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) {
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
PreferencesUtils.setString(sharedPreferences, this, R.string.stats_units_key, statsUnits);
}
ActivityUtils.applyNightMode(this);
ActivityUtils.applyNightMode(sharedPreferences, this);
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
if (PreferencesUtils.isRecording(this)) {
if (PreferencesUtils.isRecording(sharedPreferences, this)) {
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
PreferencesUtils.setLong(this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
}
//In debug builds: show thread and VM warnings.
if (BuildConfig.DEBUG) {
Log.d(TAG, "Enabling strict mode");
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.enableDefaults();
}
}
}
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks;
import android.Manifest;
import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
@@ -29,6 +30,7 @@ import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -107,18 +109,18 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
}
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackListActivity.this);
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
}
}
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackListActivity.this);
}
if (key != null) {
runOnUiThread(() -> {
@@ -195,17 +197,20 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
Intent newIntent;
if (trackId == recordingTrackId.getId()) {
// Is recording -> open record activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
startActivity(newIntent);
} else {
// Not recording -> open detail activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
this,
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
startActivity(newIntent, activityOptions.toBundle());
}
startActivity(newIntent);
});
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@@ -257,7 +262,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
trackRecordingServiceConnection.startConnection(this);
}
@@ -24,6 +24,7 @@ import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
@@ -55,6 +56,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
public static final String VIEW_TRACK_ICON = "track_icon";
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
@@ -84,6 +87,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
if (savedInstanceState != null) {
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
}
postponeEnterTransition();
}
@Override
@@ -138,7 +143,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(this));
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(PreferencesUtils.getSharedPreferences(this), this));
Track track = contentProviderUtils.getTrack(trackId);
setTitle(track != null ? track.getName() : "");
return super.onPrepareOptionsMenu(menu);
@@ -275,4 +280,9 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
}
}
}
public void startPostponedEnterTransitionWith(View viewIcon, View viewName) {
ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON);
startPostponedEnterTransition();
}
}
@@ -97,9 +97,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackRecordingActivity.this);
setLockscreenPolicy();
setScreenOnPolicy();
}
@@ -170,7 +170,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setLockscreenPolicy() {
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this);
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, TrackRecordingActivity.this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen);
@@ -182,7 +182,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setScreenOnPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this);
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -192,7 +192,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setFullscreenPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this);
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -206,7 +206,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
trackRecordingServiceConnection.startConnection(this);
trackDataHub.start();
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -134,11 +135,13 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
}
}
//TODO Check preference handling.
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String category = aggregatedStatistic.getCategory();
reportSpeed = PreferencesUtils.isReportSpeed(context, category);
metricsUnits = PreferencesUtils.isMetricUnits(context);
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
sportIcon.setImageResource(getIcon(aggregatedStatistic));
typeLabel.setText(category);
@@ -25,11 +25,12 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) {
super(context, R.layout.interval_stats_list_item, intervalList);
metricUnits = PreferencesUtils.isMetricUnits(context);
metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
this.category = category;
this.stackMode = stackMode;
}
//TODO Check preference handling! Should not be accessed in getView()
@NonNull
@Override
public View getView(int position, @Nullable View intervalView, @NonNull ViewGroup parent) {
@@ -60,7 +61,7 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
}
viewHolder.distance.setText(StringUtils.formatDistance(getContext(), sumDistance_m, metricUnits));
if (PreferencesUtils.isReportSpeed(getContext(), category)) {
if (PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category)) {
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, true));
} else {
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, false));
@@ -34,7 +34,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
private final Activity activity;
//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;
@@ -71,6 +71,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
private final ContentProviderUtils contentProviderUtils;
private final int targetNumPoints;
private SharedPreferences sharedPreferences;
private boolean started;
private HandlerThread handlerThread;
private Handler handler;
@@ -140,8 +142,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
PreferencesUtils.register(context, this);
onSharedPreferenceChanged(null, null);
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(sharedPreferences, null);
handler.post(() -> {
if (started) {
loadDataForAll();
@@ -155,7 +159,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
return;
}
PreferencesUtils.unregister(context, this);
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
sharedPreferences = null;
started = false;
@@ -224,10 +229,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
handler.post(() -> {
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context);
}
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context);
}
});
}
@@ -109,6 +109,19 @@ public class Track {
this.trackStatistics = trackStatistics;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Track track = (Track) o;
return id.equals(track.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public static class Id implements Parcelable {
private final long id;
@@ -57,6 +57,7 @@ public class TrackPoint {
private Double altitude_m;
private Float speed_mps;
private Float bearing;
private Float sensorDistance_m;
public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
@@ -224,7 +225,7 @@ public class TrackPoint {
return elevationGain != null;
}
public Float getElevationGain() {
public float getElevationGain() {
return elevationGain;
}
@@ -310,8 +311,12 @@ public class TrackPoint {
this.accuracy = horizontalAccuracy;
}
public float distanceTo(@NonNull TrackPoint dest) {
return getLocation().distanceTo(dest.getLocation());
public float distanceToPrevious(@NonNull TrackPoint previous) {
if (hasSensorDistance()) {
return getSensorDistance();
}
return getLocation().distanceTo(previous.getLocation());
}
public boolean fulfillsAccuracy(int poorAccuracy) {
@@ -327,6 +332,18 @@ public class TrackPoint {
}
// Sensor data
public boolean hasSensorDistance() {
return sensorDistance_m != null;
}
public Float getSensorDistance() {
return sensorDistance_m;
}
public void setSensorDistance(Float distance_m) {
this.sensorDistance_m = distance_m;
}
public boolean hasSensorData() {
return hasHeartRate() || hasCyclingCadence() || hasPower();
}
@@ -347,7 +364,7 @@ public class TrackPoint {
return cyclingCadence_rpm != null;
}
public Float getCyclingCadence_rpm() {
public float getCyclingCadence_rpm() {
return cyclingCadence_rpm;
}
@@ -359,7 +376,7 @@ public class TrackPoint {
return power != null;
}
public Float getPower() {
public float getPower() {
return power;
}
@@ -50,6 +50,7 @@ public interface TrackPointsColumns extends BaseColumns {
String BEARING = "bearing";
String SENSOR_HEARTRATE = "sensor_heartrate";
String SENSOR_CADENCE = "sensor_cadence";
String SENSOR_DISTANCE = "sensor_distance"; //DISTANCE from previous TrackPoint
String SENSOR_POWER = "sensor_power";
String ELEVATION_GAIN = "elevation_gain";
String ELEVATION_LOSS = "elevation_loss";
@@ -77,6 +78,7 @@ public interface TrackPointsColumns extends BaseColumns {
+ ELEVATION_GAIN + " FLOAT, "
+ ELEVATION_LOSS + " FLOAT, "
+ TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), "
+ SENSOR_DISTANCE + " FLOAT, "
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ ")";
@@ -19,6 +19,7 @@ class CachedTrackPointsIndexes {
final int bearingIndex;
final int sensorHeartRateIndex;
final int sensorCadenceIndex;
final int sensorDistanceIndex;
final int sensorPowerIndex;
final int elevationGainIndex;
final int elevationLossIndex;
@@ -35,6 +36,7 @@ class CachedTrackPointsIndexes {
bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE);
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS);
@@ -70,7 +70,6 @@ public class ContentProviderUtils {
private static final String ID_SEPARATOR = ",";
private final ContentResolver contentResolver;
private int defaultCursorBatchSize = 2000;
public ContentProviderUtils(Context context) {
contentResolver = context.getContentResolver();
@@ -176,7 +175,7 @@ public class ContentProviderUtils {
// Delete track last since it triggers a database vacuum call
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id->Long.toString(id.getId())).toArray(String[]::new));
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new));
}
public void deleteTrack(Context context, @NonNull Track.Id trackId) {
@@ -556,6 +555,9 @@ public class ContentProviderUtils {
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
}
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
trackPoint.setSensorDistance(cursor.getFloat(indexes.sensorDistanceIndex));
}
if (!cursor.isNull(indexes.sensorPowerIndex)) {
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
}
@@ -629,9 +631,9 @@ public class ContentProviderUtils {
*
* @param trackId the track id
* @param startTrackPointId the starting trackPoint id. `null` to ignore
* @param maxLocations maximum number of locations to return. `null` for no limit
*/
public Cursor getTrackPointCursor(Track.Id trackId, TrackPoint.Id startTrackPointId, Integer maxLocations) {
@NonNull
public Cursor getTrackPointCursor(@NonNull Track.Id trackId, TrackPoint.Id startTrackPointId) {
String selection;
String[] selectionArgs;
if (startTrackPointId != null) {
@@ -642,11 +644,7 @@ public class ContentProviderUtils {
selectionArgs = new String[]{Long.toString(trackId.getId())};
}
String sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER;
if (maxLocations != null) {
sortOrder += " LIMIT " + maxLocations;
}
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER);
}
/**
@@ -708,6 +706,9 @@ public class ContentProviderUtils {
if (trackPoint.hasCyclingCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
}
if (trackPoint.hasSensorDistance()) {
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance());
}
if (trackPoint.hasPower()) {
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
}
@@ -757,39 +758,6 @@ public class ContentProviderUtils {
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
}
@VisibleForTesting
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
List<TrackPoint> trackPoints = null;
try (Cursor trackPointCursor = getTrackPointCursor(trackId, null, null)) {
if (trackPointCursor != null) {
trackPointCursor.moveToFirst();
trackPoints = new ArrayList<>(trackPointCursor.getCount());
for (int i = 0; i < trackPointCursor.getCount(); i++) {
trackPoints.add(createTrackPoint(trackPointCursor));
trackPointCursor.moveToNext();
}
}
}
return trackPoints;
}
int getDefaultCursorBatchSize() {
return defaultCursorBatchSize;
}
/**
* Sets the default cursor batch size. For testing purpose.
*
* @param defaultCursorBatchSize the default cursor batch size
*/
@VisibleForTesting
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
this.defaultCursorBatchSize = defaultCursorBatchSize;
}
public static String formatIdListForUri(Track.Id... trackIds) {
long[] ids = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
@@ -259,8 +259,7 @@ public class CustomContentProvider extends ContentProvider {
break;
case TRACKS_SENSOR_STATS:
long trackId = ContentUris.parseId(url);
Cursor cursor = db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
return cursor;
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
case MARKERS:
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
@@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 30;
private static final int DATABASE_VERSION = 31;
@VisibleForTesting
public static final String DATABASE_NAME = "database.db";
@@ -82,6 +82,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 30:
upgradeFrom29to30(db);
break;
case 31:
upgradeFrom30to31(db);
break;
default:
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
@@ -115,7 +118,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 29:
downgradeFrom30to29(db);
break;
case 30:
downgradeFrom31to30(db);
break;
default:
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
}
@@ -394,4 +399,32 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.setTransactionSuccessful();
db.endTransaction();
}
/**
* Add distance column to TrackPoint.
*/
private void upgradeFrom30to31(SQLiteDatabase db) {
db.beginTransaction();
// TrackPoints
db.execSQL("ALTER TABLE trackpoints ADD COLUMN sensor_distance FLOAT");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom31to30(SQLiteDatabase db) {
db.beginTransaction();
// TrackPoints; identical to upgradeFrom27to28()
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1)), FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type FROM trackpoints_old");
db.execSQL("DROP TABLE trackpoints_old");
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
db.setTransactionSuccessful();
db.endTransaction();
}
}
@@ -1,7 +1,9 @@
package de.dennisguse.opentracks.content.provider;
import android.database.Cursor;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Iterator;
import java.util.NoSuchElementException;
@@ -10,9 +12,8 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
/**
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
* A lightweight wrapper around the original {@link Cursor}.
*/
//TODO Remove batching; that should be handled by the database/contentprovider (i.e., already in place as we use a cursor)!
public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private static final String TAG = TrackPointIterator.class.getSimpleName();
@@ -20,7 +21,6 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private final ContentProviderUtils contentProviderUtils;
private final Track.Id trackId;
private final CachedTrackPointsIndexes indexes;
private TrackPoint.Id lastTrackPointId = null;
private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
@@ -28,28 +28,11 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
this.trackId = trackId;
cursor = getCursor(startTrackPointId);
indexes = cursor != null ? new CachedTrackPointsIndexes(cursor)
: null;
indexes = new CachedTrackPointsIndexes(cursor);
}
/**
* Gets the track point cursor.
*
* @param trackPointId the starting track point id
*/
private Cursor getCursor(TrackPoint.Id trackPointId) {
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize());
}
/**
* Advances the cursor to the next batch. Returns true if successful.
*/
private boolean advanceCursorToNextBatch() {
TrackPoint.Id trackPointId = lastTrackPointId == null ? null : new TrackPoint.Id(lastTrackPointId.getId() + 1);
Log.d(TAG, "Advancing track point id: " + trackPointId);
cursor.close();
cursor = getCursor(trackPointId);
return cursor != null;
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId);
}
@Override
@@ -57,32 +40,23 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
if (cursor == null) {
return false;
}
if (cursor.isAfterLast()) {
return false;
}
if (cursor.isLast()) {
if (cursor.getCount() != contentProviderUtils.getDefaultCursorBatchSize()) {
return false;
}
return advanceCursorToNextBatch() && !cursor.isAfterLast();
}
return true;
return !cursor.isLast() && !cursor.isAfterLast();
}
@Override
@NonNull
public TrackPoint next() {
if (cursor == null) {
if (cursor == null || !cursor.moveToNext()) {
throw new NoSuchElementException();
}
if (!cursor.moveToNext()) {
if (!advanceCursorToNextBatch() || !cursor.moveToNext()) {
throw new NoSuchElementException();
}
}
lastTrackPointId = new TrackPoint.Id(cursor.getLong(indexes.idIndex));
return ContentProviderUtils.fillTrackPoint(cursor, indexes);
}
@VisibleForTesting
public int getCount() {
return cursor.getCount();
}
@Override
public void close() {
if (cursor != null) {
@@ -54,6 +54,12 @@ public abstract class SensorData<T> {
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.
*/
@@ -61,4 +67,10 @@ public abstract class SensorData<T> {
return Instant.now()
.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
*/
public Cadence(@NonNull SensorDataCycling.Speed speed) {
public Cadence(@NonNull DistanceSpeed speed) {
this(speed.getSensorAddress(), speed.getSensorName(), speed.wheelRevolutionsCount, speed.wheelRevolutionsTime);
}
@@ -74,7 +74,7 @@ public final class SensorDataCycling {
@NonNull
@Override
public String toString() {
return "cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
return super.toString() + " cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
}
@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 wheelRevolutionsTime; // UINT16; 1/1024s
public Speed(String sensorAddress) {
public DistanceSpeed(String sensorAddress) {
super(sensorAddress);
this.wheelRevolutionsCount = null;
this.wheelRevolutionsTime = null;
}
public Speed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
public DistanceSpeed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
super(sensorAddress, sensorName);
this.wheelRevolutionsCount = wheelRevolutionsCount;
this.wheelRevolutionsTime = wheelRevolutionsTime;
@@ -119,7 +119,7 @@ public final class SensorDataCycling {
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()) {
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
if (timeDiff_ms <= 0) {
@@ -127,43 +127,79 @@ public final class SensorDataCycling {
value = null;
} else {
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as it some of those seem to count backwards
double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S;
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
@Override
public String toString() {
return "speed=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
return super.toString() + " data=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Speed)) return false;
if (!(obj instanceof DistanceSpeed)) return false;
Speed comp = (Speed) obj;
DistanceSpeed comp = (DistanceSpeed) obj;
if (hasData() && comp.hasData() == hasData()) {
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
} else {
return false;
}
}
public static class Data {
public final float distance_m;
public final float distance_overall_m;
public final float speed_mps;
private Data(float distance_m, float distance_overall_m, float speed_mps) {
this.distance_m = distance_m;
this.distance_overall_m = distance_overall_m;
this.speed_mps = speed_mps;
}
@Override
public String toString() {
return "Data{" +
"distance_m=" + distance_m +
", distance_overall_m=" + distance_overall_m +
", speed_mps=" + speed_mps +
'}';
}
}
}
public static class CadenceAndSpeed extends SensorData<Pair<Cadence, Speed>> {
public static class CadenceAndSpeed extends SensorData<Pair<Cadence, DistanceSpeed>> {
public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) {
public CadenceAndSpeed(String sensorAddress, String sensorName, @Nullable Cadence cadence, @Nullable DistanceSpeed distanceSpeed) {
super(sensorAddress, sensorName);
this.value = new Pair<>(cadence, speed);
this.value = new Pair<>(cadence, distanceSpeed);
}
public Cadence getCadence() {
return this.value != null ? this.value.first : null;
}
public Speed getSpeed() {
public DistanceSpeed getDistanceSpeed() {
return this.value != null ? this.value.second : null;
}
}
@@ -16,6 +16,6 @@ public class SensorDataCyclingPower extends SensorData<Float> {
@NonNull
@Override
public String toString() {
return "power=" + value;
return super.toString() + " power=" + value;
}
}
@@ -16,6 +16,6 @@ public class SensorDataHeartRate extends SensorData<Float> {
@NonNull
@Override
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.Speed cyclingSpeed;
private SensorDataCycling.DistanceSpeed cyclingDistanceSpeed;
private SensorDataCyclingPower cyclingPower;
@@ -25,8 +25,8 @@ public final class SensorDataSet {
return cyclingCadence;
}
public SensorDataCycling.Speed getCyclingSpeed() {
return cyclingSpeed;
public SensorDataCycling.DistanceSpeed getCyclingDistanceSpeed() {
return cyclingDistanceSpeed;
}
public SensorDataCyclingPower getCyclingPower() {
@@ -44,7 +44,7 @@ public final class SensorDataSet {
public void clear() {
this.heartRate = null;
this.cyclingCadence = null;
this.cyclingSpeed = null;
this.cyclingDistanceSpeed = null;
this.cyclingPower = null;
}
@@ -57,8 +57,9 @@ public final class SensorDataSet {
trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue());
}
if (cyclingSpeed != null && cyclingSpeed.hasValue()) {
trackPoint.setSpeed(cyclingSpeed.getValue());
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m);
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps);
}
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
@Override
public String toString() {
return (getHeartRate() != null ? "" + getHeartRate() : "")
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "")
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
+ (getCyclingPower() != null ? " " + getCyclingPower() : "");
}
@@ -85,8 +93,8 @@ public final class SensorDataSet {
this.cyclingCadence = (SensorDataCycling.Cadence) data;
return;
}
if (type instanceof SensorDataCycling.Speed) {
this.cyclingSpeed = (SensorDataCycling.Speed) data;
if (type instanceof SensorDataCycling.DistanceSpeed) {
this.cyclingDistanceSpeed = (SensorDataCycling.DistanceSpeed) data;
return;
}
@@ -65,31 +65,28 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return chartFragment;
}
private final List<ChartPoint> pendingPoints = new ArrayList<>();
private SharedPreferences sharedPreferences;
private int recordingDistanceInterval;
private TrackDataHub trackDataHub;
// Stats gathered from the received data
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
private ChartBinding viewBinding;
//TODO REMOVE: Used to restore the TrackStatistics via TrackStatisticsUpdater; for the diagrams we should consider not this setting and just use all data that is in the database
@Deprecated
private int recordingDistanceInterval;
private final List<ChartPoint> pendingPoints = new ArrayList<>();
private String category = "";
// Modes of operation
private boolean chartByDistance;
private ChartBinding viewBinding;
// UI elements
private ChartView chartView;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
if (metricUnits != chartView.getMetricUnits()) {
chartView.setMetricUnits(metricUnits);
runOnUiThread(() -> {
@@ -100,7 +97,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
if (reportSpeed != chartView.getReportSpeed()) {
chartView.setReportSpeed(reportSpeed);
chartView.applyReportSpeed();
@@ -114,7 +111,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
if (PreferencesUtils.isKey(getContext(), R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(getContext());
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, getContext());
}
}
};
@@ -165,8 +162,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onResume() {
super.onResume();
resumeTrackDataHub();
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
checkChartSettings();
getActivity().runOnUiThread(updateChart);
@@ -176,7 +174,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onPause() {
super.onPause();
pauseTrackDataHub();
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -192,6 +190,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
viewBinding = null;
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
@Override
public void onTrackUpdated(Track track) {
if (isResumed()) {
@@ -201,7 +205,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
category = track.getCategory();
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
boolean reportSpeed = PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category);
if (reportSpeed != chartView.getReportSpeed()) {
chartView.setReportSpeed(reportSpeed);
chartView.applyReportSpeed();
@@ -44,14 +44,16 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
private IntervalStatisticsAdapter adapter;
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
private String category;
private IntervalListViewBinding viewBinding;
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(getContext());
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
if (adapter != null) {
adapter.notifyDataSetChanged();
spinnerAdapter.notifyDataSetChanged();
@@ -73,8 +75,9 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
metricUnits = PreferencesUtils.isMetricUnits(getContext());
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
@@ -125,6 +128,8 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
public void onPause() {
super.onPause();
pauseTrackDataHub();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -137,7 +142,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
public void onDestroy() {
super.onDestroy();
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
sharedPreferences = null;
adapter = null;
viewModel = null;
@@ -183,7 +188,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
category = track != null ? track.getCategory() : "";
// Set rate label.
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category); //TODO Handle sharedPreferenceChangeListener
viewBinding.intervalRate.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
}
});
@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.adapters.SensorsAdapter;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -64,7 +65,6 @@ public class StatisticsRecordedFragment extends Fragment {
return fragment;
}
private TrackStatistics trackStatistics;
private SensorStatistics sensorStatistics;
private SensorsAdapter sensorsAdapter;
@@ -152,26 +152,35 @@ public class StatisticsRecordedFragment extends Fragment {
getActivity().runOnUiThread(() -> {
if (isResumed()) {
Track track = contentProviderUtils.getTrack(trackId);
trackStatistics = track != null ? track.getTrackStatistics() : null;
trackStatistics = track.getTrackStatistics();
sensorStatistics = contentProviderUtils.getSensorStats(trackId);
String newCategory = track != null ? track.getCategory() : "";
String newCategory = track.getCategory();
if (!category.equals(newCategory)) {
category = newCategory;
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
}
loadTrackDescription(track);
updateUI();
updateSensorUI();
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon, viewBinding.statsNameValue);
}
});
}
}
private void loadTrackDescription(@NonNull Track track) {
viewBinding.statsNameValue.setText(track.getName());
viewBinding.statsDescriptionValue.setText(track.getDescription());
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(getContext(), trackStatistics.getStartTime()));
}
private void updateUI() {
// Set total distance
{
double totalDistance = trackStatistics == null ? Double.NaN : trackStatistics.getTotalDistance();
double totalDistance = trackStatistics.getTotalDistance();
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
viewBinding.statsDistanceValue.setText(parts.first);
@@ -185,14 +194,14 @@ public class StatisticsRecordedFragment extends Fragment {
}
// Set time and start datetime
if (trackStatistics != null) {
{
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()));
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
}
// Set average speed/pace
{
double speed = trackStatistics != null ? trackStatistics.getAverageSpeed() : Double.NaN;
double speed = trackStatistics.getAverageSpeed();
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
@@ -202,7 +211,7 @@ public class StatisticsRecordedFragment extends Fragment {
// 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);
@@ -213,7 +222,7 @@ public class StatisticsRecordedFragment extends Fragment {
// 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);
@@ -225,11 +234,11 @@ public class StatisticsRecordedFragment extends Fragment {
// Set elevation gain and loss
{
// Make elevation visible?
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
boolean showElevation = PreferencesUtils.isShowStatsElevation(sharedPreferences, getContext());
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
Float elevationGain_m = trackStatistics != null ? trackStatistics.getTotalElevationGain() : null;
Float elevationLoss_m = trackStatistics != null ? trackStatistics.getTotalElevationLoss() : null;
Float elevationGain_m = trackStatistics.getTotalElevationGain();
Float elevationLoss_m = trackStatistics.getTotalElevationLoss();
Pair<String, String> parts;
@@ -62,6 +62,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private TrackPoint lastTrackPoint;
//TODO Should not be nullable
private TrackStatistics lastTrackStatistics;
private String category = "";
@@ -366,29 +367,26 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
private void setSpeedSensorData(SensorDataSet sensorDataSet) {
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed();
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
if (data.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() {
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
// Set total distance
{
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);
}
setTotalDistance(0);
// Set activity type
{
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
viewBinding.statsActivityTypeIcon.setEnabled(isSelectedTrackRecording());
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) {
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;
// List of tracks to be exported.
private final ArrayList<Track.Id> trackIds = new ArrayList<>();
private final ArrayList<Track> tracks = new ArrayList<>();
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
private final Handler conflictsHandler = new Handler();
@@ -187,7 +187,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
protected void onDestroy() {
super.onDestroy();
conflictsQueue.clear();
trackIds.clear();
tracks.clear();
}
@Override
@@ -215,11 +215,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
for (int i = from; i < trackExportTotalCount; i++) {
cursor.moveToPosition(i);
Track track = ContentProviderUtils.createTrack(cursor);
trackIds.add(track.getId());
tracks.add(track);
}
if (!trackIds.isEmpty()) {
export(trackIds.get(0));
if (!tracks.isEmpty()) {
export(tracks.get(0));
} else {
onExportEnded();
}
@@ -227,26 +227,26 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
}
/**
* Enqueue track identified by trackId to be exported if not exported already or there is a conflict resolution.
* Enqueue track identified by UUID to be exported if not exported already or there is a conflict resolution.
*
* @param trackId the track's id.
* @param track Track object.
* @param conflictResolution conflict resolution to be applied if needed.
*/
private void export(Track.Id trackId, int conflictResolution) {
boolean fileExists = ExportUtils.isExportFileExists(trackId, trackFileFormat.getExtension(), directoryFiles);
private void export(Track track, int conflictResolution) {
boolean fileExists = ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles);
if (fileExists && conflictResolution == CONFLICT_NONE) {
conflict(trackId);
conflict(track);
} else if (fileExists && conflictResolution == CONFLICT_SKIP) {
trackExportSkippedCount++;
onExportCompleted(trackId);
onExportCompleted(track);
} else {
ExportService.enqueue(this, resultReceiver, trackId, trackFileFormat, directoryUri);
ExportService.enqueue(this, resultReceiver, track.getId(), trackFileFormat, directoryUri);
}
}
private void export(Track.Id trackId) {
export(trackId, autoConflict);
private void export(Track track) {
export(track, autoConflict);
}
private void setConflictVisibility(int visibility) {
@@ -278,14 +278,15 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
viewBinding.exportProgressSummaryErrorsGroup.setVisibility(trackExportErrorCount > 0 ? View.VISIBLE : View.GONE);
}
private void onExportCompleted(Track.Id trackId) {
trackIds.remove(trackId);
private void onExportCompleted(Track track) {
tracks.remove(track);
setProgress();
if (trackIds.isEmpty()) {
if (tracks.isEmpty()) {
onExportEnded();
return;
}
export(trackIds.get(0));
export(tracks.get(0));
}
private void onExportEnded() {
@@ -317,15 +318,15 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
}
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
Track track = contentProviderUtils.getTrack(trackId);
switch (resultCode) {
case ExportServiceResultReceiver.RESULT_CODE_ERROR:
trackExportErrorCount++;
Track track = contentProviderUtils.getTrack(trackId);
trackErrors.add(track.getName());
break;
case ExportServiceResultReceiver.RESULT_CODE_SUCCESS:
if (ExportUtils.isExportFileExists(trackId, trackFileFormat.getExtension(), directoryFiles)) {
if (ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles)) {
trackExportOverwrittenCount++;
} else {
trackExportSuccessCount++;
@@ -335,11 +336,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
throw new RuntimeException(TAG + ": export service result code invalid: " + resultCode);
}
onExportCompleted(trackId);
onExportCompleted(track);
}
private void conflict(Track.Id trackId) {
PendingConflict newConflict = new PendingConflict(trackId);
private void conflict(Track track) {
PendingConflict newConflict = new PendingConflict(track);
conflictsQueue.add(newConflict);
if (conflictsQueue.size() == 1) {
@@ -351,10 +352,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
* Handle conflicts (exporting file already exists).
*/
private class PendingConflict {
private final Track.Id trackId;
private final Track track;
public PendingConflict(Track.Id trackId) {
this.trackId = trackId;
public PendingConflict(Track track) {
this.track = track;
}
/**
@@ -365,14 +366,13 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
*/
public boolean resolve() {
if (autoConflict == CONFLICT_NONE) {
Track track = contentProviderUtils.getTrack(trackId);
viewBinding.exportProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(ExportActivity.this, R.drawable.ic_report_problem_24));
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, track.getName()));
setConflictVisibility(View.VISIBLE);
return false;
}
export(trackId);
export(track);
return true;
}
@@ -380,7 +380,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
* Overwrite the export file and set the autoConflict if user set the "do it for all" switch button.
*/
public void overwrite() {
export(trackId, CONFLICT_OVERWRITE);
export(track, CONFLICT_OVERWRITE);
if (viewBinding.exportProgressApplyToAll.isChecked()) {
autoConflict = CONFLICT_OVERWRITE;
@@ -391,7 +391,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
* Skip the export file and set the autoConflict if user set the "do it for all" switch button.
*/
public void skip() {
export(trackId, CONFLICT_SKIP);
export(track, CONFLICT_SKIP);
if (viewBinding.exportProgressApplyToAll.isChecked()) {
autoConflict = CONFLICT_SKIP;
@@ -49,8 +49,10 @@ public class GPXTrackExporter implements TrackExporter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat SPEED_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat DISTANCE_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat CADENCE_FORMAT = NumberFormat.getInstance(Locale.US);
private static final NumberFormat POWER_FORMAT = NumberFormat.getInstance(Locale.US);
static {
/*
@@ -72,6 +74,9 @@ public class GPXTrackExporter implements TrackExporter {
CADENCE_FORMAT.setMaximumFractionDigits(0);
CADENCE_FORMAT.setGroupingUsed(false);
POWER_FORMAT.setMaximumFractionDigits(0);
POWER_FORMAT.setGroupingUsed(false);
}
private final ContentProviderUtils contentProviderUtils;
@@ -91,15 +96,16 @@ public class GPXTrackExporter implements TrackExporter {
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
Track mainTrack = tracks[0];
try {
prepare(outputStream);
writeHeader(mainTrack);
writeMarkers(mainTrack);
writeHeader();
for (Track track : tracks) {
writeLocations(track);
writeMarkers(track);
}
for (Track track : tracks) {
writeTrackPoints(track);
}
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 wroteSegment = false;
@@ -183,7 +189,7 @@ public class GPXTrackExporter implements TrackExporter {
}
public void writeHeader(Track track) {
public void writeHeader() {
if (printWriter != null) {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
printWriter.println("<gpx");
@@ -195,17 +201,13 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\"");
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
printWriter.println("xmlns:pwr=\"http://www.garmin.com/xmlschemas/PowerExtension/v1\"");
printWriter.println("xsi:schemaLocation=" +
"\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
+ " http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd"
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
printWriter.println("<metadata>");
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
printWriter.println("</metadata>");
}
}
@@ -299,6 +301,10 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
}
if (trackPoint.hasPower()) {
printWriter.println("<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower()) + "</pwr:PowerInWatts>");
}
if (trackPoint.hasElevationGain()) {
printWriter.println("<opentracks:gain>" + ELEVATION_FORMAT.format(trackPoint.getElevationGain()) + "</opentracks:gain>");
}
@@ -307,6 +313,10 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<opentracks:loss>" + ELEVATION_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:loss>");
}
if (trackPoint.hasSensorDistance()) {
printWriter.println("<opentracks:distance>" + DISTANCE_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:distance>");
}
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
}
@@ -57,6 +57,7 @@ public class KMLTrackExporter implements TrackExporter {
private static final String SCHEMA_ID = "schema";
public static final String EXTENDED_DATA_TYPE_SPEED = "speed";
public static final String EXTENDED_DATA_TYPE_DISTANCE = "distance";
public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence";
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
public static final String EXTENDED_DATA_TYPE_POWER = "power";
@@ -74,6 +75,7 @@ public class KMLTrackExporter implements TrackExporter {
private PrintWriter printWriter;
private final List<Float> speedList = new ArrayList<>();
private final List<Float> distanceList = new ArrayList<>();
private final List<Float> powerList = new ArrayList<>();
private final List<Float> cadenceList = new ArrayList<>();
private final List<Float> heartRateList = new ArrayList<>();
@@ -338,6 +340,7 @@ public class KMLTrackExporter implements TrackExporter {
if (printWriter != null) {
printWriter.println("<gx:Track>");
speedList.clear();
distanceList.clear();
powerList.clear();
cadenceList.clear();
heartRateList.clear();
@@ -355,6 +358,9 @@ public class KMLTrackExporter implements TrackExporter {
writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED);
}
if (exportSensorData) {
if (distanceList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(distanceList, EXTENDED_DATA_TYPE_DISTANCE);
}
if (powerList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER);
}
@@ -392,6 +398,7 @@ public class KMLTrackExporter implements TrackExporter {
speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null);
if (exportSensorData) {
distanceList.add(trackPoint.hasSensorDistance() ? trackPoint.getSensorDistance() : null);
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri;
import android.util.Log;
@@ -90,6 +91,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
protected String uuid;
protected String gain;
protected String loss;
protected String distance;
// The current track data
private TrackData trackData;
@@ -97,10 +99,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The SAX locator to get the current line information
private Locator locator;
private final SharedPreferences sharedPreferences;
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
this.context = context;
this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
}
@Override
@@ -120,10 +125,11 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
@Override
public Track.Id importFile(InputStream inputStream) {
@NonNull
public List<Track.Id> importFile(InputStream inputStream) {
try {
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, this);
return trackIds.get(0);
return trackIds;
} catch (IOException | SAXException | ParserConfigurationException | ParsingException e) {
Log.e(TAG, "Unable to import file", e);
if (trackIds.size() > 0) {
@@ -136,9 +142,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
}
/**
* On file end.
*/
protected void onFileEnd() {
// Add markers to the last imported track
int size = trackIds.size();
@@ -261,7 +264,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
Track track = contentProviderUtils.getTrack(trackData.track.getUuid());
if (track != null) {
if (PreferencesUtils.getPreventReimportTracks(context)) {
if (PreferencesUtils.getPreventReimportTracks(sharedPreferences, context)) {
throw new ImportAlreadyExistsException(context.getString(R.string.import_prevent_reimport));
}
@@ -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.
*/
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);
}
}
@@ -379,8 +382,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
double distanceToLastTrackLocation = trackPoint.distanceTo(trackData.lastLocationInCurrentSegment);
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
double distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
if (distanceToLastTrackLocation > maxRecordingDistance) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
}
@@ -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) {
try {
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);
}
}
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;
}
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
@@ -18,10 +19,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
protected SharedPreferences sharedPreferences;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
if (directoryUri == null) {
@@ -38,6 +43,12 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
onActivityResultCustom(requestCode, resultCode, resultData);
}
@Override
protected void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) {
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
@@ -80,14 +91,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
return PreferencesUtils.getDefaultExportDirectoryUri(this);
return PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this);
}
@Override
protected Intent createNextActivityIntent(Uri directoryUri) {
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(this));
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(sharedPreferences, this));
return intent;
}
}
@@ -101,11 +112,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
case RESULT_OK:
Uri directoryUri = resultData.getData();
PreferencesUtils.setDefaultExportDirectoryUri(this, directoryUri);
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, directoryUri);
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), resultData, directoryUri);
break;
case RESULT_CANCELED:
PreferencesUtils.setDefaultExportDirectoryUri(this, null);
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, null);
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
break;
}
@@ -118,8 +129,8 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (PreferencesUtils.isDefaultExportDirectoryUri(this)) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri());
if (PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, this)) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this).getUri());
}
return null;
}
@@ -28,7 +28,9 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
/**
* Imports a GPX file.
* Uses https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd
* Uses:
* * https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd
* * https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd
* <p>
* {@link de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter} does not export information if a segment was started automatic or manually.
* Therefore, all segments starts are marked as SEGMENT_START_AUTOMATIC.
@@ -57,9 +59,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
private static final String TAG_EXTENSION_GAIN = "opentracks:gain";
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
/**
* Constructor.
@@ -156,6 +160,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
cadence = content.trim();
}
break;
case TAG_EXTENSION_POWER:
if (content != null) {
power = content.trim();
}
break;
case TAG_ID:
if (content != null) {
uuid = content.trim();
@@ -171,6 +180,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
loss = content.trim();
}
break;
case TAG_EXTENSION_DISTANCE:
if (content != null) {
distance = content.trim();
}
break;
}
// Reset element content
@@ -196,11 +210,12 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
altitude = null;
time = null;
speed = null;
power = null;
gain = null;
loss = null;
}
private void onTrackPointEnd() throws SAXException {
private void onTrackPointEnd() {
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
TrackPoint trackPoint = getTrackPoint();
if (isFirstTrackPointInSegment) {
@@ -221,7 +236,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
markerType = null;
}
private void onMarkerEnd() throws SAXException {
private void onMarkerEnd() {
addMarker();
}
}
@@ -16,6 +16,7 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -30,6 +31,10 @@ import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
@@ -47,17 +52,15 @@ public class ImportActivity extends FragmentActivity {
public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
private static final String BUNDLE_TOOLBAR_TITLE = "toolbar_title";
private static final String BUNDLE_DOCUMENT_URI = "document_uri";
private static final String BUNDLE_DOCUMENT_URIS = "document_uris";
private static final String BUNDLE_IS_DIRECTORY = "is_directory";
private ImportActivityBinding viewBinding;
boolean doubleBackToCancel = false;
private Uri documentUri;
private ArrayList<Uri> documentUris = new ArrayList<>();
private boolean isDirectory;
private String toolbarTitle;
private ImportViewModel viewModel;
private ImportViewModel.Summary summary;
@@ -70,35 +73,46 @@ public class ImportActivity extends FragmentActivity {
Toolbar toolbar = findViewById(R.id.toolbar);
final DocumentFile documentFile;
final Intent intent = getIntent();
final ClipData intentClipData = intent.getClipData();
if (savedInstanceState == null) {
if (getIntent().getData() != null) {
documentUri = getIntent().getData();
isDirectory = false;
} else if (getIntent().getClipData() != null && getIntent().getClipData().getItemCount() > 0) {
documentUri = getIntent().getClipData().getItemAt(0).getUri();
if (intent.getData() != null) {
documentUris.add(intent.getData());
isDirectory = false;
} else {
// Started from DirectoryChooserActivity
documentUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
isDirectory = true;
if (intentClipData != null && intentClipData.getItemCount() > 0) {
for (int i = 0; i < intentClipData.getItemCount(); i++) {
documentUris.add(intentClipData.getItemAt(i).getUri());
}
isDirectory = false;
} else {
// Started from DirectoryChooserActivity
documentUris.add(intent.getParcelableExtra(EXTRA_DIRECTORY_URI_KEY));
isDirectory = true;
}
}
documentFile = isDirectory ? DocumentFile.fromTreeUri(this, documentUri) : DocumentFile.fromSingleUri(this, documentUri);
toolbarTitle = getString(R.string.import_progress_message, documentFile.getName());
} else {
documentUri = savedInstanceState.getParcelable(BUNDLE_DOCUMENT_URI);
toolbarTitle = savedInstanceState.getString(BUNDLE_TOOLBAR_TITLE);
documentUris = savedInstanceState.getParcelable(BUNDLE_DOCUMENT_URIS);
isDirectory = savedInstanceState.getBoolean(BUNDLE_IS_DIRECTORY);
documentFile = isDirectory ? DocumentFile.fromTreeUri(this, documentUri) : DocumentFile.fromSingleUri(this, documentUri);
}
toolbar.setTitle(toolbarTitle);
final List<DocumentFile> documentFiles;
if (isDirectory) {
documentFiles = new ArrayList<>();
documentFiles.add(DocumentFile.fromTreeUri(this, documentUris.get(0)));
} else {
documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList());
}
//Works for a directory, but we might have received multiple files via SEND_MULTIPLE.
toolbar.setTitle(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
initViews();
viewModel = new ViewModelProvider(this).get(ImportViewModel.class);
viewModel.getImportData(documentFile).observe(this, data -> {
viewModel.getImportData(documentFiles).observe(this, data -> {
summary = data;
setProgress();
});
@@ -107,9 +121,8 @@ public class ImportActivity extends FragmentActivity {
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_DOCUMENT_URI, documentUri);
outState.putParcelableArrayList(BUNDLE_DOCUMENT_URIS, documentUris);
outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory);
outState.putString(BUNDLE_TOOLBAR_TITLE, toolbarTitle);
}
@Override
@@ -14,6 +14,7 @@ import androidx.documentfile.provider.DocumentFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
@@ -62,11 +63,12 @@ public class ImportService extends JobIntentService {
}
try (InputStream inputStream = getContentResolver().openInputStream(file.getUri())) {
Track.Id trackId = trackImporter.importFile(inputStream);
if (trackId != null) {
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackId, file, getString(R.string.import_file_imported, file.getName()));
ArrayList<Track.Id> trackIds = new ArrayList<>(trackImporter.importFile(inputStream));
if (!trackIds.isEmpty()) {
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName()));
} else {
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackId, file, getString(R.string.import_unable_to_import_file, file.getName()));
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackIds, file, getString(R.string.import_unable_to_import_file, file.getName()));
}
} catch (IOException e) {
Log.d(TAG, "Unable to import file", e);
@@ -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.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_MESSAGE, message);
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_ALREADY_EXISTS = 2;
public static final String RESULT_EXTRA_TRACK_ID = "result_track_id";
public static final String RESULT_EXTRA_LIST_TRACK_ID = "result_track_id";
public static final String RESULT_EXTRA_FILENAME = "result_extra_filename";
public static final String RESULT_EXTRA_MESSAGE = "result_extra_message";
@@ -11,7 +11,9 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
@@ -33,20 +35,20 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
summary = new Summary();
}
public LiveData<Summary> getImportData(DocumentFile documentFile) {
LiveData<Summary> getImportData(List<DocumentFile> documentFiles) {
if (importData == null) {
importData = new MutableLiveData<>();
loadData(documentFile);
loadData(documentFiles);
}
return importData;
}
public void cancel() {
void cancel() {
cancel = true;
}
private void loadData(DocumentFile documentFile) {
List<DocumentFile> fileList = FileUtils.getFiles(documentFile);
private void loadData(List<DocumentFile> documentFiles) {
List<DocumentFile> fileList = documentFiles.stream().map(FileUtils::getFiles).flatMap(Collection::stream).collect(Collectors.toList());
summary.totalCount = fileList.size();
filesToImport.addAll(fileList);
importNextFile();
@@ -66,7 +68,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
throw new RuntimeException(TAG + ": onReceiveResult resultData NULL");
}
Track.Id trackId = resultData.getParcelable(ImportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
ArrayList<Track.Id> trackIds = resultData.getParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID);
String fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME);
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
@@ -76,7 +78,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
break;
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED:
summary.importedTrackIds.add(trackId);
summary.importedTrackIds.addAll(trackIds);
summary.successCount++;
break;
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
@@ -90,7 +92,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
importNextFile();
}
public static class Summary {
static class Summary {
private int totalCount;
private int successCount;
private int existsCount;
@@ -35,6 +35,7 @@ import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
*
* @author Jimmy Shih
*/
//TODO Do not use AbstractFileTrackImporter as TrackPoint sensor generation is done differently here.
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
@@ -65,6 +66,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private String extendedDataType;
private final ArrayList<TrackPoint> trackPoints = new ArrayList<>();
private final ArrayList<Float> speedList = new ArrayList<>();
private final ArrayList<Float> distanceList = new ArrayList<>();
private final ArrayList<Float> cadenceList = new ArrayList<>();
private final ArrayList<Float> heartRateList = new ArrayList<>();
private final ArrayList<Float> powerList = new ArrayList<>();
@@ -215,6 +217,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
super.onTrackSegmentStart();
trackPoints.clear();
speedList.clear();
distanceList.clear();
heartRateList.clear();
cadenceList.clear();
powerList.clear();
@@ -231,6 +234,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (i < speedList.size()) {
trackPoint.setSpeed(speedList.get(i));
}
if (i < distanceList.size()) {
trackPoint.setSensorDistance(distanceList.get(i));
}
if (i < heartRateList.size()) {
trackPoint.setHeartRate_bpm(heartRateList.get(i));
}
@@ -309,6 +315,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED:
speedList.add(value);
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE:
distanceList.add(value);
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER:
powerList.add(value);
break;
@@ -20,6 +20,8 @@ import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -66,17 +68,23 @@ public class KmzTrackImporter implements TrackImporter {
}
@Override
public Track.Id importFile(InputStream inputStream) {
Track.Id trackId = findAndParseKmlFile(inputStream);
public @NonNull
List<Track.Id> importFile(InputStream inputStream) {
List<Track.Id> trackIds = findAndParseKmlFile(inputStream);
if (!copyKmzImages(trackId)) {
cleanImport(context, trackId);
return null;
ArrayList<Track.Id> trackIdsWithImages = new ArrayList<>();
for (Track.Id trackId : trackIds) {
if (copyKmzImages(trackId)) {
trackIdsWithImages.add(trackId);
deleteOrphanImages(context, trackId);
} else {
cleanImport(context, trackId);
return new ArrayList<>();
}
}
deleteOrphanImages(context, trackId);
return trackId;
return trackIdsWithImages;
}
/**
@@ -150,14 +158,13 @@ public class KmzTrackImporter implements TrackImporter {
/**
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it.
* TODO: May load multiple tracks, but only returns the last Track.Id.
*
* @param inputStream kmz input stream.
*/
private Track.Id findAndParseKmlFile(InputStream inputStream) {
private List<Track.Id> findAndParseKmlFile(InputStream inputStream) {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
Track.Id trackId = null;
ArrayList<Track.Id> trackIds = new ArrayList<>();
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (Thread.interrupted()) {
@@ -167,20 +174,21 @@ public class KmzTrackImporter implements TrackImporter {
String fileName = zipEntry.getName();
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
trackId = parseKml(zipInputStream);
if (trackId == null) {
List<Track.Id> trackId = parseKml(zipInputStream);
if (trackId.isEmpty()) {
Log.d(TAG, "Unable to parse kml in kmz");
throw new ImportParserException(context.getString(R.string.import_unable_to_import_file, fileName));
}
trackIds.addAll(trackId);
}
zipInputStream.closeEntry();
}
if (trackId == null) {
if (trackIds.isEmpty()) {
Log.d(TAG, "Unable to find doc.kml in kmz");
throw new ImportParserException(context.getString(R.string.import_no_kml_file_found));
}
return trackId;
return trackIds;
} catch (ImportParserException | ImportAlreadyExistsException e) {
Log.e(TAG, "Unable to import file", e);
throw e;
@@ -234,13 +242,7 @@ public class KmzTrackImporter implements TrackImporter {
}
}
/**
* Parses kml
*
* @param zipInputStream the zip input stream
* @return the imported track id or -1L
*/
private Track.Id parseKml(ZipInputStream zipInputStream) {
private List<Track.Id> parseKml(ZipInputStream zipInputStream) {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
@@ -15,7 +15,10 @@
*/
package de.dennisguse.opentracks.io.file.importer;
import androidx.annotation.NonNull;
import java.io.InputStream;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
@@ -30,8 +33,7 @@ public interface TrackImporter {
* Import a file.
*
* @param inputStream the file's input stream
* @return the imported track id or null.
*/
//TODO Figure out how can make the import an atomic operation (incl. database transaction rollback).
Track.Id importFile(InputStream inputStream);
@NonNull
List<Track.Id> importFile(InputStream inputStream) throws ImportParserException;
}
@@ -82,39 +82,40 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private PeriodicTaskExecutor voiceExecutor;
private TrackRecordingServiceNotificationManager notificationManager;
private SharedPreferences sharedPreferences;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private int recordingDistanceInterval;
private int maxRecordingDistance;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) {
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
if (PreferencesUtils.isRecording(TrackRecordingService.this)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingService.this);
if (PreferencesUtils.isRecording(sharedPreferences, TrackRecordingService.this)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackRecordingService.this);
}
}
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context);
}
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
voiceExecutor.setMetricUnits(metricUnits);
notificationManager.setMetricUnits(metricUnits);
}
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(sharedPreferences, context));
}
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
}
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
}
handlerServer.onSharedPreferenceChanged(context, preferences, key);
handlerServer.onSharedPreferenceChanged(context, sharedPreferences, key);
}
};
@@ -147,8 +148,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = null;
PreferencesUtils.register(this, sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
restartTrackAfterServiceRestart();
}
@@ -189,8 +191,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Reverse order from onCreate
showNotification(false); //TODO Why?
PreferencesUtils.unregister(this, sharedPreferenceChangeListener);
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferences = null;
try {
voiceExecutor.shutdown();
@@ -305,7 +307,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
//TODO Pass TrackPoint
track.setName(TrackNameUtils.getTrackName(this, trackId, segmentStartTrackPoint.getTime()));
String category = PreferencesUtils.getDefaultActivity(this);
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, this);
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(this, category));
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
@@ -536,9 +538,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private void updateRecordingState(Track.Id trackId, boolean paused) {
recordingTrackId = trackId;
long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
PreferencesUtils.setLong(this, R.string.recording_track_id_key, currentTrackId);
PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, currentTrackId);
recordingTrackPaused = paused;
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
PreferencesUtils.setBoolean(sharedPreferences, this, R.string.recording_track_paused_key, recordingTrackPaused);
}
@Override
@@ -556,7 +558,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
fillWithSensorDataSet(trackPoint);
notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy);
notificationManager.updateTrackPoint(this, track.getTrackStatistics(), trackPoint, recordingGpsAccuracy);
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
@@ -570,7 +572,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return;
}
double distanceToLastTrackLocation = trackPoint.distanceTo(lastValidTrackPoint);
double distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
if (distanceToLastTrackLocation > maxRecordingDistance) {
insertTrackPointIfNewer(track, lastTrackPoint);
@@ -663,6 +665,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
trackPoint.setElevationLoss(elevationSumManager.getElevationLoss_m());
elevationSumManager.reset();
}
if (remoteSensorManager != null) {
fillWithSensorDataSet(trackPoint);
remoteSensorManager.reset();
}
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
@@ -209,9 +210,10 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
try {
if (showEditor) {
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context);
trackRecordingService.endCurrentTrack();
if (PreferencesUtils.isRecording(context)) {
if (PreferencesUtils.isRecording(sharedPreferences, context)) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId)
.putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true);
@@ -12,7 +12,7 @@ import androidx.core.app.NotificationCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
/**
@@ -30,6 +30,8 @@ class TrackRecordingServiceNotificationManager {
private boolean previousLocationWasAccurate = true;
private Boolean metricUnits = null;
TrackRecordingServiceNotificationManager(Context context) {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -59,14 +61,14 @@ class TrackRecordingServiceNotificationManager {
}
void updateContent(String content) {
notificationBuilder.setContentText(content);
notificationBuilder.setSubText(content);
updateNotification();
}
void updateTrackPoint(Context context, TrackPoint trackPoint, int recordingGpsAccuracy) {
void updateTrackPoint(Context context, TrackStatistics trackStatistics, TrackPoint trackPoint, int recordingGpsAccuracy) {
String formattedAccuracy = context.getString(R.string.value_none);
if (trackPoint.hasAccuracy()) {
formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getAccuracy(), PreferencesUtils.isMetricUnits(context));
formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getAccuracy(), metricUnits);
boolean currentLocationWasAccurate = trackPoint.getAccuracy() < recordingGpsAccuracy;
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
@@ -74,7 +76,9 @@ class TrackRecordingServiceNotificationManager {
previousLocationWasAccurate = currentLocationWasAccurate;
}
notificationBuilder.setContentText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, StringUtils.formatDistance(context, trackStatistics.getTotalDistance(), metricUnits)));
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, StringUtils.formatSpeed(context, trackPoint.getSpeed(), metricUnits, true)));
notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
updateNotification();
notificationBuilder.setOnlyAlertOnce(true);
@@ -93,6 +97,10 @@ class TrackRecordingServiceNotificationManager {
return notificationBuilder.build();
}
void setMetricUnits(boolean metricUnits) {
this.metricUnits = metricUnits;
}
private void updateNotification() {
notificationManager.notify(NOTIFICATION_ID, getNotification());
}
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.LocationManager;
import android.os.Handler;
@@ -15,6 +16,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* This class handle GPS status according to received locations and some thresholds.
*/
//TODO should handle sharedpreference changes
class GpsStatus {
private static final String TAG = GpsStatus.class.getSimpleName();
@@ -60,16 +62,16 @@ class GpsStatus {
private final Handler gpsStatusHandler;
private GpsStatusRunner gpsStatusRunner = null;
/**
* @param context The context object.
* @param client The client.
* @param minRecordingInterval Value of min recording interval preference.
*/
public GpsStatus(Context context, GpsStatusListener client, Duration minRecordingInterval) {
public GpsStatus(Context context, GpsStatusListener client) {
this.client = client;
this.context = context;
signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(context);
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
Duration minRecordingInterval = Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context));
signalLostThreshold = !minRecordingInterval.isNegative() ? SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval) : SIGNAL_LOST_THRESHOLD;
gpsStatusHandler = new Handler();
}
@@ -3,12 +3,14 @@ package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
public class HandlerServer {
@@ -32,8 +34,9 @@ public class HandlerServer {
public void start(Context context) {
serviceExecutor = Executors.newSingleThreadExecutor();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
locationHandler.onStart(context);
locationHandler.onSharedPreferenceChanged(context, null, null);
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, null);
}
public void stop(Context context) {
@@ -45,7 +48,7 @@ public class HandlerServer {
serviceExecutor = null;
}
public void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences preferences, String key) {
locationHandler.onSharedPreferenceChanged(context, preferences, key);
}
@@ -70,8 +73,10 @@ public class HandlerServer {
}
public interface Handler {
void onStart(Context context);
void onStop(Context context);
void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key);
void onStart(@NonNull Context context);
void onStop(@NonNull Context context);
void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences preferences, String key);
}
}
@@ -34,14 +34,14 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
}
@Override
public void onStart(Context context) {
gpsStatus = new GpsStatus(context, this, Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(context)));
public void onStart(@NonNull Context context) {
gpsStatus = new GpsStatus(context, this);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
registerLocationListener();
}
@Override
public void onStop(Context context) {
public void onStop(@NonNull Context context) {
unregisterLocationListener();
locationManager = null;
if (gpsStatus != null) {
@@ -51,9 +51,9 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
}
@Override
public void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
// Choose battery life over moving time accuracy.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(Duration.ofSeconds(30), Duration.ofSeconds(5), 5);
@@ -69,16 +69,16 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
}
}
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
}
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
if (gpsStatus != null) {
gpsStatus.onMinRecordingIntervalChanged(PreferencesUtils.getMinRecordingInterval(context));
gpsStatus.onMinRecordingIntervalChanged(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context));
}
}
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
if (gpsStatus != null) {
gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(context));
gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context));
}
}
}
@@ -101,11 +101,11 @@ public abstract class BluetoothConnectionManager {
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
String sensorName = gatt.getDevice().getName();
Log.d(TAG, "Received data from " + sensorName);
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress());
SensorData sensorData = parsePayload(sensorName, gatt.getDevice().getAddress(), characteristic);
SensorData<?> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
if (sensorData != null) {
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
observer.onChanged(sensorData);
}
}
@@ -125,7 +125,7 @@ public abstract class BluetoothConnectionManager {
Log.d(TAG, "Connecting to: " + device);
bluetoothGatt = device.connectGatt(context, true, connectCallback);
SensorData sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
SensorData<?> sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
observer.onChanged(sensorData);
}
@@ -148,12 +148,12 @@ public abstract class BluetoothConnectionManager {
return address.equals(bluetoothGatt.getDevice().getAddress());
}
protected abstract SensorData createEmptySensorData(String address);
protected abstract SensorData<?> createEmptySensorData(String address);
/**
* @return null if data could not be parsed.
*/
protected abstract SensorData parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
protected abstract SensorData<?> parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
public static class HeartRate extends BluetoothConnectionManager {
@@ -162,7 +162,7 @@ public abstract class BluetoothConnectionManager {
}
@Override
protected SensorData createEmptySensorData(String address) {
protected SensorDataHeartRate createEmptySensorData(String address) {
return new SensorDataHeartRate(address);
}
@@ -181,7 +181,7 @@ public abstract class BluetoothConnectionManager {
}
@Override
protected SensorData createEmptySensorData(String address) {
protected SensorDataCycling.Cadence createEmptySensorData(String address) {
return new SensorDataCycling.Cadence(address);
}
@@ -197,30 +197,36 @@ public abstract class BluetoothConnectionManager {
}
//Workaround for Wahoo CADENCE: this sensor reports speed (instead of cadence)
if (cadenceAndSpeed.getSpeed() != null) {
return new SensorDataCycling.Cadence(cadenceAndSpeed.getSpeed());
if (cadenceAndSpeed.getDistanceSpeed() != null) {
return new SensorDataCycling.Cadence(cadenceAndSpeed.getDistanceSpeed());
}
return null;
}
}
public static class CyclingSpeed extends BluetoothConnectionManager {
public static class CyclingDistanceSpeed extends BluetoothConnectionManager {
CyclingSpeed(SensorDataObserver observer) {
CyclingDistanceSpeed(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
}
@Override
protected SensorData createEmptySensorData(String address) {
return new SensorDataCycling.Speed(address);
protected SensorDataCycling.DistanceSpeed createEmptySensorData(String address) {
return new SensorDataCycling.DistanceSpeed(address);
}
@Override
protected SensorDataCycling.Speed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
protected SensorDataCycling.DistanceSpeed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed != null) {
return cadenceAndSpeed.getSpeed();
// Workaround for Garmin Speed Sensor 2: provides cadence instead of speed
SensorDataCycling.DistanceSpeed result = cadenceAndSpeed.getDistanceSpeed();
if (result == null && cadenceAndSpeed.getCadence() != null) {
result = new SensorDataCycling.DistanceSpeed(cadenceAndSpeed.getSensorAddress(), cadenceAndSpeed.getSensorName(), (int) cadenceAndSpeed.getCadence().getCrankRevolutionsCount(), cadenceAndSpeed.getCadence().getCrankRevolutionsTime());
}
return result;
}
return null;
}
@@ -233,7 +239,7 @@ public abstract class BluetoothConnectionManager {
}
@Override
protected SensorData createEmptySensorData(String address) {
protected SensorDataCyclingPower createEmptySensorData(String address) {
return new SensorDataCyclingPower(address);
}
@@ -247,8 +253,8 @@ public abstract class BluetoothConnectionManager {
interface SensorDataObserver {
void onChanged(SensorData sensorData);
void onChanged(SensorData<?> sensorData);
void onDisconnecting(SensorData sensorData);
void onDisconnecting(SensorData<?> sensorData);
}
}
@@ -53,43 +53,50 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
private final BluetoothAdapter bluetoothAdapter;
private final Context context;
private final SharedPreferences sharedPreferences;
private boolean started = false;
private final SharedPreferences sharedPreferences;
private int preferenceWheelCircumference;
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
private final BluetoothConnectionManager.CyclingSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingSpeed(this);
private final BluetoothConnectionManager.CyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingDistanceSpeed(this);
private final BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(this);
private final SensorDataSet sensorDataSet = new SensorDataSet();
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (!started) return;
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(sharedPreferences, context);
connect(heartRate, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(context);
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(sharedPreferences, context);
connect(cyclingCadence, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(context);
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(sharedPreferences, context);
connect(cyclingSpeed, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(context);
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(sharedPreferences, context);
connect(cyclingPower, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(sharedPreferences, context);
}
}
};
@@ -104,7 +111,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
//Trigger connection startup
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
}
public synchronized void stop() {
@@ -155,11 +162,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return sensorDataSet;
}
public void reset() {
sensorDataSet.reset();
}
@Override
public synchronized void onChanged(SensorData sensorData) {
public synchronized void onChanged(SensorData<?> sensorData) {
if (sensorData instanceof SensorDataCycling.Cadence) {
SensorDataCycling.Cadence previous = sensorDataSet.getCyclingCadence();
Log.d(TAG, "previous " + previous + "; current" + sensorData);
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: cadence data repeated.");
@@ -167,21 +178,21 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
}
((SensorDataCycling.Cadence) sensorData).compute(previous);
}
if (sensorData instanceof SensorDataCycling.Speed) {
SensorDataCycling.Speed previous = sensorDataSet.getCyclingSpeed();
Log.d(TAG, "previous " + previous + "; current" + sensorData);
if (sensorData instanceof SensorDataCycling.DistanceSpeed) {
SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: speed data repeated.");
return;
}
((SensorDataCycling.Speed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(context));
((SensorDataCycling.DistanceSpeed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(sharedPreferences, context));
}
sensorDataSet.set(sensorData);
}
@Override
public void onDisconnecting(SensorData sensorData) {
public void onDisconnecting(SensorData<?> sensorData) {
sensorDataSet.remove(sensorData);
}
}
@@ -17,17 +17,19 @@
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.AnnouncementUtils;
@@ -102,6 +104,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
}
};
private SharedPreferences sharedPreferences;
private TextToSpeech tts;
// Response from TTS after its initialization
private int ttsInitStatus = TextToSpeech.ERROR;
@@ -112,12 +116,15 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
this.context = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
contentProviderUtils = new ContentProviderUtils(context);
}
@Override
public void start() {
Log.d(TAG, "Start");
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
if (tts == null) {
tts = new TextToSpeech(context, status -> {
Log.i(TAG, "TextToSpeech initialized with status " + status);
@@ -164,14 +171,23 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
return;
}
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context));
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
String category = track != null ? track.getCategory() : "";
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(track.getId());
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, (float) (PreferencesUtils.isMetricUnits(context) ? 1d * UnitConversions.KM_TO_M : 1d * UnitConversions.MI_TO_M));
//TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else.
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
while (trackPointIterator.hasNext()) {
trackPoints.add(trackPointIterator.next());
}
boolean isMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, (float) (isMetricUnits ? 1d * UnitConversions.KM_TO_M : 1d * UnitConversions.MI_TO_M));
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, category, lastInterval);
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, isMetricUnits, isReportSpeed, lastInterval);
speakAnnouncement(announcement);
}
@@ -181,6 +197,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
tts.shutdown();
tts = null;
}
sharedPreferences = null;
}
private void onTtsReady() {
@@ -94,7 +94,7 @@ public class PeriodicTaskExecutor {
if (timerTaskExecutor == null) {
timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService);
}
timerTaskExecutor.scheduleTask(Duration.ofSeconds(taskFrequency));
timerTaskExecutor.scheduleTask(Duration.ofMinutes(taskFrequency));
} else {
// For distance periodic task
calculateNextTaskDistance();
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
@@ -48,7 +49,7 @@ public class ActivityTypePreference extends DialogPreference {
setDialogIcon(null);
setPersistent(true);
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), ActivityTypePreference.this.getContext());
setSummaryProvider(summaryProvider);
}
@@ -60,6 +61,8 @@ public class ActivityTypePreference extends DialogPreference {
public static class ActivityPreferenceDialog extends PreferenceDialogFragmentCompat {
private SharedPreferences sharedPreferences;
private AutoCompleteTextView textView;
private ImageView iconView;
@@ -77,9 +80,11 @@ public class ActivityTypePreference extends DialogPreference {
super.onBindDialogView(view);
final Context context = getActivity();
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
textView = view.findViewById(R.id.activity_type_preference_text_view);
String category = PreferencesUtils.getDefaultActivity(context);
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, context);
textView.setText(category);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
textView.setAdapter(adapter);
@@ -100,8 +105,14 @@ public class ActivityTypePreference extends DialogPreference {
updateIcon(TrackIconUtils.getIconValue(context, category));
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
private void showIconSelectDialog() {
String category = PreferencesUtils.getDefaultActivity(getActivity());
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, getActivity());
ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
}
@@ -110,7 +121,7 @@ public class ActivityTypePreference extends DialogPreference {
if (positiveResult) {
String newDefaultActivity = textView.getText().toString();
if (getPreference().callChangeListener(newDefaultActivity)) {
PreferencesUtils.setDefaultActivity(getActivity(), newDefaultActivity);
PreferencesUtils.setDefaultActivity(sharedPreferences, getActivity(), newDefaultActivity);
HackUtils.invalidatePreference(getPreference());
}
}
@@ -91,7 +91,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
public static class PrefsFragment extends PreferenceFragmentCompat {
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
private SharedPreferences sharedPreferences;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
getActivity().runOnUiThread(this::updateReset);
}
@@ -99,7 +101,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
getActivity().runOnUiThread(this::updateUnits);
}
if (PreferencesUtils.isKey(getActivity(), R.string.night_mode_key, key)) {
getActivity().runOnUiThread(() -> ActivityUtils.applyNightMode(getContext()));
getActivity().runOnUiThread(() -> ActivityUtils.applyNightMode(sharedPreferences, getContext()));
}
};
@@ -108,6 +110,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
try {
setPreferencesFromResource(R.xml.settings, rootKey);
} catch (ClassCastException e) {
@@ -123,7 +127,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key));
instantExportDirectoryPreference.setSummaryProvider(preference -> {
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(getContext());
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, getContext());
//Use same value for not set as Androidx ListPreference and EditTextPreference
return directory != null ? directory.getName() : getString(R.string.not_set);
});
@@ -132,7 +136,10 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
@Override
public void onResume() {
super.onResume();
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
updateUnits();
updateReset();
updateBluetooth();
@@ -142,13 +149,13 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
private void updatePostWorkoutExport() {
Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key));
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext()));
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, getContext()));
}
@Override
public void onPause() {
super.onPause();
PreferencesUtils.getSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -176,6 +183,12 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
super.onDisplayPreferenceDialog(preference);
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
public void setDefaultActivity(String iconValue) {
if (activityPreferenceDialog != null) {
activityPreferenceDialog.updateUI(iconValue);
@@ -183,7 +196,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
}
private void updateReset() {
final boolean isRecording = PreferencesUtils.isRecording(getActivity());
final boolean isRecording = PreferencesUtils.isRecording(sharedPreferences, getActivity());
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
resetPreference.setEnabled(!isRecording);
@@ -196,7 +209,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
}
private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity());
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
@@ -103,10 +103,15 @@ public class TrackStatisticsUpdater {
/**
* Adds a trackPoint.
*
* @param trackPoint the trackPoint
* @param minRecordingDistance the min recording distance
* @param trackPoint the trackPoint
* @param minGPSDistance the min recording distance
*/
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
public void addTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
internalAddTrackPoint(trackPoint, minGPSDistance);
Log.d(TAG, this.toString());
}
private void internalAddTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
if (!trackInitialized) {
trackStatistics.setStartTime(trackPoint.getTime());
trackInitialized = true;
@@ -120,19 +125,26 @@ public class TrackStatisticsUpdater {
currentSegment.setStopTime(trackPoint.getTime());
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));
if (trackPoint.isSegmentStart() || trackPoint.isSegmentEnd()) {
if (trackPoint.isSegmentEnd()) {
if (lastTrackPoint != null && lastMovingTrackPoint != null && lastTrackPoint != lastMovingTrackPoint) {
currentSegment.addTotalDistance(lastMovingTrackPoint.distanceTo(lastTrackPoint));
}
}
trackStatistics.merge(currentSegment);
currentSegment.reset(trackPoint.getTime());
if (trackPoint.isSegmentStart()) {
reset(trackPoint);
return;
}
lastTrackPoint = null;
lastMovingTrackPoint = null;
elevationBuffer_m.reset();
speedBuffer_ms.reset();
// Process sensor data
if (trackPoint.hasElevationGain()) {
currentSegment.addTotalElevationGain(trackPoint.getElevationGain());
}
if (trackPoint.hasElevationLoss()) {
currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss());
}
if (trackPoint.hasSensorDistance()) {
currentSegment.addTotalDistance(trackPoint.getSensorDistance());
}
if (trackPoint.isSegmentEnd()) {
reset(trackPoint);
return;
}
@@ -141,39 +153,30 @@ public class TrackStatisticsUpdater {
updateAbsoluteElevation(trackPoint.getAltitude());
}
//Get elevation gain
if (trackPoint.hasElevationGain()) {
currentSegment.addTotalElevationGain(trackPoint.getElevationGain());
Log.d(TAG, "elevation gain: " + trackPoint.getElevationGain());
}
//Get elevation loss
if (trackPoint.hasElevationLoss()) {
currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss());
Log.d(TAG, "elevation loss: " + trackPoint.getElevationLoss());
}
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
lastTrackPoint = trackPoint;
lastMovingTrackPoint = trackPoint;
return;
}
double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint);
if (movingDistance < minRecordingDistance && !trackPoint.isMoving()) {
speedBuffer_ms.reset();
lastTrackPoint = trackPoint;
return;
if (!trackPoint.hasSensorDistance()) {
// GPS-based distance/speed
float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
if (movingDistance < minGPSDistance && !trackPoint.isMoving()) {
speedBuffer_ms.reset();
lastTrackPoint = trackPoint;
return;
}
// Update total distance
currentSegment.addTotalDistance(movingDistance);
}
Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
if (movingTime.isNegative()) {
lastTrackPoint = trackPoint;
return;
}
// Update total distance
currentSegment.addTotalDistance(movingDistance);
// Update moving time
currentSegment.addMovingTime(movingTime);
@@ -186,6 +189,16 @@ public class TrackStatisticsUpdater {
lastMovingTrackPoint = trackPoint;
}
private void reset(TrackPoint trackPoint) {
trackStatistics.merge(currentSegment);
currentSegment.reset(trackPoint.getTime());
lastTrackPoint = null;
lastMovingTrackPoint = null;
elevationBuffer_m.reset();
speedBuffer_ms.reset();
}
public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
while (iterator.hasNext()) {
TrackPoint location = iterator.next();
@@ -224,20 +237,14 @@ public class TrackStatisticsUpdater {
/**
* Updates an elevation reading.
*
* @param elevation the elevation
* @return the difference
*/
@VisibleForTesting
private double updateAbsoluteElevation(double elevation) {
private void updateAbsoluteElevation(double elevation) {
// Update elevation using the smoothed average
double oldAverage = elevationBuffer_m.getAverage();
elevationBuffer_m.setNext(elevation);
double newAverage = elevationBuffer_m.getAverage();
currentSegment.updateElevationExtremities(newAverage);
return newAverage - oldAverage;
}
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
@@ -267,4 +274,11 @@ public class TrackStatisticsUpdater {
return true;
}
@Override
public String toString() {
return "TrackStatisticsUpdater{" +
"trackStatistics=" + trackStatistics +
'}';
}
}
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.util;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -150,7 +151,7 @@ public class ActivityUtils {
toast.show();
}
public static void applyNightMode(Context context) {
AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode(context));
public static void applyNightMode(SharedPreferences sharedPreferences, Context context) {
AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode(sharedPreferences, context));
}
}
@@ -15,9 +15,7 @@ public class AnnouncementUtils {
private AnnouncementUtils() {
}
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, String category, @Nullable IntervalStatistics.Interval currentInterval) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context, category);
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MPS_TO_KMH;
double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0;
@@ -26,7 +24,7 @@ public class AnnouncementUtils {
return context.getString(R.string.voice_total_distance_zero);
}
if (!metricUnits) {
if (!isMetricUnits) {
distance *= UnitConversions.KM_TO_MI;
distancePerTime *= UnitConversions.KM_TO_MI;
currentDistancePerTime *= UnitConversions.KM_TO_MI;
@@ -35,15 +33,15 @@ public class AnnouncementUtils {
String rate;
String currentRate;
String currentRateMsg;
if (reportSpeed) {
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
if (isReportSpeed) {
int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTime), currentDistancePerTime);
currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate);
} else {
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
int paceId = isMetricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
Duration time = Duration.ofMillis((long) (timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS));
rate = context.getString(paceId, getAnnounceTime(context, time));
@@ -53,7 +51,7 @@ public class AnnouncementUtils {
currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate);
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
@@ -65,8 +63,8 @@ public class AnnouncementUtils {
private static String getAnnounceTime(Context context, Duration duration) {
String result = "";
int hours = (int) (duration.getSeconds() / (60 * 60));
int minutes = (int) (duration.getSeconds() / 60);
int hours = (int) (duration.toHours());
int minutes = (int) (duration.toMinutes() % 60);
int seconds = (int) (duration.getSeconds() % 60);
if (hours != 0) {
@@ -126,12 +126,12 @@ public class BluetoothUtils {
cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime);
}
SensorDataCycling.Speed speed = null;
SensorDataCycling.DistanceSpeed speed = null;
if (hasWheel && valueLength - index >= 4) {
int wheelCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
index += 2;
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
speed = new SensorDataCycling.Speed(address, sensorName, wheelCount, wheelTime);
speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelCount, wheelTime);
}
return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed);
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.util;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
@@ -14,6 +15,8 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -26,9 +29,10 @@ public class ExportUtils {
private static final String TAG = ExportUtils.class.getSimpleName();
public static void postWorkoutExport(Context context, Track track, ExportServiceResultReceiver resultReceiver) {
if (PreferencesUtils.shouldInstantExportAfterWorkout(context)) {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(context);
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
if (PreferencesUtils.shouldInstantExportAfterWorkout(sharedPreferences, context)) {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, context);
ExportService.enqueue(context, resultReceiver, track.getId(), trackFileFormat, directory.getUri());
}
@@ -37,7 +41,7 @@ public class ExportUtils {
public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) {
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context);
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track.getId(), trackFileFormat, directory);
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory);
if (exportDocumentFileUri == null) {
Log.e(TAG, "Couldn't create document file for export");
return false;
@@ -62,8 +66,10 @@ public class ExportUtils {
}
}
public static boolean isExportFileExists(Track.Id trackId, String trackFileFormatExtension, List<String> filesName) {
return filesName.contains(getExportFileNameByTrackId(trackId, trackFileFormatExtension));
public static boolean isExportFileExists(UUID uuuid, String trackFileFormatExtension, List<String> filesName) {
String word = uuuid.toString().substring(0, 8) + ".*\\." + trackFileFormatExtension;
Pattern pattern = Pattern.compile(word);
return filesName.stream().anyMatch(w -> pattern.matcher(w).matches());
}
public static List<String> getAllFiles(Context context, Uri directoryUri) {
@@ -82,8 +88,8 @@ public class ExportUtils {
return fileNames;
}
private static Uri getExportDocumentFileUri(Context context, Track.Id trackId, TrackFileFormat trackFileFormat, DocumentFile directory) {
String exportFileName = getExportFileNameByTrackId(trackId, trackFileFormat.getExtension());
private static Uri getExportDocumentFileUri(Context context, Track track, TrackFileFormat trackFileFormat, DocumentFile directory) {
String exportFileName = getExportFileNameForTrack(track, trackFileFormat.getExtension());
Uri exportDocumentFileUri = findFile(context, directory.getUri(), exportFileName);
if (exportDocumentFileUri == null) {
final DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), exportFileName);
@@ -94,8 +100,8 @@ public class ExportUtils {
return exportDocumentFileUri;
}
private static String getExportFileNameByTrackId(Track.Id trackId, String trackFileFormatExtension) {
return trackId.getId() + "." + trackFileFormatExtension;
private static String getExportFileNameForTrack(Track track, String trackFileFormatExtension) {
return track.getUuid().toString().substring(0, 8) + "_" + track.getName() + "." + trackFileFormatExtension;
}
private static Uri findFile(Context context, Uri directoryUri, String exportFileName) {
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.util;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import androidx.annotation.NonNull;
@@ -73,11 +74,13 @@ public class IntentDashboardUtils {
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(context));
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(context));
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(sharedPreferences, context));
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, context));
intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
if (isRecording) {
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen(context));
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen(sharedPreferences, context));
}
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -91,7 +91,7 @@ public class IntentUtils {
continue;
}
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(context));
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(PreferencesUtils.getSharedPreferences(context), context));
uris.add(uriAndMime.first);
mime = uriAndMime.second;
}
@@ -42,37 +42,32 @@ public class PreferencesUtils {
private PreferencesUtils() {
}
@Deprecated //Should only be used to get a sharedPreference for more than one interaction!
public static SharedPreferences getSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
public static 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
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
//NOTE: This was also used to recover from a service restart, but this data should not be exposed to the whole application.
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
@Deprecated //Use the TrackRecordingService
public static Track.Id getRecordingTrackId(Context context) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
public static Track.Id getRecordingTrackId(Context context, SharedPreferences sharedPreferences) {
return new Track.Id(sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT));
}
public static String getDefaultActivity(Context context) {
return getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
@Deprecated //Use the TrackRecordingService
public static Track.Id getRecordingTrackId(SharedPreferences sharedPreferences, Context context) {
return getRecordingTrackId(context, sharedPreferences);
}
public static void setDefaultActivity(Context context, String newDefaultActivity) {
setString(context, R.string.default_activity_key, newDefaultActivity);
public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
}
public static void setDefaultActivity(SharedPreferences sharedPreferences, Context context, String newDefaultActivity) {
setString(sharedPreferences, context, R.string.default_activity_key, newDefaultActivity);
}
/**
@@ -96,48 +91,19 @@ public class PreferencesUtils {
return key == null || key.equals(getKey(context, keyId));
}
/**
* Gets a boolean preference value.
*
* @param context the context
* @param keyId the key id
* @param defaultValue the default value
*/
private static boolean getBoolean(Context context, int keyId, boolean defaultValue) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
private static boolean getBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean defaultValue) {
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
}
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.
@VisibleForTesting
public static void setBoolean(Context context, int keyId, boolean value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
public static void setBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean value) {
Editor editor = sharedPreferences.edit();
editor.putBoolean(getKey(context, keyId), value);
editor.apply();
}
/**
* Gets an integer preference value.
*
* @param context the context
* @param keyId the key id
* @param defaultValue the default value
*/
private static int getInt(Context context, int keyId, int defaultValue) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
try {
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
} catch (ClassCastException e) {
@@ -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
public static void setInt(Context context, int keyId, int value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
public static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) {
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(context, keyId), value);
editor.apply();
}
/**
* Sets a long preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
//TODO Don't use; this function is only to be used TrackRecordingService and will be removed.
@Deprecated
@VisibleForTesting
public static void setLong(Context context, int keyId, long value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
public static void setLong(SharedPreferences sharedPreferences, Context context, int keyId, long value) {
Editor editor = sharedPreferences.edit();
editor.putLong(getKey(context, keyId), value);
editor.apply();
}
@Deprecated
public static String getString(Context context, int keyId, String defaultValue) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
public static String getString(SharedPreferences sharedPreferences, Context context, int keyId, String defaultValue) {
return sharedPreferences.getString(getKey(context, keyId), defaultValue);
}
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
public static void setString(Context context, int keyId, String value) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
public static void setString(SharedPreferences sharedPreferences, Context context, int keyId, String value) {
Editor editor = sharedPreferences.edit();
editor.putString(getKey(context, keyId), value);
editor.apply();
}
@Deprecated
public static boolean isMetricUnits(Context context) {
return isMetricUnits(getSharedPreferences(context), context);
}
public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) {
final String STATS_UNIT = context.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(context, sharedPreferences, R.string.stats_units_key, STATS_UNIT));
}
@Deprecated
public static boolean isReportSpeed(Context context, String category) {
return isReportSpeed(getSharedPreferences(context), context, category);
return STATS_UNIT.equals(getString(sharedPreferences, context, R.string.stats_units_key, STATS_UNIT));
}
public static boolean isReportSpeed(SharedPreferences sharedPreferences, Context context, String category) {
final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
String currentStatsRate = getString(context, sharedPreferences, 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))) {
String currentStatsRate = getString(sharedPreferences, context, R.string.stats_rate_key, STATS_RATE_DEFAULT);
if (currentStatsRate.equals(getString(sharedPreferences, context, R.string.stats_rate_speed_or_pace_default, STATS_RATE_DEFAULT))) {
return TrackIconUtils.isSpeedIcon(context, category);
}
@@ -236,8 +162,8 @@ public class PreferencesUtils {
}
@Deprecated //Use TrackRecordingService
public static boolean isRecordingTrackPaused(Context context) {
return getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
public static boolean isRecordingTrackPaused(SharedPreferences sharedPreferences, Context context) {
return getBoolean(sharedPreferences, context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
}
@Deprecated //Use TrackRecordingService
@@ -246,9 +172,9 @@ public class PreferencesUtils {
}
@Deprecated //Use TrackRecordingService
public static void defaultRecordingTrackPaused(Context context) {
public static void defaultRecordingTrackPaused(SharedPreferences sharedPreferences, Context context) {
final boolean RECORDING_TRACK_PAUSED = context.getResources().getBoolean(R.bool.recording_track_paused_default);
setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
}
private static String getBluetoothSensorAddressNone(Context context) {
@@ -259,83 +185,74 @@ public class PreferencesUtils {
return getBluetoothSensorAddressNone(context).equals(currentValue);
}
public static String getBluetoothHeartRateSensorAddress(Context context) {
return getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone(context));
public static String getBluetoothHeartRateSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone(context));
}
public static String getBluetoothCyclingCadenceSensorAddress(Context context) {
return getString(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone(context));
public static String getBluetoothCyclingCadenceSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone(context));
}
public static String getBluetoothCyclingSpeedSensorAddress(Context context) {
return getString(context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(context));
public static String getBluetoothCyclingSpeedSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(context));
}
public static int getWheelCircumference(Context context) {
public static int getWheelCircumference(SharedPreferences sharedPreferences, Context context) {
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
return getInt(context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT);
return getInt(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT);
}
public static boolean isBluetoothCyclingPowerSensorAddressNone(Context context) {
return isBluetoothSensorAddressNone(context, getBluetoothCyclingPowerSensorAddress(context));
public static String getBluetoothCyclingPowerSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
}
public static String getBluetoothCyclingPowerSensorAddress(Context context) {
return getString(context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
}
public static boolean shouldShowStatsOnLockscreen(Context context) {
public static boolean shouldShowStatsOnLockscreen(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
return getBoolean(sharedPreferences, context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
}
public static boolean shouldKeepScreenOn(Context context) {
public static boolean shouldKeepScreenOn(SharedPreferences sharedPreferences, Context context) {
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_keep_screen_on_while_recording_default);
return getBoolean(context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
return getBoolean(sharedPreferences, context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
}
public static boolean shouldUseFullscreen(Context context) {
public static boolean shouldUseFullscreen(SharedPreferences sharedPreferences, Context context) {
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_fullscreen_while_recording_default);
return getBoolean(context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
@Deprecated
public static boolean isShowStatsElevation(Context context) {
return isShowStatsElevation(getSharedPreferences(context), context);
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static boolean isShowStatsElevation(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_elevation_default);
return getBoolean(context, 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) {
final boolean STATS_SHOW_COORDINATE = context.getResources().getBoolean(R.bool.stats_show_coordinate_default);
return getBoolean(context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
return getBoolean(sharedPreferences, context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
}
public static int getVoiceFrequency(Context context) {
public static int getVoiceFrequency(SharedPreferences sharedPreferences, Context context) {
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
return getInt(context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
}
public static int getRecordingDistanceInterval(Context context) {
return getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
public static int getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) {
return getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
}
public static int getRecordingDistanceIntervalDefault(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
}
public static int getMaxRecordingDistance(Context context) {
public static int getMaxRecordingDistance(SharedPreferences sharedPreferences, Context context) {
final int MAX_RECORDING_DISTANCE = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
return getInt(context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE);
return getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE);
}
public static int getMinRecordingInterval(Context context) {
//TODO Duration
public static int getMinRecordingInterval(SharedPreferences sharedPreferences, Context context) {
final int MIN_RECORDING_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
return getInt(context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
return getInt(sharedPreferences, context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
}
public static int getMinRecordingIntervalAdaptAccuracy(Context context) {
@@ -350,19 +267,19 @@ public class PreferencesUtils {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
}
public static int getRecordingGPSAccuracy(Context context) {
public static int getRecordingGPSAccuracy(SharedPreferences sharedPreferences, Context context) {
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
return getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
return getInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
}
public static boolean shouldInstantExportAfterWorkout(Context context) {
public static boolean shouldInstantExportAfterWorkout(SharedPreferences sharedPreferences, Context context) {
final boolean INSTANT_POST_WORKOUT_EXPORT_DEFAULT = context.getResources().getBoolean(R.bool.post_workout_export_enabled_default);
return getBoolean(context, R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT);
return getBoolean(sharedPreferences, context, R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT);
}
public static TrackFileFormat getExportTrackFileFormat(Context context) {
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(context, R.string.export_trackfileformat_default, null);
String trackFileFormatName = getString(context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
public static TrackFileFormat getExportTrackFileFormat(SharedPreferences sharedPreferences, Context context) {
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(sharedPreferences, context, R.string.export_trackfileformat_default, null);
String trackFileFormatName = getString(sharedPreferences, context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
try {
return TrackFileFormat.valueOf(trackFileFormatName);
} catch (Exception e) {
@@ -370,24 +287,24 @@ public class PreferencesUtils {
}
}
public static boolean getPreventReimportTracks(Context context) {
final boolean defaultValue = getBoolean(context, R.bool.import_prevent_reimport_default, false);
return getBoolean(context, R.string.import_prevent_reimport_key, defaultValue);
public static boolean getPreventReimportTracks(SharedPreferences sharedPreferences, Context context) {
final boolean defaultValue = getBoolean(sharedPreferences, context, R.bool.import_prevent_reimport_default, false);
return getBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, defaultValue);
}
/**
* @return {@link androidx.appcompat.app.AppCompatDelegate}.MODE_*
*/
public static int getDefaultNightMode(Context context) {
public static int getDefaultNightMode(SharedPreferences sharedPreferences, Context context) {
final String defaultValue = getKey(context, R.string.night_mode_default);
final String value = getString(context, R.string.night_mode_key, defaultValue);
final String value = getString(sharedPreferences, context, R.string.night_mode_key, defaultValue);
return Integer.parseInt(value);
}
@Deprecated //Use TrackRecordingService
public static boolean isRecording(Context context) {
return isRecording(getRecordingTrackId(context));
public static boolean isRecording(SharedPreferences sharedPreferences, Context context) {
return isRecording(getRecordingTrackId(context, sharedPreferences));
}
@Deprecated
@@ -399,16 +316,19 @@ public class PreferencesUtils {
return recordingTrackId.getId() != RECORDING_TRACK_ID_DEFAULT;
}
public static void resetPreferences(Context context, boolean readAgain) {
public static SharedPreferences resetPreferences(Context context, boolean readAgain) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
if (readAgain) {
// We want to really clear settings now.
getSharedPreferences(context).edit().clear().commit();
sharedPreferences.edit().clear().commit();
}
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
return sharedPreferences;
}
public static DocumentFile getDefaultExportDirectoryUri(Context context) {
String singleExportDirectorySettingsKey = getString(context, R.string.settings_default_export_directory_key, null);
public static DocumentFile getDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
String singleExportDirectorySettingsKey = getString(sharedPreferences, context, R.string.settings_default_export_directory_key, null);
if (singleExportDirectorySettingsKey == null) {
return null;
}
@@ -420,12 +340,12 @@ public class PreferencesUtils {
return null;
}
public static void setDefaultExportDirectoryUri(Context context, Uri directoryUri) {
public static void setDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context, Uri directoryUri) {
String value = directoryUri != null ? directoryUri.toString() : null;
setString(context, R.string.settings_default_export_directory_key, value);
setString(sharedPreferences, context, R.string.settings_default_export_directory_key, value);
}
public static boolean isDefaultExportDirectoryUri(Context context) {
return getDefaultExportDirectoryUri(context) != null;
public static boolean isDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
return getDefaultExportDirectoryUri(sharedPreferences, context) != null;
}
}
@@ -41,8 +41,9 @@ public class TrackNameUtils {
private TrackNameUtils() {
}
//TODO Should not access sharedPreferences; trackName should be an ENUM.
public static String getTrackName(Context context, Track.Id trackId, Instant startTime) {
String trackName = PreferencesUtils.getString(context, R.string.track_name_key, context.getString(R.string.track_name_default));
String trackName = PreferencesUtils.getString(PreferencesUtils.getSharedPreferences(context), context, R.string.track_name_key, context.getString(R.string.track_name_default));
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
return StringUtils.formatDateTime(context, startTime);
@@ -33,7 +33,7 @@ public class IntervalStatistics {
TrackPoint trackPoint = trackPoints.get(i);
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.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0;
interval.loss_m += trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : 0;
@@ -19,9 +19,9 @@ public class SensorDataModel {
this.sensorValue = StringUtils.formatDecimal(sensorValue, 0);
}
public SensorDataModel(SensorData sensorData) {
public SensorDataModel(SensorData<Float> sensorData) {
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) {
this.labelId = R.string.sensor_state_heart_rate;
this.unitId = R.string.sensor_unit_beats_per_minute;
+4 -3
View File
@@ -25,8 +25,9 @@
android:id="@+id/stats_sensor_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
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"
tools:text="90" />
@@ -34,8 +35,8 @@
android:id="@+id/stats_sensor_unit"
style="@style/StatsUnit"
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_constraintTop_toBottomOf="@id/stats_sensor_value" />
app:layout_constraintBottom_toBottomOf="@id/stats_sensor_value" />
</androidx.constraintlayout.widget.ConstraintLayout>
+183 -150
View File
@@ -30,91 +30,124 @@
android:orientation="vertical"
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 -->
<TextView
android:id="@+id/stats_distance_label"
style="@style/StatsLargeLabel"
android:text="@string/stats_distance"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_information_horizontal_line" />
<TextView
android:id="@+id/stats_distance_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_distance_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_distance_label"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/stats_distance_unit"
app:layout_constraintTop_toBottomOf="@id/stats_distance_label"
tools:text="100" />
<TextView
android:id="@+id/stats_distance_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_distance_value"
app:layout_constraintStart_toEndOf="@+id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
app:layout_constraintStart_toEndOf="@id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
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 -->
<TextView
android:id="@+id/stats_moving_time_label"
style="@style/StatsLargeLabel"
android:text="@string/stats_moving_time"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@+id/stats_total_time_label"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/stats_total_time_label"
app:layout_constraintVertical_bias="1.0" />
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_information_horizontal_line" />
<TextView
android:id="@+id/stats_moving_time_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
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" />
<!-- Barrier -->
@@ -130,18 +163,74 @@
android:id="@+id/stats_speed_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_time_barrier" />
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
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 -->
<TextView
android:id="@+id/stats_average_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_speed_horizontal_line"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_moving_speed_value"
tools:text="Avg. Speed" />
<TextView
@@ -149,73 +238,17 @@
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_average_speed_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_average_speed_label"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/stats_average_speed_unit"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_average_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_average_speed_value"
app:layout_constraintStart_toEndOf="@+id/stats_average_speed_value"
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"
app:layout_constraintBottom_toBottomOf="@id/stats_average_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_average_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
tools:text="km/h" />
<!-- Barrier -->
@@ -224,16 +257,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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 -->
<View
android:id="@+id/stats_elevation_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_speed_barrier" />
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_speed_barrier" />
<!-- Elevation gain and loss -->
<androidx.constraintlayout.widget.Group
@@ -244,56 +277,56 @@
<TextView
android:id="@+id/stats_elevation_gain_label"
style="@style/StatsSmallLabel"
style="@style/StatsLargeLabel"
android:text="@string/stats_gain"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
<TextView
android:id="@+id/stats_elevation_gain_value"
style="@style/StatsSmallValue"
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_elevation_gain_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_gain_label"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_gain_unit"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_gain_label"
tools:text="100" />
<TextView
android:id="@+id/stats_elevation_gain_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_gain_value"
app:layout_constraintStart_toEndOf="@+id/stats_elevation_gain_value"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintBottom_toBottomOf="@id/stats_elevation_gain_value"
app:layout_constraintStart_toEndOf="@id/stats_elevation_gain_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
tools:text="m" />
<TextView
android:id="@+id/stats_elevation_loss_label"
style="@style/StatsSmallLabel"
style="@style/StatsLargeLabel"
android:text="@string/stats_loss"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
<TextView
android:id="@+id/stats_elevation_loss_value"
style="@style/StatsSmallValue"
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_elevation_loss_unit"
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_loss_label"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_loss_unit"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_loss_label"
tools:text="100" />
<TextView
android:id="@+id/stats_elevation_loss_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_loss_value"
app:layout_constraintStart_toEndOf="@+id/stats_elevation_loss_value"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintBottom_toBottomOf="@id/stats_elevation_loss_value"
app:layout_constraintStart_toEndOf="@id/stats_elevation_loss_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
tools:text="m" />
<!-- Barrier -->
@@ -309,9 +342,9 @@
android:id="@+id/stats_end_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_elevation_barrier" />
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_barrier" />
<!-- Sensors data -->
<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"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/track_list_search"
android:title="@string/menu_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:id="@+id/track_list_aggregated_stats"
android:icon="@drawable/ic_statistics_24dp"
android:title="@string/menu_aggregated_statistics"
app:showAsAction="ifRoom" />
<!-- Icon and title is overwritten by TrackListActivity -->
<!-- Hide icon as workaround for https://github.com/OpenTracksApp/OpenTracks/issues/200 -->
@@ -29,9 +29,9 @@ limitations under the License.
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="@+id/track_list_aggregated_stats"
android:icon="@drawable/ic_statistics_24dp"
android:title="@string/menu_aggregated_statistics"
android:id="@+id/track_list_search"
android:title="@string/menu_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom" />
<item
android:id="@+id/track_list_markers"
+97 -36
View File
@@ -32,45 +32,37 @@ limitations under the License.
* vis spor på et kort (se nedenfor)</string>
<string name="help_track_map_title">Hvordan viser man et spor på et kort?</string>
<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 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_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_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>
<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>
<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_content">
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>
<string name="help_data_storage_title">Hvordan gemmer OpenTracks sensordata?</string>
<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>
<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>
<string name="help_gps_troubleshooting_title">OpenTracks optager ikke placeringer?</string>
<string name="help_gps_troubleshooting_content">
Hvad du kan gøre:\n
a) Kontroller, om GPS er aktiveret,\n
b) forbedre GPS-modtagelse (f.eks. gå udendørs), \n
c) Kontroller, om du har appen <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> og \n
d) deaktiver strømbesparelse. \n
<string name="help_gps_troubleshooting_content"> Hvad du kan gøre:
\n a) Kontroller, om GPS er aktiveret,
\n b) forbedre GPS-modtagelse (f.eks. gå udendørs),
\n c) Kontroller, om du har appen <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> og
\n d) deaktiver strømbesparelse.
\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_content">
Du kan: \n
a) sprede ordet om OpenTracks, \n
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> foreslå funktioner </a>, \n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> rapportfejl </a>, \n
d) hjælp til softwareudvikling, \n
e) køb i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play Store </a>, \n
f) donere, og \n
g) gør hvad du synes hjælper OpenTracks.</string>
<string name="help_support_content"> Du kan:
\n a) sprede ordet om OpenTracks,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> foreslå funktioner </a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> rapportfejl </a>,
\n d) hjælp til softwareudvikling,
\n e) køb i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play Store </a>,
\n f) donere, og
\n 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_as_KMZ">Åben som KMZ</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_running">løb på sti</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_wakeboarding">wakeboarding</string>
<string name="activity_type_wakeboarding">vågneboarding</string>
<string name="activity_type_walking">til fods</string>
<string name="activity_type_water_skiing">vandski</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_time">Samlet tid: %1$s</string>
<string name="description_track_point">Punkt</string>
<string name="description_track_segment">Segment</string>
<string name="description_track_segment">segment</string>
<!-- Export -->
<string name="export_all_option">Alle som %1$s</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_error_initial_values_restored">Beklager, men der opstod en fejl. Alle indstillinger er vendt tilbage til standardværdierne.</string>
<!-- 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_sensor">Bluetooth-sensor</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>
<!-- Share Track -->
<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
kortlægningssoftware til at se ruten.\n\n%1$s</string>
<string name="share_track_share_file_body">Jeg tror, du måske er interesseret i dette nummer.
\n
\n%1$s</string>
<string name="share_track_subject">Jeg vil gerne dele en rute med dig</string>
<!-- Stats -->
<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_mile">%1$d mil</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_second">%1$d sek.</string>
<string name="value_integer_second">%1$d sek</string>
<string name="value_none">Ingen</string>
<string name="value_off">Fra</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_railway_crossing">jernbaneoverskæring</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_store">butik</string>
<string name="waypoint_type_swimming_pool">swimmingpool</string>
<string name="waypoint_type_telephone">telefon</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_tree">træ</string>
<string name="waypoint_type_tunnel">tunnel</string>
@@ -549,4 +542,72 @@ g) gør hvad du synes hjælper OpenTracks.</string>
<!-- Share image -->
<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="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>
+28 -30
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
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.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
--><resources>
<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_url">Projekt-Website: <a href="%1$s">%1$s</a></string>
@@ -30,7 +30,6 @@ limitations under the License.
* Markierungen erstellen (inkl. Bilder)\n
* arbeitet vollständig offline\n
* 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_content">
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>).
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>
<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_track_export_title">Wie kann ich aufgezeichnete Tracks exportieren/teilen?</string>
<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>
<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>
<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_content">
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>
<string name="help_data_storage_title">Wie speichert OpenTracks Sensordaten?</string>
<string name="help_data_storage_content">
Sensordaten werden pro aufgezeichneter Stelle gespeichert.
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>
<string name="help_gps_troubleshooting_title">OpenTracks zeichnet keine Standorte auf?</string>
<string name="help_gps_troubleshooting_content">
Was Du tun kannst:\n
a) prüfen, ob GPS aktiviert ist,\n
b) den GPS-Empfang verbessern (z.B. ins Freie gehen),\n
c) prüfen, ob mit <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, und\n
d) Energiesparen deaktivieren.\n
<string name="help_gps_troubleshooting_content"> Was Du tun kannst:
\n a) prüfen, ob GPS aktiviert ist,
\n b) den GPS-Empfang verbessern (z.B. ins Freie gehen),
\n c) mit der <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>-App das GPS testen, und
\n d) Energiesparen deaktivieren.
\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_content">
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
f) spenden, und\n
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_as_KMZ">Als KMZ öffnen</string>
<string name="open_track_as_GPX">Als GPX öffnen</string>
<string name="open_track_as_trackfileformat">Als %1$s öffnen</string>
<!-- Activity Type -->
<string name="activity_type_airplane">Flugzeug</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_connecting">Verbunden mit %1$s</string>
<string name="settings_sensor_disconnected">Verbindung getrennt von %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Breiten-/Längengrad</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_metric">Metrisch (km, m)</string>
<string name="settings_stats_units_title">Bevorzugte Einheiten</string>
<!-- Share Track -->
<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_subject">Ich möchte einen Track mit dir teilen</string>
<!-- Stats -->
<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_speed">Durchschnitt</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_longitude">Längengrad</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_moving_time">Bewegungszeit</string>
<string name="stats_pace">Tempo</string>
@@ -570,5 +555,18 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
<!-- Share image -->
<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>
</resources>
<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>
+66 -57
View File
@@ -23,62 +23,45 @@ limitations under the License.
<string name="about_libraries">Bibliotecas</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks tiene las siguientes características:</string>
<string name="help_feature_content">
* crear un seguimiento GPS *
* usa un sensor de ritmo cardíaco (sólo Bluetooth LE) *
* anuncios de voz (por tiempo o distancia)*
* crear marcadores (incluyendo imágenes) *
* funciona completamente fuera de línea *
* mostrar las pistas en un mapa (ver abajo)</string>
<string name="help_feature_content">* crear pistas de GPS
\n* utilizar un sensor de frecuencia cardíaca (solo Bluetooth LE)
\n* Anuncios de voz (por tiempo o distancia)
\n* crear marcadores (incluidas imágenes)
\n* funciona completamente sin conexión
\n* mostrar pistas en un mapa (ver más abajo)</string>
<string name="help_track_map_title">¿Cómo mostrar una pista en un mapa?</string>
<string name="help_track_map_content">
Usa el icono del mapa pequeño.
a)
(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>.\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_track_map_content"> 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>.
\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_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_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>
<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>
<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_content">
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>
<string name="help_data_storage_title">¿Cómo almacena OpenTracks los datos de los sensores?</string>
<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>
<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>
<string name="help_gps_troubleshooting_title">OpenTracks no está grabando las localizaciones?</string>
<string name="help_gps_troubleshooting_content">
Lo que puedes hacer...
a) Comprobar si el GPS está activado.
b) mejorar la recepción del GPS (por ejemplo, ir al exterior),\n
c) comprueba si con <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, y\n
d) desactivar el ahorro de energía.
<string name="help_gps_troubleshooting_content"> Lo que puedes hacer:
\na) compruebe si el GPS está habilitado,
\nb) mejorar la recepción del GPS (por ejemplo, salir al aire libre),
\nc) verifique con la aplicación <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a>, y
\nd) desactivar el ahorro de energía.
\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_content">
Puedes...
a) difundir la palabra sobre OpenTracks,...
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">proponer características</a>,\n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">report bugs</a>,\n
d) ayudar en el desarrollo de software,\n
e) comprar en <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n
f) donar, y
g) haz lo que creas que ayude a OpenTracks.</string>
<string name="help_support_content"> Usted puede:
\n a) difundir la existencia de OpenTracks,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">proponer características</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">reportar errores</a>,
\n d) ayudar en el desarrollo del software,
\n e) comprar en <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\n f) donar, y
\n g) hacer lo que creas que ayuda a OpenTracks. </string>
<!-- Activity Type -->
<string name="activity_type_airplane">avión</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_bus">ruta en autobús</string>
<string name="activity_type_driving_car">ruta en automóvil</string>
<string name="activity_type_ferry">ferry</string>
<string name="activity_type_frisbee">frisbee</string>
<string name="activity_type_ferry">balsa</string>
<string name="activity_type_frisbee">Friso</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_helicopter">helicóptero</string>
@@ -301,14 +284,14 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<!-- Search -->
<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_marker_track_location">En la ruta \"%1$s\"</string>
<string name="search_list_marker_track_location">En la ruta \"%1$s\".</string>
<!-- Sensor State -->
<string name="sensor_state_battery">Nivel de batería</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_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_value">%1$d W</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_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_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_default_activity_title">Actividad predeterminada</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_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_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_track_name_date_iso_8601_option">Fecha (ISO 8601)</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_error_initial_values_restored">Lo siento, pero ha habido un error. Todos los ajustes se han vuelto a los valores predeterminados.</string>
<!-- 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_sensor">Sensor Bluetooth</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>
<!-- Share Track -->
<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>
<!-- Stats -->
<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_drinking_water">agua potable</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_station">parque de bomberos</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_night">Oscuro</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_split_interval">Intervalo</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="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="settings_default_export_uri_title">Carpeta de pistas exportadas</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="interval_list_empty_message">No hay intervalos todavía</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="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>
+111 -65
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
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.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
--><resources>
<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_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>
<!-- Help -->
<string name="help_feature_title">OpenTracks sellel on järgmised omadused:</string>
<string name="help_feature_content">
* GPS-radade loomine \ n
* kasutage pulsisensorit (ainult Bluetooth LE) \ n
* häälteated (aja või vahemaa järgi) \ n
* loo markerid (koos piltidega) \ n
* töötab täiesti võrguühenduseta \ n
* näidata radu kaardil (vt allpool)</string>
<string name="help_feature_content">* loo GPS rajad
\n* kasutage südame löögisageduse andurit ( ainult Bluetooth LE)
\n* häälteated (aja või vahemaa järgi)
\n* loo markerid (sh. digikuvat)
\n* töötab täiesti offline
\n* näita lugusid kaardil (vt allpool)</string>
<string name="help_track_map_title">Kuidas näidata pala kaardil?</string>
<string name="help_track_map_content">
Kasutage väikest kaardiikooni. \n
a)
(reaalajas värskendused) Installige <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>.
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_track_map_content"> Kasutage väikest kaardisümbolit.
\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.
\n Tegelikult on OSMDashboard eraldi rakendus, mis saab ainult (ajutiselt) juurdepääsu näidatavate rada(de) andmetele.
\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>
<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_track_export_title">Kuidas saab salvestatud lugusid eksportida / jagada?</string>
<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>
<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>
<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_content">
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>
<string name="help_data_storage_title">Kuidas OpenTracks sensori andmeid salvestab?</string>
<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>
<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>
<string name="help_gps_troubleshooting_title">OpenTracks ei salvesta asukohti?</string>
<string name="help_gps_troubleshooting_content">
Mida saate teha: \ n
a) kontrollige, kas GPS on lubatud, \ n
b) parandage GPS-vastuvõttu (nt käige õues), \ n
c) kontrollige, kas rakendusega <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> ja \ n
d) keelake energiasääst. \ n
\ n
Kui kõik ei aita, teatage veast.</string>
<string name="help_gps_troubleshooting_content"> Mida saate teha:
\na) kontrollige, kas GPS on lubatud,
\nb) parandada GPS vastuvõtt (NT minna õues),
\nc) kontrollige <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid /">GPSTest</a> app ja
\nd) keelata energiasäästu.
\n
\nKui kõik ei aita, palun teatage veast. </string>
<string name="help_support_title">Kuidas OpenTracks it toetada?</string>
<string name="help_support_content">
Saate: \ n
a) levitage sõna OpenTracks kohta, \ n
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> funktsioonide pakkumine </a>, \ n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> teatage vigadest </a>, \ n
d) abi tarkvara arendamisel, \ n
e) ostke sisse <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play pood </a>, \ n
f) annetama ja \ n
g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<string name="help_support_content"> Saate:
\n a) levitada OpenTracks\'i kohta sõna,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ettepanekuid teha</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">raportida vigadest</a>,
\n d) aidata tarkvara arendamisel,
\n e) osta <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store\'is</a>,
\n f) annetada ja
\n g) teha kõike, mis teie arvates aitab OpenTracks\'ile kaasa. </string>
<!-- Activity Type -->
<string name="activity_type_airplane">lennuk</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_running">maastikujooks</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_wakeboarding">veelauaga sõitmine</string>
<string name="activity_type_walking">kõndimine</string>
@@ -239,7 +215,7 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<!-- Image -->
<string name="image_application">Rakendus</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_my_location">Minu asukoht</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_label_end">%1$s (Lõpp)</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_title">%1$d/%2$d</string>
<!-- Marker Detail -->
@@ -311,7 +287,7 @@ g) tehke kõike, mis teie arvates OpenTracks aitab.</string>
<!-- Sensor State -->
<string name="sensor_state_battery">Aku tase</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_heart_rate">Pulsisagedus</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_default_activity_title">Tegevuse vaiketüüp</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_min_recording_distance_title">Salvestamiskauguse 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_connecting">Ühendatud anduriga %1$s</string>
<string name="settings_sensor_disconnected">Lahti ühendatud %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</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_metric">Meetermõõdustik (km, m)</string>
<string name="settings_stats_units_title">Eelistatud ühikud</string>
<!-- Share Track -->
<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>
<!-- Stats -->
<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_tourist_information">turismiinfo</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_water_fountain">purskkaev</string>
<string name="waypoint_type_waypoint">teekonnapunkt</string>
<!-- Share image -->
<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>
</resources>
<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>
+107 -55
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
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.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
--><resources>
<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_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_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>
<!-- Help -->
<string name="help_feature_title">OpenTracks ek ezaugarri hauek ditu:</string>
@@ -30,60 +30,39 @@ limitations under the License.
* Sortu markatzaileak (irudiak barne)\n
* guztiz lineaz kanpo funtzionatzen du\n
* 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_content">
Erabili maparen ikono txikia.\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>.\n
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_track_map_content"> Erabili maparen ikono txikia.
\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>.
\n 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_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_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>
<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>
<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_content">
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>
<string name="help_data_storage_title">Nola gordetzen du OpenTracks ek sentsoreen datuak?</string>
<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>
<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>
<string name="help_gps_troubleshooting_title">OpenTracks ez dira kokapenak grabatzen?</string>
<string name="help_gps_troubleshooting_content">
Zer egin dezakezu: \n
a) egiaztatu GPSa gaituta dagoen ala ez, \n
b) GPS harrera hobetu (adibidez, kanpora joan), \n
c) egiaztatu <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> aplikazioarekin eta \n
d) desgaitu energia aurreztea. \n
<string name="help_gps_troubleshooting_content"> Zer egin dezakezu:
\n a) egiaztatu GPSa gaituta dagoen ala ez,
\n b) GPS harrera hobetu (adibidez, kanpora joan),
\n c) egiaztatu <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a> aplikazioarekin eta
\n d) desgaitu energia aurreztea.
\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_content">
Aukera hau: \ n
a) zabaldu OpenTracks-i buruzko hitza, \ n
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> proposatu ezaugarriak </a>, \ n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> txostenen akatsak </a>, \ n
d) softwarearen garapenean laguntzeko, \ n
e) erosi <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play Store </a>, \ n
f) eman, eta \ n
g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<string name="help_support_content"> Ahal izango duzu:
\n a) buruz hitz zabaltzeko OpenTracks,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">proposatzen ezaugarriak</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">txostena bugs</a>,
\n d) laguntza software garapena,
\n e) erosi <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Denda</a>,
\n f) eman, eta
\n g) ez edozein dela uste duzu laguntzen OpenTracks. </string>
<!-- Activity Type -->
<string name="activity_type_airplane">hegazkina</string>
<string name="activity_type_atv">Lurreko ibilgailu guztiak</string>
@@ -257,7 +236,7 @@ g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<!-- Marker -->
<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_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_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>
@@ -309,16 +288,16 @@ g) Egin uste duzu OpenTracks-ek laguntzen duela.</string>
<!-- Sensor State -->
<string name="sensor_state_battery">Bateria maila</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_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_value">%1$d W</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="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>
<!-- Settings Advanced -->
<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_connecting">Honekin konektatuta: %1$s</string>
<string name="settings_sensor_disconnected">Deskonektatuta %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitudea/Longitudea</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_metric">Metrikoa (km, m)</string>
<string name="settings_stats_units_title">Unitate hobetsiak</string>
<!-- Share Track -->
<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>
<!-- Stats -->
<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_two">2 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_many">distantzia, guztira: %1$.2f 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 -->
<string name="share_image_subject">Irudi bat zurekin partekatu nahi nuke</string>
<string name="share_image_body">Irudi hau interesatuko zaizuela uste dut.</string>
</resources>
<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>
+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_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_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>
<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>
<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_content">
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>
<string name="help_data_storage_title">Miten OpenTracks tallentaa anturidatan?</string>
<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>
<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>
<string name="help_gps_troubleshooting_title">Miksi OpenTracks ei tallenna sijaintia?</string>
<string name="help_gps_troubleshooting_content"> Mitä voit tehdä:
\na) tarkista, onko GPS käytössä,
@@ -61,15 +55,13 @@ limitations under the License.
\n
\nJos kaikki ei auta, ilmoita virheestä. </string>
<string name="help_support_title">Miten voin tukea OpenTracksin kehitystä?</string>
<string name="help_support_content">
Voit:\n
a) levittää sanaa OpenTracksista,\n
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ehdottaa ominaisuuksia</a>,\n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ilmoittaa ohjelmistovirheistä</a>,\n
d) auttaa ohjelmiston kehittämisessä,\n
e) ostaa sovelluksen <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play -kaupasta</a>,\n
f) lahjoittaa ja
g) tehdä mikä mielestäsi edistäisi OpenTracksia.</string>
<string name="help_support_content"> Voit:
\n a) levittää sanaa OpenTracksista,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ehdottaa ominaisuuksia</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ilmoittaa ohjelmistovirheistä</a>,
\n d) auttaa ohjelmiston kehittämisessä,
\n e) ostaa sovelluksen <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play -kaupasta</a>,
\n 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_as_KMZ">Avaa KMZ-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_car">autolla ajo</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_hang_gliding">riippuliito</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="voice_speed_lap">Kierrosnopeus %1$s</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="import_thread_interrupted">Lanka keskeytetty</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_exists_msg">Jo olemassa</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>
+18 -6
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_car">guidare auto</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_hang_gliding">deltaplano</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_skating">pattinaggio</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_snow_boarding">snowboard</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_running">corsa campestre</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_wakeboarding">wakeboard</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_bank">banca</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_bus_stop">fermata dell\'autobus</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_tourist_information">informazioni turistiche</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_water_fountain">fontana ad acqua</string>
<string name="waypoint_type_waypoint">tappa</string>
@@ -519,7 +519,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="help_track_export_content"> Le tracce possono essere esportate dalle Impostazioni con formato KMZ, KML o GPX. KMZ/KML contiene tutti i dati; GPX contiene solo le posizioni. Le tracce condivise tramite l\'icona <i>condividi</i> sono esportate come KMZ. </string>
<string name="help_recording_content">OpenTracks utilizza il sensore GPS del dispositivo per registrare continuativamente la posizione. Se il sensore GPS riporta dati imprecisi (ad es. posizione, velocità, altitudine), OpenTracks non può fare nulla.</string>
<string name="help_data_storage_content"> I dati del sensore vengono memorizzati per ogni posizione registrata. Riguardo al sensore della frequenza cardiaca, ciò significa che viene memorizzata solo <i>una</i> misurazione della frequenza cardiaca per cambiamento di posizione. Se si è fermi o si è al chiuso, non verranno registrati i dati del sensore (ma saranno mostrati). </string>
<string name="help_gps_troubleshooting_content"> Cosa puoi fare:
<string name="help_gps_troubleshooting_content"> Cosa puoi fare:
\na) verificare se il GPS è abilitato,
\nb) migliorare la ricezione GPS (ad esempio, andare all\'aperto),
\nc) verificare la ricezione del segnale GPS con l\' applicazione<a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, e
@@ -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_exists_msg">Esiste già</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>
+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_power_avg">Gjennomsnittlig effekt</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>
+115 -71
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
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.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
--><resources>
<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_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>
<!-- Help -->
<string name="help_feature_title">OpenTracks heeft de volgende kenmerken:</string>
<string name="help_feature_content">
* creëer GPS tracks
* gebruik een hartslagsensor (alleen Bluetooth LE)...
* spraakaankondigingen (op tijd of afstand)...
* creëer markers (incl. fotos)
* werkt volledig offline
* toon tracks op een kaart (zie hieronder)</string>
<string name="help_feature_content">* GPS tracks maken
\n * een hartslagsensor gebruiken (alleen Bluetooth LE)
\n * spraakaankondigingen (op tijd of afstand)
\n * markers maken (incl. foto\'s)
\n * werkt volledig offline
\n * 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_content">
Gebruik het kleine kaarticoontje.
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>.\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_track_map_content"> Gebruik het kleine kaartpictogram.
\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>.
\n Eigenlijk is OSMDashboard een aparte app die alleen (tijdelijk) toegang heeft tot de gegevens van de track(s) die getoond gaan worden.
\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>
<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_track_export_title">Hoe kan ik opgenomen tracks exporteren/delen?</string>
<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>
<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>
<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_content">
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>
<string name="help_data_storage_title">Hoe slaat OpenTracks sensorgegevens op?</string>
<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>
<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>
<string name="help_gps_troubleshooting_title">OpenTracks neemt geen locaties op?</string>
<string name="help_gps_troubleshooting_content">
Wat kun je doen?
a) controleer of de GPS is ingeschakeld.
b) het verbeteren van de GPS-ontvangst (bijv. naar buiten gaan),\n
c) controleer of met <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, en
d) schakel stroombesparing uit.
<string name="help_gps_troubleshooting_content"> Wat u kunt doen:
\n a) Controleer of GPS is ingeschakeld,
\n b) verbeter de GPS-ontvangst (ga bijvoorbeeld naar buiten),
\n c) controleren met <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, en
\n d) schakel energiebesparing uit.
\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_content">
Je kunt...
a) het woord verspreiden over OpenTracks, en...
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">voorstellen van functies</a>,\n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">melding van bugs</a>,\n
d) hulp bij de ontwikkeling van software.
e) koop in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n
f) doneert u, en
g) doe wat je denkt dat OpenTracks helpt.</string>
<string name="help_support_content"> U kunt:
\n a) het woord verspreiden over OpenTracks,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">functies voorstellen</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">bugs melden</a>,
\n d) helpen 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>,
\n f) doneren, en
\n g) doe wat u denkt dat OpenTracks helpt. </string>
<!-- Activity Type -->
<string name="activity_type_airplane">vliegtuig</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_car">autorijden</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_hang_gliding">deltavliegen</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_running">trailrunning</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_wakeboarding">wakeboarden</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_heart_rate">Hartslag (spm)</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_metric">Snelheid (km/u)</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_time">Totale tijd: %1$s</string>
<string name="description_track_point">Punt</string>
<string name="description_track_segment">Segment</string>
<string name="description_track_segment">segment</string>
<!-- Export -->
<string name="export_all_option">Alles als %1$s</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_x_axis">X-as</string>
<!-- 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_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>
@@ -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_error_initial_values_restored">Sorry, maar er is een fout gemaakt. Alle instellingen zijn teruggezet naar de standaardwaarden.</string>
<!-- 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_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connecting">Verbonden met %1$s</string>
<string name="settings_sensor_disconnected">Ontkoppeld van %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Breedte-/lengtegraad</string>
<string name="settings_stats_rate_title">Voorkeursverhouding</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_title">Voorkeurseenheden</string>
<!-- Share Track -->
<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>
<!-- Stats -->
<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_two">2 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_seconds_few">%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_bank">bank</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_bus_stop">bushalte</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_swimming_pool">zwembad</string>
<string name="waypoint_type_telephone">telefoon</string>
<string name="waypoint_type_theater">theater</string>
<string name="waypoint_type_toilet">toilet</string>
<string name="waypoint_type_theater">schouwburg</string>
<string name="waypoint_type_toilet">restroom</string>
<string name="waypoint_type_tourist_information">toeristische informatie</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_water_fountain">waterfontein</string>
<string name="waypoint_type_waypoint">routepunt</string>
<!-- Share image -->
<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>
</resources>
<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>

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