From 36c3885dce4d29cfd6522c54883bdefb98cfd200 Mon Sep 17 00:00:00 2001 From: jimkats Date: Sun, 26 Jul 2026 21:47:22 +0000 Subject: [PATCH 01/35] Translated using Weblate (Greek) Currently translated at 70.3% (390 of 554 strings) Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/el/ --- src/main/res/values-el/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 1cbeb2fea..94c150d1c 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -302,7 +302,7 @@ limitations under the License. συντριβάνι σημείο διαδρομής Σχετικά - Ιστότοπους εγχειρήματος: %1$s + Ιστότοπος εγχειρήματος: %1$s Όνομα έκδοσης: %1$s Κωδικός έκδοσης: %1$d Δεδομένα από τρίτους From 25a2f978c48f007d5050fb7aad9a72d4cee771e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 27 Jul 2026 08:51:37 +0000 Subject: [PATCH 02/35] Translated using Weblate (Estonian) Currently translated at 100.0% (554 of 554 strings) Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/et/ --- src/main/res/values-et/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/res/values-et/strings.xml b/src/main/res/values-et/strings.xml index e8c29bb19..61ae42bb1 100644 --- a/src/main/res/values-et/strings.xml +++ b/src/main/res/values-et/strings.xml @@ -598,4 +598,11 @@ limitations under the License. rattasõit kruusateedel Ekspordi kõik Ekspordi „Health Connect“ liidesesse + Peale salvestamise lõppemist ekspordi tegevus „Health Connect“ taustateenusesse + „Health Connect“ taustateenus pole selles seadmes saadaval + „Health Connect“ taustateenuse seadistused + Ava „Health Connect“ taustateenuse seadistused + „Health Connect“ taustateenuse õigused + Kuidas OpenTracks kasutab „Health Connect“ taustateenust + Kui see on seadistustest lubatud, siis OpenTracks salvestab treeningute andmed ka „Health Connect“ taustateenusesse. OpenTracks ei loe andmeid „Health Connect“ taustateenusest. From e372a780f0f4472da7044e430d7b1147029e90ce Mon Sep 17 00:00:00 2001 From: CI/CD Bot by pstorch Date: Wed, 29 Jul 2026 17:43:19 +0000 Subject: [PATCH 03/35] Update dependency androidx.constraintlayout:constraintlayout to v2.2.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 944de6b26..f232f5e38 100644 --- a/build.gradle +++ b/build.gradle @@ -150,7 +150,7 @@ dependencies { implementation 'androidx.documentfile:documentfile:1.1.0' implementation 'androidx.gridlayout:gridlayout:1.1.0' implementation 'com.google.android.material:material:1.14.0' - implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'androidx.constraintlayout:constraintlayout:2.2.2' implementation 'androidx.core:core:1.19.0' implementation 'androidx.core:core-splashscreen:1.2.0' implementation 'androidx.mediarouter:mediarouter:1.8.1' From 245854581b74788274e389ed096d291141b0d7e4 Mon Sep 17 00:00:00 2001 From: jimkats Date: Sun, 26 Jul 2026 21:47:22 +0000 Subject: [PATCH 04/35] Translated using Weblate (Greek) Currently translated at 70.3% (390 of 554 strings) Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/el/ --- src/main/res/values-el/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 1cbeb2fea..94c150d1c 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -302,7 +302,7 @@ limitations under the License. συντριβάνι σημείο διαδρομής Σχετικά - Ιστότοπους εγχειρήματος: %1$s + Ιστότοπος εγχειρήματος: %1$s Όνομα έκδοσης: %1$s Κωδικός έκδοσης: %1$d Δεδομένα από τρίτους From 2a8e1c99b6ec9d55ac3f83bea705a9c718b127cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 27 Jul 2026 08:51:37 +0000 Subject: [PATCH 05/35] Translated using Weblate (Estonian) Currently translated at 100.0% (554 of 554 strings) Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/et/ --- src/main/res/values-et/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/res/values-et/strings.xml b/src/main/res/values-et/strings.xml index e8c29bb19..61ae42bb1 100644 --- a/src/main/res/values-et/strings.xml +++ b/src/main/res/values-et/strings.xml @@ -598,4 +598,11 @@ limitations under the License. rattasõit kruusateedel Ekspordi kõik Ekspordi „Health Connect“ liidesesse + Peale salvestamise lõppemist ekspordi tegevus „Health Connect“ taustateenusesse + „Health Connect“ taustateenus pole selles seadmes saadaval + „Health Connect“ taustateenuse seadistused + Ava „Health Connect“ taustateenuse seadistused + „Health Connect“ taustateenuse õigused + Kuidas OpenTracks kasutab „Health Connect“ taustateenust + Kui see on seadistustest lubatud, siis OpenTracks salvestab treeningute andmed ka „Health Connect“ taustateenusesse. OpenTracks ei loe andmeid „Health Connect“ taustateenusest. From e9456919cb9e5cb0c2c22426f602e5818eb91267 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 3 Aug 2026 20:27:47 +0200 Subject: [PATCH 06/35] Bug: don't crash in settings if HealthConnect is not installed. Part of #2376. --- .../io/healthconnect/exporter/HealthConnectUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectUtils.java b/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectUtils.java index cc3998bf5..6ce00829e 100644 --- a/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectUtils.java +++ b/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectUtils.java @@ -41,6 +41,9 @@ public class HealthConnectUtils { } public static Set getGrantedPermissions(Context context) { + if (!isAvailable(context)) + return Set.of(); + try { HealthConnectClient client = HealthConnectClient.getOrCreate(context); PermissionController controller = client.getPermissionController(); From 01ea65742b661b9ff1c2fa67694e87d8d2f76e72 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 31 Jul 2026 07:38:09 +0200 Subject: [PATCH 07/35] HealthConnect: send total distance, elevation gain and speed. --- src/main/AndroidManifest.xml | 3 + .../exporter/HealthConnectUtils.java | 15 ++- .../exporter/HealthConnectWorker.java | 108 ++++++++++++++---- 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 3b1339e6b..fe0f90b97 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -71,6 +71,9 @@ limitations under the License. + + + PERMISSIONS = Set.of(WRITE_EXERCISE_PERMISSION, WRITE_EXERCISE_ROUTE_PERMISSION); + public static final Set PERMISSIONS = Set.of( + HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ExerciseSessionRecord.class)), + HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(DistanceRecord.class)), + HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ElevationGainedRecord.class)), + HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(SpeedRecord.class)), + HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE + ); private HealthConnectUtils() { } diff --git a/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java b/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java index 65832d4f1..5d24b10f5 100644 --- a/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java +++ b/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java @@ -6,16 +6,22 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.health.connect.client.HealthConnectClient; +import androidx.health.connect.client.records.DistanceRecord; +import androidx.health.connect.client.records.ElevationGainedRecord; import androidx.health.connect.client.records.ExerciseRoute; import androidx.health.connect.client.records.ExerciseSessionRecord; +import androidx.health.connect.client.records.Record; +import androidx.health.connect.client.records.SpeedRecord; import androidx.health.connect.client.records.metadata.Device; import androidx.health.connect.client.records.metadata.Metadata; import androidx.health.connect.client.units.Length; +import androidx.health.connect.client.units.Velocity; import androidx.work.Worker; import androidx.work.WorkerParameters; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import de.dennisguse.opentracks.data.ContentProviderUtils; @@ -111,27 +117,65 @@ public class HealthConnectWorker extends Worker { throw new RuntimeException("Track not found: " + trackId); } - ExerciseRoute exerciseRoute = createExerciseRoute(contentProviderUtils, trackId); - HealthConnectClient client = HealthConnectClient.getOrCreate(context); + Metadata metadata = Metadata.activelyRecorded( + new Device(Device.TYPE_PHONE, Build.MANUFACTURER, Build.MODEL), + track.uuid().toString() + ); - ExerciseSessionRecord record = new ExerciseSessionRecord( + List records = new ArrayList<>(); + + ExerciseData exerciseData = createExerciseData(contentProviderUtils, trackId); + + //Avoid bug in HealthConnect: data must be within ExerciseSessionRecord.startTime and ExerciseSessionRecord.stopTime + List locations = exerciseData.locations.stream() + .filter(i -> track.startTime().toInstant().isBefore(i.getTime()) && track.stopTime().toInstant().isAfter(i.getTime())) + .toList(); + + records.add(new ExerciseSessionRecord( track.startTime().toInstant(), track.startTime().getOffset(), track.stopTime().toInstant(), track.stopTime().getOffset(), - Metadata.activelyRecorded( - new Device(Device.TYPE_PHONE, Build.MANUFACTURER, Build.MODEL), - track.uuid().toString() - ), + metadata, toExerciseType(track.activityType()), track.name(), track.description(), Collections.emptyList(), Collections.emptyList(), - exerciseRoute); + new ExerciseRoute(locations))); + + records.add(new DistanceRecord( + track.startTime().toInstant(), + track.startTime().getOffset(), + track.stopTime().toInstant(), + track.stopTime().getOffset(), + Length.meters(track.statistics().totalDistance().toM()), + metadata + )); + + if (track.statistics().altitudeGainLoss() != null) { + records.add(new ElevationGainedRecord( + track.startTime().toInstant(), + track.startTime().getOffset(), + track.stopTime().toInstant(), + track.stopTime().getOffset(), + Length.meters(track.statistics().altitudeGainLoss().gain_m()), + metadata + )); + } + + if (!exerciseData.speeds.isEmpty()) { + records.add(new SpeedRecord( + track.startTime().toInstant(), + track.startTime().getOffset(), + track.stopTime().toInstant(), + track.stopTime().getOffset(), + exerciseData.speeds, + metadata + )); + } - List records = Collections.singletonList(record); Object ignored = BuildersKt.runBlocking( kotlin.coroutines.EmptyCoroutineContext.INSTANCE, (Function2) (scope, cont) -> client.insertRecords(records, (Continuation) cont)); @@ -140,30 +184,50 @@ public class HealthConnectWorker extends Worker { return new ExportResult(track.name()); } - private static ExerciseRoute createExerciseRoute(ContentProviderUtils contentProviderUtils, Track.Id trackId) { - List route = new ArrayList<>(); + private static ExerciseData createExerciseData(ContentProviderUtils contentProviderUtils, Track.Id trackId) { + List locations = new LinkedList<>(); + List speeds = new LinkedList<>(); try (TrackPointIterator iterator = contentProviderUtils.getTrackPointIterator(trackId, null)) { - while (iterator.hasNext()) { + while (iterator.hasNext()) { //TODO iterator.iterator.forEachRemaining() TrackPoint trackPoint = iterator.next(); - if (trackPoint.type() != TrackPoint.Type.TRACKPOINT) continue; + if (trackPoint.type() != TrackPoint.Type.TRACKPOINT) continue; //TODO Include SEGMENT_START_AUTOMATIC? Position position = trackPoint.position(); - route.add(new ExerciseRoute.Location( - trackPoint.getTime(), - position.latitude(), - position.longitude(), - position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null, - position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null, - position.altitude() != null ? Length.meters(position.altitude().toM()) : null) - ); + if (position.hasLocation()) { + locations.add(new ExerciseRoute.Location( + trackPoint.getTime(), + position.latitude(), + position.longitude(), + position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null, + position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null, + position.altitude() != null ? Length.meters(position.altitude().toM()) : null) + ); + } + + if (position.hasSpeed()) { + speeds.add(new SpeedRecord.Sample( + trackPoint.getTime(), + Velocity.metersPerSecond(position.speed().toMPS()) + )); + } + + //TODO Cadence + //TODO Power + //TODO Heartrate } } - return new ExerciseRoute(route); + return new ExerciseData(locations, speeds); } + private record ExerciseData( + List locations, + List speeds + ) {} + + private static int toExerciseType(ActivityType activityType) { return switch (activityType) { case RUNNING, STREET_RUNNING, TRAIL_RUNNING, TRACK_RUNNING -> ExerciseSessionRecord.EXERCISE_TYPE_RUNNING; From 1eeb7063302200fe092847a55be0243926e7b978 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 5 Aug 2026 19:13:16 +0200 Subject: [PATCH 08/35] Bugfix: HealthConnect requires unique timestamps for locations. Part of #2376. --- .../exporter/HealthConnectWorker.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java b/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java index 5d24b10f5..568ac22c1 100644 --- a/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java +++ b/src/main/java/de/dennisguse/opentracks/io/healthconnect/exporter/HealthConnectWorker.java @@ -196,14 +196,17 @@ public class HealthConnectWorker extends Worker { Position position = trackPoint.position(); if (position.hasLocation()) { - locations.add(new ExerciseRoute.Location( - trackPoint.getTime(), - position.latitude(), - position.longitude(), - position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null, - position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null, - position.altitude() != null ? Length.meters(position.altitude().toM()) : null) - ); + //ExcerciseRoute does not allow duplicated timestamps. + if (locations.stream().noneMatch(location -> location.getTime().equals(position.time()))) { + locations.add(new ExerciseRoute.Location( + trackPoint.getTime(), + position.latitude(), + position.longitude(), + position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null, + position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null, + position.altitude() != null ? Length.meters(position.altitude().toM()) : null) + ); + } } if (position.hasSpeed()) { From a542f45661fcd58db5145e6bdcc0e722a2bd8587 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 5 Aug 2026 19:16:00 +0200 Subject: [PATCH 09/35] Bugfix: HealthConnect doesn't provide granted permissions in time. Fixes #2376. --- .../opentracks/settings/ImportExportSettingsFragment.java | 5 ----- src/main/res/xml/settings_import_export.xml | 8 -------- 2 files changed, 13 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java index ebfc6dc80..d09cb8a27 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java @@ -27,11 +27,6 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat { setExportTrackFileFormatOptions(); setExportDirectorySummary(); setFilenameTemplate(); - - findPreference("healthConnectPermissions").setSummaryProvider(preference -> { - //TODO This should be localized and prefixes trimmed. - return String.join(",", HealthConnectUtils.getGrantedPermissions(getActivity())); - }); } @Override diff --git a/src/main/res/xml/settings_import_export.xml b/src/main/res/xml/settings_import_export.xml index 136b72684..0237f7188 100644 --- a/src/main/res/xml/settings_import_export.xml +++ b/src/main/res/xml/settings_import_export.xml @@ -65,14 +65,6 @@ android:targetPackage="@string/applicationId" /> - - - - Date: Thu, 6 Aug 2026 14:36:45 +0000 Subject: [PATCH 10/35] chore(deps): update dependency gradle to v9.7.0 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a351597e6..1e922f407 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From aa63474114f80a43498c3fafcf5d08218593d002 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sat, 8 Aug 2026 12:06:32 +0200 Subject: [PATCH 11/35] Release: v4.28.1 --- build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/6733.txt | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/6733.txt diff --git a/build.gradle b/build.gradle index f232f5e38..84bc29ea9 100644 --- a/build.gradle +++ b/build.gradle @@ -47,8 +47,8 @@ android { defaultConfig { applicationId 'de.dennisguse.opentracks' - versionCode 6698 - versionName "v4.28.0" + versionCode 6733 + versionName "v4.28.1" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" diff --git a/fastlane/metadata/android/en-US/changelogs/6733.txt b/fastlane/metadata/android/en-US/changelogs/6733.txt new file mode 100644 index 000000000..a5af502af --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/6733.txt @@ -0,0 +1,10 @@ +v4.28.1: OpenTracks + +Changes: +- HealthConnect: sending total distance, speed and elevation gain + +Bugfix: +- Gravel activity was not selectable + +Developer: +- Cleanup ContentProvider URLs From d3ff2061c6575cb9709535e390ec37b4079b63bf Mon Sep 17 00:00:00 2001 From: CI/CD Bot by pstorch Date: Wed, 12 Aug 2026 17:34:26 +0000 Subject: [PATCH 12/35] chore(deps): update dependency androidx.appcompat:appcompat to v1.8.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 84bc29ea9..9e92b7577 100644 --- a/build.gradle +++ b/build.gradle @@ -145,7 +145,7 @@ android { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' - implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'androidx.appcompat:appcompat:1.8.0' implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.documentfile:documentfile:1.1.0' implementation 'androidx.gridlayout:gridlayout:1.1.0' From ce26e20571b1139b22697ad2d314c54247e1ef54 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 17 Aug 2026 15:48:21 +0200 Subject: [PATCH 13/35] Sensor: on start register GPS only once. Part of #2369. --- .../opentracks/sensors/GpsHandler.java | 5 ++-- .../opentracks/sensors/SensorHandler.java | 4 +-- .../opentracks/sensors/driver/Driver.java | 4 +++ .../sensors/driver/GpsInternal.java | 27 +++++++++++++------ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/sensors/GpsHandler.java b/src/main/java/de/dennisguse/opentracks/sensors/GpsHandler.java index 5948a4ed8..2ab681310 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/GpsHandler.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/GpsHandler.java @@ -45,8 +45,9 @@ public class GpsHandler extends SensorHandler { gpsStatusManager.onMinSamplingIntervalChanged(PreferencesUtils.getMinSamplingInterval()); } - //TODO - connect(); + if (driver != null) { + driver.updateDriverParameters(getContext(), sensorManager.getHandler()); + } } }; diff --git a/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java b/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java index 31433932d..971c13307 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java @@ -21,11 +21,11 @@ public abstract class SensorHandler implement private static final String TAG = SensorHandler.class.getSimpleName(); - private final SensorManager sensorManager; - @VisibleForTesting public Driver driver; + protected final SensorManager sensorManager; + protected Aggregator aggregator; protected SensorHandler(SensorManager sensorManager) { diff --git a/src/main/java/de/dennisguse/opentracks/sensors/driver/Driver.java b/src/main/java/de/dennisguse/opentracks/sensors/driver/Driver.java index bc5628393..435a9000a 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/driver/Driver.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/driver/Driver.java @@ -11,5 +11,9 @@ public interface Driver { //TODO Remove from interface? boolean isConnected(); + default void updateDriverParameters(Context context, Handler handler) { + throw new UnsupportedOperationException(); + } + void disconnect(); } diff --git a/src/main/java/de/dennisguse/opentracks/sensors/driver/GpsInternal.java b/src/main/java/de/dennisguse/opentracks/sensors/driver/GpsInternal.java index cb9316d56..3b0a0ac51 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/driver/GpsInternal.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/driver/GpsInternal.java @@ -57,16 +57,11 @@ public class GpsInternal implements Driver { return; } - LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis()) - .setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY) - .setMaxUpdateDelayMillis(0) - .build(); - if (PermissionRequester.GPS.hasPermission(context)) { try { - Log.i(TAG, "Register for location updates " + context); + Log.i(TAG, "Register for location updates"); driverObserver.onConnected(null, null); - LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, handler::post, locationListenerCompat); + LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, createLocationRequest(), handler::post, locationListenerCompat); return; } catch (SecurityException e) { Log.e(TAG, "Could not register location listener; permissions not granted.", e); @@ -76,6 +71,16 @@ public class GpsInternal implements Driver { driverObserver.onDisconnected(); } + @SuppressWarnings({"MissingPermission"}) + @Override + public void updateDriverParameters(Context context, Handler handler) { + if (!isConnected()) return; + + Log.i(TAG, "Update for location updates"); + driverObserver.onConnected(null, null); + LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, createLocationRequest(), handler::post, locationListenerCompat); + } + @SuppressWarnings({"MissingPermission"}) @Override public void disconnect() { @@ -87,9 +92,15 @@ public class GpsInternal implements Driver { driverObserver.onDisconnected(); } - @Override public boolean isConnected() { return locationManager != null; } + + private LocationRequestCompat createLocationRequest() { + return new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis()) + .setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY) + .setMaxUpdateDelayMillis(0) + .build(); + } } From a6fd701004886a656e7ac434bdf7ce5b48cbecbd Mon Sep 17 00:00:00 2001 From: CI/CD Bot by pstorch Date: Wed, 19 Aug 2026 15:34:29 +0000 Subject: [PATCH 14/35] chore(deps): update dependency gradle to v9.7.1 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1e922f407..c42672d95 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From d56b01867612b0d373ad485d275437c6e41ba77f Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 17 Aug 2026 16:06:18 +0200 Subject: [PATCH 15/35] Bugfix: when stopping a recording, immediately stop the idle detection timer. Fixes #2386. --- .../de/dennisguse/opentracks/services/TrackRecordingManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index 3a4197aee..57ab6214c 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -120,6 +120,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen } synchronized void endCurrentTrack() { + handler.removeCallbacks(ON_IDLE); TrackPoint segmentEnd = trackPointCreator.createSegmentEnd(); insertTrackPoint(segmentEnd, true); From e12bb967e6421237cacac566f830d63f786d8de7 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 17 Aug 2026 16:14:25 +0200 Subject: [PATCH 16/35] Cleanup. --- .../opentracks/io/file/importer/GPXTrackImporterTest.java | 4 ++-- .../res/raw/{gpx_timezone.gpx => gpx11_timezone.gpx} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/androidTest/res/raw/{gpx_timezone.gpx => gpx11_timezone.gpx} (100%) diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java index 52c479ee3..91dbaa188 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java @@ -285,8 +285,8 @@ public class GPXTrackImporterTest { public void importExportTest_timezone() throws IOException { // given XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter)); - InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone); - try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone)) { + InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_timezone); + try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_timezone)) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // when diff --git a/src/androidTest/res/raw/gpx_timezone.gpx b/src/androidTest/res/raw/gpx11_timezone.gpx similarity index 100% rename from src/androidTest/res/raw/gpx_timezone.gpx rename to src/androidTest/res/raw/gpx11_timezone.gpx From 12a868689602654c995ab3ac01047d7ffd71059a Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 19 Aug 2026 20:38:05 +0200 Subject: [PATCH 17/35] Cleanup. --- .../java/de/dennisguse/opentracks/settings/PreferencesUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 2ef449f90..d344190d9 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -71,7 +71,6 @@ public class PreferencesUtils { private static SharedPreferences sharedPreferences; private static Resources resources; - private static final long LONG_UNSET = Long.MIN_VALUE; /** * Must be called during application startup. From 7c030857616f3bbe9ba9702b1c1d9c043992f511 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 19 Aug 2026 20:45:23 +0200 Subject: [PATCH 18/35] GPX: on import ignore TrackPoints without time. Previous behavior: abort import. Fixes #2384. --- .../file/importer/GPXTrackImporterTest.java | 41 +++++++++++++++++++ .../res/raw/gpx11_missing_time.gpx | 22 ++++++++++ .../io/file/importer/GPXTrackImporter.java | 17 ++++++-- 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/androidTest/res/raw/gpx11_missing_time.gpx diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java index 91dbaa188..32e37bde5 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java @@ -120,6 +120,47 @@ public class GPXTrackImporterTest { ), importedTrackPoints); } + @LargeTest + @Test + public void gpx_missing_time() throws IOException { + // given + XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter)); + InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_missing_time); + + // when + // 1. import + importTrackId = importer.importFile(inputStream).get(0); + + // then + // 2. track + Track importedTrack = contentProviderUtils.getTrack(importTrackId); + assertNotNull(importedTrack); + assertEquals("", importedTrack.activityTypeLocalized()); + assertEquals("", importedTrack.description()); + assertEquals("20210907_213924.gpx", importedTrack.name()); + assertEquals(ActivityType.UNKNOWN, importedTrack.activityType()); + + // 3. trackstatistics + Statistics trackStatistics = importedTrack.statistics(); + assertEquals(0, trackStatistics.maxSpeed().toMPS(), 0.01); + assertEquals(Duration.ofSeconds(0), trackStatistics.movingDuration()); + + // 4. trackpoints + List importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId); + assertEquals(1, importedTrackPoints.size()); + + TrackPointAssert a = new TrackPointAssert(); + a.assertEquals(List.of( + new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, + new Position( + Instant.parse("2021-09-07T22:12:00Z"), + 30.14185982, -40.3863038, null, + Altitude.WGS84.of(-5), null, + null, + null)) + ), importedTrackPoints); + } + @LargeTest @Test public void gpx_speed_no_namespace() throws IOException { diff --git a/src/androidTest/res/raw/gpx11_missing_time.gpx b/src/androidTest/res/raw/gpx11_missing_time.gpx new file mode 100644 index 000000000..4cadd43bd --- /dev/null +++ b/src/androidTest/res/raw/gpx11_missing_time.gpx @@ -0,0 +1,22 @@ + + + + 20210907_213924.gpx + + + -5 + 4 + + + -5 + + + -5 + + 4 + + + + \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java index 1cd877c83..de24a08a5 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java @@ -18,8 +18,11 @@ package de.dennisguse.opentracks.io.file.importer; import android.content.Context; import android.net.Uri; +import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; + import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.helpers.DefaultHandler; @@ -178,7 +181,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac zoneOffset = null; } case TAG_TRACK_SEGMENT -> onTrackSegmentEnd(); - case TAG_TRACK_POINT -> currentSegment.add(createTrackPoint()); + case TAG_TRACK_POINT -> { + TrackPoint trackPoint = createTrackPoint(); + if (trackPoint != null) currentSegment.add(trackPoint); + } case TAG_NAME -> { if (content != null) { name = content.trim(); @@ -284,8 +290,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac currentSegment.clear(); } - + @Nullable private TrackPoint createTrackPoint() throws ParsingException { + if (TextUtils.isEmpty(time)) return null; + OffsetDateTime parsedTime; try { parsedTime = StringUtils.parseTime(time); @@ -468,7 +476,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac } TrackPoint trackPoint = createTrackPoint(); - + if (trackPoint == null) { + Log.w(TAG, "Marker with no TrackPoint"); + return; + } if (!trackPoint.position().hasLocation()) { Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.position()); return; From ab8791a620a005da514e7659abd159eef10c5696 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sat, 22 Aug 2026 19:20:38 +0200 Subject: [PATCH 19/35] Release: v4.28.2 --- build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/6741.txt | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/6741.txt diff --git a/build.gradle b/build.gradle index 9e92b7577..b8ce59273 100644 --- a/build.gradle +++ b/build.gradle @@ -47,8 +47,8 @@ android { defaultConfig { applicationId 'de.dennisguse.opentracks' - versionCode 6733 - versionName "v4.28.1" + versionCode 6741 + versionName "v4.28.2" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" diff --git a/fastlane/metadata/android/en-US/changelogs/6741.txt b/fastlane/metadata/android/en-US/changelogs/6741.txt new file mode 100644 index 000000000..e69a55a8e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/6741.txt @@ -0,0 +1,7 @@ +v4.28.2: OpenTracks + +Changes: +- Import: ignore GPX waypoints without time + +Bugfix: +- Only start GPS once From fb928ccc45f15cf2084b6e7a41fcad34e1916efc Mon Sep 17 00:00:00 2001 From: CI/CD Bot by pstorch Date: Mon, 24 Aug 2026 03:34:20 +0000 Subject: [PATCH 20/35] chore(deps): update dependency com.android.tools.build:gradle to v9.3.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b8ce59273..1edc96192 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:9.3.1' + classpath 'com.android.tools.build:gradle:9.3.2' } } From 2d4e98781264e9b0a24f854bb59a9b7c9b7b26f2 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 24 Aug 2026 17:25:58 +0200 Subject: [PATCH 21/35] GPX: export moving speed in summary. --- src/androidTest/res/raw/gpx11_timezone.gpx | 3 ++- .../opentracks/io/file/exporter/GPXTrackExporter.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/androidTest/res/raw/gpx11_timezone.gpx b/src/androidTest/res/raw/gpx11_timezone.gpx index c6a342faf..ce37b1b22 100644 --- a/src/androidTest/res/raw/gpx11_timezone.gpx +++ b/src/androidTest/res/raw/gpx11_timezone.gpx @@ -28,10 +28,11 @@ xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/ 0.0 -0 +0 0 0 0.0 +0.0 diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java index e45e17509..05fa0864e 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java @@ -282,10 +282,11 @@ public class GPXTrackExporter implements TrackExporter { Statistics trackStatistics = track.statistics(); printWriter.println(""); printWriter.println("" + trackStatistics.totalDistance().toM() + ""); - printWriter.println("" + trackStatistics.totalDuration().getSeconds() + ""); + printWriter.println("" + trackStatistics.totalDuration().getSeconds() + ""); printWriter.println("" + trackStatistics.movingDuration().getSeconds() + ""); printWriter.println("" + trackStatistics.getStoppedTime().getSeconds() + ""); printWriter.println("" + trackStatistics.maxSpeed().toMPS() + ""); + printWriter.println("" + trackStatistics.getAverageMovingSpeed().toMPS() + ""); if (trackStatistics.altitudeGainLoss() != null) { printWriter.println("" + trackStatistics.altitudeGainLoss().gain_m() + ""); printWriter.println("" + trackStatistics.altitudeGainLoss().loss_m() + ""); From 2ff8d21897f6d9d53376e3fdf8fe1986aec43152 Mon Sep 17 00:00:00 2001 From: Dany Mestas <94061157+DanyPM@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:39:51 +0200 Subject: [PATCH 22/35] GPX: match activity types case-insensitively on import. --- .../data/models/ActivityTypeTest.java | 78 +++++++++++++++++++ .../opentracks/data/models/ActivityType.java | 77 +++++++++++++----- 2 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 src/androidTest/java/de/dennisguse/opentracks/data/models/ActivityTypeTest.java diff --git a/src/androidTest/java/de/dennisguse/opentracks/data/models/ActivityTypeTest.java b/src/androidTest/java/de/dennisguse/opentracks/data/models/ActivityTypeTest.java new file mode 100644 index 000000000..4dd0ff2b8 --- /dev/null +++ b/src/androidTest/java/de/dennisguse/opentracks/data/models/ActivityTypeTest.java @@ -0,0 +1,78 @@ +package de.dennisguse.opentracks.data.models; + +import static org.junit.Assert.assertEquals; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +@RunWith(AndroidJUnit4.class) +public class ActivityTypeTest { + + @Test + public void findBy_id() { + assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("trail running")); + } + + @Test + public void findBy_idOfEveryActivityType() { + for (ActivityType activityType : ActivityType.values()) { + assertEquals(activityType, ActivityType.findBy(activityType.getId())); + } + } + + @Test + public void findBy_synonymsOfEveryActivityType() { + for (ActivityType activityType : ActivityType.values()) { + for (String synonym : activityType.synonyms) { + assertEquals(activityType, ActivityType.findBy(synonym)); + assertEquals(activityType, ActivityType.findBy(synonym.toUpperCase(Locale.ROOT).replace(' ', '_'))); + } + } + } + + @Test + public void findBy_ignoresCase() { + assertEquals(ActivityType.RUNNING, ActivityType.findBy("RUNNING")); + assertEquals(ActivityType.ATV, ActivityType.findBy("atv")); + } + + @Test + public void findBy_ignoresSeparators() { + assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("TRAIL_RUNNING")); + assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy("mountain-biking")); + assertEquals(ActivityType.CROSS_COUNTRY_SKIING, ActivityType.findBy("cross_country_skiing")); + } + + @Test + public void findBy_ignoresSurroundingAndRepeatedWhitespace() { + assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy(" Mountain Biking ")); + } + + @Test + public void findBy_synonym() { + assertEquals(ActivityType.RUNNING, ActivityType.findBy("treadmill")); + assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("TRAIL_RUN")); + assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy("MOUNTAIN_BIKE")); + assertEquals(ActivityType.SWIMMING_OPEN, ActivityType.findBy("SWIMMING_OPENWATER")); + assertEquals(ActivityType.BIKING, ActivityType.findBy("bike")); + } + + @Test + public void findBy_unknown() { + assertEquals(ActivityType.UNKNOWN, ActivityType.findBy("hovercraft")); + } + + @Test + public void findBy_null() { + assertEquals(ActivityType.UNKNOWN, ActivityType.findBy(null)); + } + + @Test + public void findBy_blank() { + assertEquals(ActivityType.UNKNOWN, ActivityType.findBy(" ")); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/data/models/ActivityType.java b/src/main/java/de/dennisguse/opentracks/data/models/ActivityType.java index f51bc62de..998695d8c 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/ActivityType.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/ActivityType.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import java.util.Arrays; import java.util.List; +import java.util.Locale; import de.dennisguse.opentracks.R; @@ -14,13 +15,13 @@ public enum ActivityType { AIRPLANE("airplane", R.string.activity_type_airplane, R.drawable.ic_activity_flight_24dp, true), ATV("ATV", R.string.activity_type_atv, R.drawable.ic_activity_drive_24dp, true), - BIKING("biking", R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true), + BIKING("biking", List.of("bike", "indoor cycling"), R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true), BLIMP("blimp", R.string.activity_type_blimp, ActivityIcon.ICON_UNKNOWN, true), BOAT("boat", R.string.activity_type_boat, R.drawable.ic_activity_boat_24dp, true), CLIMBING("climbing", R.string.activity_type_climbing, R.drawable.ic_activity_climbing_24dp, false), COMMERCIAL_AIRPLANE("commercial airplane", R.string.activity_type_commercial_airplane, R.drawable.ic_activity_flight_24dp, true), CROSS_COUNTRY_SKIING("cross-country skiing", R.string.activity_type_cross_country_skiing, R.drawable.ic_activity_skiing_24dp, true), - CYCLING("cycling", R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true), + CYCLING("cycling", List.of("cycle"), R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true), DIRT_BIKE("dirt bike", R.string.activity_type_dirt_bike, R.drawable.ic_activity_mtb_24dp, true), DONKEY_BACK_RIDING("donkey back riding", R.string.activity_type_donkey_back_riding, ActivityIcon.ICON_UNKNOWN, true), DRIVING("driving", R.string.activity_type_driving, R.drawable.ic_activity_drive_24dp, true), @@ -30,21 +31,21 @@ public enum ActivityType { FERRY("ferry", R.string.activity_type_ferry, R.drawable.ic_activity_boat_24dp, true), FRISBEE("frisbee", R.string.activity_type_frisbee, ActivityIcon.ICON_UNKNOWN, true), GLIDING("gliding", R.string.activity_type_gliding, ActivityIcon.ICON_UNKNOWN, true), - GRAVEL_BIKING("gravel biking", R.string.activity_type_gravel_biking, R.drawable.ic_activity_bike_24dp, true), + GRAVEL_BIKING("gravel biking", List.of("gravel bike"), R.string.activity_type_gravel_biking, R.drawable.ic_activity_bike_24dp, true), HANG_GLIDING("hang gliding", R.string.activity_type_hang_gliding, ActivityIcon.ICON_UNKNOWN, true), HELICOPTER("helicopter", R.string.activity_type_helicopter, ActivityIcon.ICON_UNKNOWN, true), - HIKING("hiking", R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false), + HIKING("hiking", List.of("hike"), R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false), HORSE_BACK_RIDING("horse back riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true), HOT_AIR_BALLOON("hot air balloon", R.string.activity_type_hot_air_balloon, ActivityIcon.ICON_UNKNOWN, true), ICE_SAILING("ice sailing", R.string.activity_type_ice_sailing, ActivityIcon.ICON_UNKNOWN, true), - INLINE_SKATING("inline skating", R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true), + INLINE_SKATING("inline skating", List.of("inline skate"), R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true), KAYAKING("kayaking", R.string.activity_type_kayaking, R.drawable.ic_activity_kayaking_24dp, true), KITE_SURFING("kite surfing", R.string.activity_type_kite_surfing, ActivityIcon.ICON_UNKNOWN, true), LAND_SAILING("land sailing", R.string.activity_type_land_sailing, ActivityIcon.ICON_UNKNOWN, true), MIXED_TYPE("mixed type", R.string.activity_type_mixed_type, ActivityIcon.ICON_UNKNOWN, true), MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp, true), MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true), - MOUNTAIN_BIKING("mountain biking", R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true), + MOUNTAIN_BIKING("mountain biking", List.of("mountain bike"), R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true), OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false), OTHER("other", R.string.activity_type_other, ActivityIcon.ICON_UNKNOWN, true), PADDLING("paddling", R.string.activity_type_paddling, ActivityIcon.ICON_UNKNOWN, true), @@ -53,46 +54,57 @@ public enum ActivityType { RC_BOAT("RC boat", R.string.activity_type_rc_boat, R.drawable.ic_activity_boat_24dp, true), RC_HELICOPTER("RC helicopter", R.string.activity_type_rc_helicopter, ActivityIcon.ICON_UNKNOWN, true), RIDING("riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true), - ROAD_BIKING("road biking", R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true), + ROAD_BIKING("road biking", List.of("road bike"), R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true), ROLLER_SKIING("roller skiing", R.string.activity_type_roller_skiing, ActivityIcon.ICON_UNKNOWN, true), ROWING("rowing", R.string.activity_type_rowing, ActivityIcon.ICON_UNKNOWN, true), - RUNNING("running", R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false), + RUNNING("running", List.of("run", "treadmill"), R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false), SAILING("sailing", R.string.activity_type_sailing, R.drawable.ic_activity_sailing_24dp, true), KICKSCOOTER("kickscooter", R.string.activity_type_kickscooter, R.drawable.ic_activity_scooter_24dp, true), SEAPLANE("seaplane", R.string.activity_type_seaplane, R.drawable.ic_activity_flight_24dp, true), - SKATE_BOARDING("skateboarding", R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true), + SKATE_BOARDING("skateboarding", List.of("skateboard"), R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true), SKATING("skating", R.string.activity_type_skating, ActivityIcon.ICON_UNKNOWN, true), SKIING("skiing", R.string.activity_type_skiing, R.drawable.ic_activity_skiing_24dp, true), SKY_JUMPING("sky jumping", R.string.activity_type_sky_jumping, ActivityIcon.ICON_UNKNOWN, true), SLED("sled", R.string.activity_type_sled, ActivityIcon.ICON_UNKNOWN, true), - SNOW_BOARDING("snowboarding", R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true), + SNOW_BOARDING("snowboarding", List.of("snowboard"), R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true), SNOW_SHOEING("snow shoeing", R.string.activity_type_snow_shoeing, ActivityIcon.ICON_UNKNOWN, true), SPEED_WALKING("speed walking", R.string.activity_type_speed_walking, R.drawable.ic_activity_walk_24dp, false), STREET_RUNNING("street running", R.string.activity_type_street_running, R.drawable.ic_activity_run_24dp, false), SURFING("surfing", R.string.activity_type_surfing, ActivityIcon.ICON_UNKNOWN, true), TRACK_CYCLING("track cycling", R.string.activity_type_track_cycling, R.drawable.ic_activity_bike_24dp, true), - TRACK_RUNNING("track running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false), + TRACK_RUNNING("track running", List.of("track run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false), TRAIL_HIKING("trail hiking", R.string.activity_type_trail_hiking, R.drawable.ic_activity_walk_24dp, false), - TRAIL_RUNNING("trail running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false), + TRAIL_RUNNING("trail running", List.of("trail run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false), TRAIN("train", R.string.activity_type_train, ActivityIcon.ICON_UNKNOWN, true), ULTIMATE_FRISBEE("ultimate frisbee", R.string.activity_type_ultimate_frisbee, ActivityIcon.ICON_UNKNOWN, true), WAKEBOARDING("wakeboarding", R.string.activity_type_wakeboarding, ActivityIcon.ICON_UNKNOWN, true), - WALKING("walking", R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false), + WALKING("walking", List.of("walk"), R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false), WATER_SKIING("water skiing", R.string.activity_type_water_skiing, ActivityIcon.ICON_UNKNOWN, true), WIND_SURFING("wind surfing", R.string.activity_type_wind_surfing, ActivityIcon.ICON_UNKNOWN, true), - SWIMMING("swimming", R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false), - SWIMMING_OPEN("swimming in open water", R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false), + SWIMMING("swimming", List.of("swim"), R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false), + SWIMMING_OPEN("swimming in open water", List.of("swimming openwater", "open water swimming"), R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false), WORKOUT("workout", R.string.activity_type_workout, R.drawable.ic_activity_workout_24dp, false), UNKNOWN("unknown", R.string.activity_type_unknown, ActivityIcon.ICON_UNKNOWN, true); final String id; + /** + * Spellings used by other applications that {@link #normalize(String)} cannot map onto {@link #id}. + * Already normalized. + */ + final List synonyms; + final int iconDrawableId; final boolean showSpeedPreferred; final int localizedStringId; ActivityType(String id, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) { + this(id, List.of(), localizedStringId, iconDrawableId, showSpeedPreferred); + } + + ActivityType(String id, List synonyms, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) { this.id = id; + this.synonyms = synonyms; this.localizedStringId = localizedStringId; this.iconDrawableId = iconDrawableId; this.showSpeedPreferred = showSpeedPreferred; @@ -121,12 +133,39 @@ public enum ActivityType { .toList(); } + /** + * Lowercases and reduces "_", "-" and whitespace runs to a single space. + * Lowercasing is locale-independent: under a Turkish locale the I of "RUNNING" would otherwise + * lowercase to a dotless i and stop matching "running". + */ + @NonNull + private static String normalize(String activityTypeId) { + if (activityTypeId == null) { + return ""; + } + + return activityTypeId.toLowerCase(Locale.ROOT) + .replaceAll("[\\s_-]+", " ") + .trim(); + } + + /** + * Resolves an activity type as written by another application; GPX's type element is free text. + */ @NonNull public static ActivityType findBy(String activityTypeId) { - return Arrays.stream(ActivityType.values()).filter( - it -> it.id.equals(activityTypeId) - ).findFirst() - .orElse(ActivityType.UNKNOWN); + String normalized = normalize(activityTypeId); + if (normalized.isEmpty()) { + return ActivityType.UNKNOWN; + } + + return Arrays.stream(ActivityType.values()) + .filter(it -> normalize(it.id).equals(normalized)) + .findFirst() + .orElseGet(() -> Arrays.stream(ActivityType.values()) + .filter(it -> it.synonyms.contains(normalized)) + .findFirst() + .orElse(ActivityType.UNKNOWN)); } @NonNull From 337c50eb91292c5f96ac8c94620c2998e921eb14 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 26 Aug 2026 07:34:07 +0200 Subject: [PATCH 23/35] Cleanup: qName. --- .../opentracks/io/file/importer/GPXTrackImporter.java | 8 ++++---- .../opentracks/io/file/importer/KMLTrackImporter.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java index de24a08a5..6145ecb89 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java @@ -145,8 +145,8 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac } @Override - public void startElement(String uri, String localName, String tag, Attributes attributes) { - switch (tag) { + public void startElement(String uri, String localName, String qName, Attributes attributes) { + switch (qName) { case TAG_MARKER: onMarkerStart(attributes); break; @@ -168,8 +168,8 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac } @Override - public void endElement(String uri, String localName, String tag) { - switch (tag) { + public void endElement(String uri, String localName, String qName) { + switch (qName) { case TAG_GPX -> onFileEnd(); case TAG_MARKER -> onMarkerEnd(); case TAG_TRACK -> { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java index 7e0ae6ba8..61fd71bba 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java @@ -146,8 +146,8 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac } @Override - public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException { - switch (tag) { + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + switch (qName) { case TAG_PLACEMARK, TAG_PHOTO_OVERLAY -> // Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, typeLocalized, and description. onMarkerStart(); @@ -168,8 +168,8 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac } @Override - public void endElement(String uri, String localName, String tag) throws SAXException { - switch (tag) { + public void endElement(String uri, String localName, String qName) throws SAXException { + switch (qName) { case TAG_KML -> onFileEnd(); case TAG_PLACEMARK, TAG_PHOTO_OVERLAY -> // Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track. From 7ca871c69943ebb5d1638971c6f1d13db18d091a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Wed, 26 Aug 2026 15:21:06 +0100 Subject: [PATCH 24/35] Bugfix: Respect declared namespace prefixes --- .../file/importer/GPXTrackImporterTest.java | 137 +++++++++++ .../res/raw/gpx_with_power_gpxpx.gpx | 32 +++ .../res/raw/gpx_with_power_pwr.gpx | 32 +++ .../io/file/importer/GPXTrackImporter.java | 222 ++++++++++-------- 4 files changed, 330 insertions(+), 93 deletions(-) create mode 100644 src/androidTest/res/raw/gpx_with_power_gpxpx.gpx create mode 100644 src/androidTest/res/raw/gpx_with_power_pwr.gpx diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java index 32e37bde5..6060b12c1 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java @@ -161,6 +161,143 @@ public class GPXTrackImporterTest { ), importedTrackPoints); } + @LargeTest + @Test + public void gpx_with_power_pwd() throws IOException { + // given + XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter)); + InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_with_power_pwr); + + // when + // 1. import + importTrackId = importer.importFile(inputStream).get(0); + + // then + // 2. track + Track importedTrack = contentProviderUtils.getTrack(importTrackId); + assertNotNull(importedTrack); + assertEquals("", importedTrack.activityTypeLocalized()); + assertEquals("", importedTrack.description()); + assertEquals("gpx with power", importedTrack.name()); + assertEquals(ActivityType.UNKNOWN, importedTrack.activityType()); + + // 3. trackpoints + List importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId); + assertEquals(3, importedTrackPoints.size()); + + TrackPointAssert a = new TrackPointAssert(); + a.assertEquals(List.of( + new TrackPoint(null, TrackPoint.Type.SEGMENT_START_AUTOMATIC, + new Position( + Instant.parse("2021-09-07T22:10:19Z"), + 30.14185982, -40.3863038, null, + Altitude.WGS84.of(-5), null, + null, + null), + null, + null, + null, + null, + new Power(100f), + null + ), + new TrackPoint(null, TrackPoint.Type.TRACKPOINT, + new Position( + Instant.parse("2021-09-07T22:11:07Z"), + 30.14184657, -40.38670089, null, + Altitude.WGS84.of(-5), null, + null, + Speed.of(0.7976524233818054)), + null, + null, + null, + null, + new Power(110f), + null), + new TrackPoint(null, TrackPoint.Type.TRACKPOINT, + new Position( + Instant.parse("2021-09-07T22:12:00Z"), + 30.14185982, -40.3863038, null, + Altitude.WGS84.of(-5), null, + null, + Speed.of(0.7224021553993225)), + null, + null, + null, + null, + new Power(150f), + null) + ), importedTrackPoints); + } + @LargeTest + @Test + public void gpx_with_power_gpxpx() throws IOException { + // given + XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter)); + InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_with_power_gpxpx); + + // when + // 1. import + importTrackId = importer.importFile(inputStream).get(0); + + // then + // 2. track + Track importedTrack = contentProviderUtils.getTrack(importTrackId); + assertNotNull(importedTrack); + assertEquals("", importedTrack.activityTypeLocalized()); + assertEquals("", importedTrack.description()); + assertEquals("gpx with power", importedTrack.name()); + assertEquals(ActivityType.UNKNOWN, importedTrack.activityType()); + + // 3. trackpoints + List importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId); + assertEquals(3, importedTrackPoints.size()); + + TrackPointAssert a = new TrackPointAssert(); + a.assertEquals(List.of( + new TrackPoint(null, TrackPoint.Type.SEGMENT_START_AUTOMATIC, + new Position( + Instant.parse("2021-09-07T22:10:19Z"), + 30.14185982, -40.3863038, null, + Altitude.WGS84.of(-5), null, + null, + null), + null, + null, + null, + null, + new Power(100f), + null + ), + new TrackPoint(null, TrackPoint.Type.TRACKPOINT, + new Position( + Instant.parse("2021-09-07T22:11:07Z"), + 30.14184657, -40.38670089, null, + Altitude.WGS84.of(-5), null, + null, + Speed.of(0.7976524233818054)), + null, + null, + null, + null, + new Power(110f), + null), + new TrackPoint(null, TrackPoint.Type.TRACKPOINT, + new Position( + Instant.parse("2021-09-07T22:12:00Z"), + 30.14185982, -40.3863038, null, + Altitude.WGS84.of(-5), null, + null, + Speed.of(0.7224021553993225)), + null, + null, + null, + null, + new Power(150f), + null) + ), importedTrackPoints); + } + @LargeTest @Test public void gpx_speed_no_namespace() throws IOException { diff --git a/src/androidTest/res/raw/gpx_with_power_gpxpx.gpx b/src/androidTest/res/raw/gpx_with_power_gpxpx.gpx new file mode 100644 index 000000000..599e8501a --- /dev/null +++ b/src/androidTest/res/raw/gpx_with_power_gpxpx.gpx @@ -0,0 +1,32 @@ + + + + gpx with power + + + -5 + + 4 + + 100 + + + + -5 + + 4 + + 110 + + + + -5 + + 4 + + 150 + + + + + \ No newline at end of file diff --git a/src/androidTest/res/raw/gpx_with_power_pwr.gpx b/src/androidTest/res/raw/gpx_with_power_pwr.gpx new file mode 100644 index 000000000..b42b13148 --- /dev/null +++ b/src/androidTest/res/raw/gpx_with_power_pwr.gpx @@ -0,0 +1,32 @@ + + + + gpx with power + + + -5 + + 4 + + 100 + + + + -5 + + 4 + + 110 + + + + -5 + + 4 + + 150 + + + + + \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java index 6145ecb89..51b594a45 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java @@ -74,28 +74,44 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac private static final String TAG_TRACK_POINT = "trkpt"; private static final String TAG_TRACK_SEGMENT = "trkseg"; private static final String TAG_TYPE = "type"; - private static final String TAG_TYPE_LOCALIZED = "opentracks:typeTranslated"; private static final String TAG_MARKER = "wpt"; - private static final String TAG_ID = "opentracks:trackid"; private static final String ATTRIBUTE_LAT = "lat"; private static final String ATTRIBUTE_LON = "lon"; - private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed"; /** * Often speed is exported without the proper namespace. */ private static final String TAG_EXTENSION_SPEED_COMPAT = "speed"; - private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr"; - private static final String TAG_EXTENSION_TEMPERATURE = "gpxtpx:atemp"; - 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"; - private static final String TAG_EXTENSION_ACCURACY_HORIZONTAL = "opentracks:accuracy_horizontal"; - private static final String TAG_EXTENSION_ACCURACY_VERTICAL = "opentracks:accuracy_vertical"; + /** + * Garmin Trackpoint Extensions. + */ + private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1"; + private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V2 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v2"; + private static final String TAG_GARMIN_TPX_EXTENSION_SPEED = "speed"; + private static final String TAG_GARMIN_TPX_EXTENSION_HEARTRATE = "hr"; + private static final String TAG_GARMIN_TPX_EXTENSION_TEMPERATURE = "atemp"; + private static final String TAG_GARMIN_TPX_EXTENSION_CADENCE = "cad"; + + /** + * Garmin Trackpoint Extensions. + */ + private static final String NS_GARMIN_POWER_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/PowerExtension/v1"; + private static final String TAG_GARMIN_PWR_EXTENSION_POWER = "PowerInWatts"; + + /** + * OpenTracks Extensions. + */ + private static final String NS_OPENTRACKS = "http://opentracksapp.com/xmlschemas/v1"; + private static final String TAG_OPENTRACKS_EXTENSION_TRACK_ID = "trackid"; + private static final String TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED = "typeTranslated"; + private static final String TAG_OPENTRACKS_EXTENSION_GAIN = "gain"; + private static final String TAG_OPENTRACKS_EXTENSION_LOSS = "loss"; + private static final String TAG_OPENTRACKS_EXTENSION_DISTANCE = "distance"; + private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL = "accuracy_horizontal"; + private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL = "accuracy_vertical"; + private Locator locator; private final Context context; @@ -169,107 +185,127 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac @Override public void endElement(String uri, String localName, String qName) { - switch (qName) { - case TAG_GPX -> onFileEnd(); - case TAG_MARKER -> onMarkerEnd(); - case TAG_TRACK -> { - if (activityTypeLocalized == null) { - // Backward compatibility: up v4.9.1 as contained localized content. - activityTypeLocalized = activityType; + if (NS_GARMIN_TRACKPOINT_EXTENSION_V1.equals(uri) || NS_GARMIN_TRACKPOINT_EXTENSION_V2.equals(uri)) { + switch (localName) { + case TAG_GARMIN_TPX_EXTENSION_SPEED -> { + if (content != null) { + speed = content.trim(); + } } - trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset); - zoneOffset = null; - } - case TAG_TRACK_SEGMENT -> onTrackSegmentEnd(); - case TAG_TRACK_POINT -> { - TrackPoint trackPoint = createTrackPoint(); - if (trackPoint != null) currentSegment.add(trackPoint); - } - case TAG_NAME -> { - if (content != null) { - name = content.trim(); + case TAG_GARMIN_TPX_EXTENSION_HEARTRATE -> { + if (content != null) { + heartrate = content.trim(); + } } - } - case TAG_DESCRIPTION -> { - if (content != null) { - description = content.trim(); + case TAG_GARMIN_TPX_EXTENSION_TEMPERATURE -> { + if (content != null) { + temperature = content.trim(); + } } - } - case TAG_TYPE -> { //Track or Marker/WPT - if (content != null) { - // In older version this might be localized content. - activityType = content.trim(); - markerType = content.trim(); + case TAG_GARMIN_TPX_EXTENSION_CADENCE -> { + if (content != null) { + cadence = content.trim(); + } } + default -> Log.w(TAG, "Unknown Garmin trackpoint extension tag " + localName); } - case TAG_TYPE_LOCALIZED -> { - if (content != null) { - activityTypeLocalized = content.trim(); + } else if (NS_GARMIN_POWER_EXTENSION_V1.equals(uri)) { + //noinspection SwitchStatementWithTooFewBranches + switch (localName) { + case TAG_GARMIN_PWR_EXTENSION_POWER -> { + if (content != null) { + power = content.trim(); + } } + default -> Log.w(TAG, "Unknown Garmin power extension tag " + localName); } - case TAG_TIME -> { - if (content != null) { - time = content.trim(); + } else if (NS_OPENTRACKS.equals(uri)) { + switch (localName) { + case TAG_OPENTRACKS_EXTENSION_TRACK_ID -> { + if (content != null) { + uuid = content.trim(); + } } - } - case TAG_ALTITUDE -> { - if (content != null) { - altitude = content.trim(); + case TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED -> { + if (content != null) { + activityTypeLocalized = content.trim(); + } } - } - case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> { - if (content != null) { - speed = content.trim(); + case TAG_OPENTRACKS_EXTENSION_GAIN -> { + if (content != null) { + gain = content.trim(); + } } - } - case TAG_EXTENSION_HEARTRATE -> { - if (content != null) { - heartrate = content.trim(); + case TAG_OPENTRACKS_EXTENSION_LOSS -> { + if (content != null) { + loss = content.trim(); + } } - } - case TAG_EXTENSION_TEMPERATURE -> { - if (content != null) { - temperature = content.trim(); + case TAG_OPENTRACKS_EXTENSION_DISTANCE -> { + if (content != null) { + sensorDistance = content.trim(); + } } - } - case TAG_EXTENSION_CADENCE -> { - if (content != null) { - cadence = content.trim(); + case TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL -> { + if (content != null) { + accuracyHorizontal = content.trim(); + } } - } - case TAG_EXTENSION_POWER -> { - if (content != null) { - power = content.trim(); + case TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL -> { + if (content != null) { + accuracyVertical = content.trim(); + } } + default -> Log.w(TAG, "Unknown OpenTracks extension tag " + localName); } - case TAG_ID -> { - if (content != null) { - uuid = content.trim(); + } else { + switch (qName) { + case TAG_GPX -> onFileEnd(); + case TAG_MARKER -> onMarkerEnd(); + case TAG_TRACK -> { + if (activityTypeLocalized == null) { + // Backward compatibility: up v4.9.1 as contained localized content. + activityTypeLocalized = activityType; + } + trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset); + zoneOffset = null; } - } - case TAG_EXTENSION_GAIN -> { - if (content != null) { - gain = content.trim(); + case TAG_TRACK_SEGMENT -> onTrackSegmentEnd(); + case TAG_TRACK_POINT -> { + TrackPoint trackPoint = createTrackPoint(); + if (trackPoint != null) currentSegment.add(trackPoint); } - } - case TAG_EXTENSION_LOSS -> { - if (content != null) { - loss = content.trim(); + case TAG_NAME -> { + if (content != null) { + name = content.trim(); + } } - } - case TAG_EXTENSION_DISTANCE -> { - if (content != null) { - sensorDistance = content.trim(); + case TAG_DESCRIPTION -> { + if (content != null) { + description = content.trim(); + } } - } - case TAG_EXTENSION_ACCURACY_HORIZONTAL -> { - if (content != null) { - accuracyHorizontal = content.trim(); + case TAG_TYPE -> { //Track or Marker/WPT + if (content != null) { + // In older version this might be localized content. + activityType = content.trim(); + markerType = content.trim(); + } } - } - case TAG_EXTENSION_ACCURACY_VERTICAL -> { - if (content != null) { - accuracyVertical = content.trim(); + case TAG_TIME -> { + if (content != null) { + time = content.trim(); + } + } + case TAG_ALTITUDE -> { + if (content != null) { + altitude = content.trim(); + } + } + case TAG_EXTENSION_SPEED_COMPAT -> { + if (content != null) { + speed = content.trim(); + } } } } From e28a84fc88c60c2a2a51cf920c8b30baeca730a6 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 28 Aug 2026 06:58:38 +0200 Subject: [PATCH 25/35] Allow to disable segment creation (max recording distance). Fixes #2389. --- .../dennisguse/opentracks/settings/PreferencesUtils.java | 7 +++++++ src/main/res/values/settings.xml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index d344190d9..8bd6e8501 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -539,6 +539,7 @@ public class PreferencesUtils { String[] entries = new String[entryValues.length]; final int maxRecordingDistanceDefault = Integer.parseInt(resources.getString(R.string.max_recording_distance_default)); + final int maxRecordingDistanceOff = Integer.parseInt(resources.getString(R.string.max_recording_distance_off)); UnitSystem unitSystem = getUnitSystem(); DistanceFormatter formatter = DistanceFormatter.Builder() @@ -555,6 +556,9 @@ public class PreferencesUtils { case METRIC, IMPERIAL_METER -> { if (value == maxRecordingDistanceDefault) { entries[i] = resources.getString(R.string.value_integer_meter_recommended, value); + } + if (value == maxRecordingDistanceOff) { + entries[i] = resources.getString(R.string.value_off); } else { entries[i] = displayValue; } @@ -562,6 +566,9 @@ public class PreferencesUtils { case IMPERIAL_FEET, NAUTICAL_IMPERIAL -> { if (value == maxRecordingDistanceDefault) { entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT()); + } + if (value == maxRecordingDistanceOff) { + entries[i] = resources.getString(R.string.value_off); } else { entries[i] = displayValue; } diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 5d9c2038e..517004d33 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -81,7 +81,9 @@ maxRecordingDistance 200 + 2147483647 + @string/max_recording_distance_off 50 100 @string/max_recording_distance_default From 91b379c328362764b60b7f0dd05f03dc952bc123 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 31 Aug 2026 20:07:23 +0200 Subject: [PATCH 26/35] Release: v4.28.3 --- build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/6751.txt | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/6751.txt diff --git a/build.gradle b/build.gradle index 1edc96192..08951cd30 100644 --- a/build.gradle +++ b/build.gradle @@ -47,8 +47,8 @@ android { defaultConfig { applicationId 'de.dennisguse.opentracks' - versionCode 6741 - versionName "v4.28.2" + versionCode 6751 + versionName "v4.28.3" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" diff --git a/fastlane/metadata/android/en-US/changelogs/6751.txt b/fastlane/metadata/android/en-US/changelogs/6751.txt new file mode 100644 index 000000000..3922100aa --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/6751.txt @@ -0,0 +1,10 @@ +v4.28.3: OpenTracks + +Changes: +- Settings: allow to disable segment creation (max recording distance) +- Importer: match activity type case-insensitive +- GPX: export moving speed in summary + +Bugfix: +- GPX: importer must respect namespace prefixes + From cefdb1280a6ce866db6e3c62a2bd5fe12b1dbfe7 Mon Sep 17 00:00:00 2001 From: CI/CD Bot by pstorch Date: Tue, 1 Sep 2026 14:35:00 +0000 Subject: [PATCH 27/35] chore(deps): update dependency com.android.tools.build:gradle to v9.4.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 08951cd30..ed6fe90c2 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:9.3.2' + classpath 'com.android.tools.build:gradle:9.4.0' } } From b711c4fdc5467f681c678674e29c6a88d4d8c849 Mon Sep 17 00:00:00 2001 From: love80312 Date: Wed, 2 Sep 2026 05:15:59 +0000 Subject: [PATCH 28/35] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 1.5% (3 of 190 strings) Translation: OpenTracks/App store metadata files Translate-URL: https://translate.codeberg.org/projects/opentracks/app-store-metadata-files/zh_Hant/ --- fastlane/metadata/android/zh-TW/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/zh-TW/short_description.txt b/fastlane/metadata/android/zh-TW/short_description.txt index 7267c2795..f60ba6060 100644 --- a/fastlane/metadata/android/zh-TW/short_description.txt +++ b/fastlane/metadata/android/zh-TW/short_description.txt @@ -1 +1 @@ -一個尊重您的隱私的運動蹤跡紀錄器。 +一款尊重您隱私的運動蹤跡紀錄器。 From 86726c5e0efd3637d0c944df6ff9bb935d5c5d7c Mon Sep 17 00:00:00 2001 From: love80312 Date: Wed, 2 Sep 2026 05:06:19 +0000 Subject: [PATCH 29/35] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (551 of 551 strings) Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/zh_Hant/ --- src/main/res/values-zh-rTW/strings.xml | 80 +++++++++++++++----------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 395677859..4a479cdce 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. --> 關於 - OpenTracks 是一個完全尊重您的隱私的運動蹤跡應用程式。 + OpenTracks 是一款完全尊重您隱私的運動追蹤應用程式。 專案網頁:%1$s 版本名稱:%1$s 版本代號:%1$d @@ -31,14 +31,11 @@ limitations under the License. \n * 完全離線工作 \n * 在地圖上顯示蹤跡(見下文) 如何在地圖顯示一個蹤跡? - \u0020使用小的地圖圖示。 -\n \u0020a) (即時更新)安裝 OSMDashboard。它透過 OpenStreetMap 提供線上或離線地圖。 -\n \u0020實際上,OSMDashboard 是一個單獨的應用程式,只能(暫時)存取將要顯示的蹤跡資料。 -\n \u0020b) \u0020透過一個普通的地圖應用程式(例如,OsmAnd 與 MAPS.ME)。 這些 APP 需要支援 KMZ 檔案格式(只有位置與時間戳記)。 更準確地說,APP 需要支援 ACTION_VIEW 與 MIME application/vnd.google-earth.kmz。 \u0020 + 使用小型地圖圖示。\na)(即時更新)安裝 OSMDashboard。它透過 OpenStreetMap 提供線上或離線地圖。\n實際上,OSMDashboard 是一個獨立的應用程式,只能(暫時)存取即將顯示的蹤跡資料。\nb) 透過一般地圖應用程式(例如 OsmAnd 和 MAPS.ME)。這些應用程式必須支援 KMZ 檔案格式(僅包含位置與時間戳記)。實際上,應用程式需要支援 MIME 類型為 application/vnd.google-earth.kmz 的 ACTION_VIEW。 OpenTracks 如何存儲作為標記拍攝的照片? - 這些儲存在應用程序內部儲存區中 (即,只能在 OpenTracks 中存取)。 + 這些儲存在應用程序內部儲存區中 (即,僅限 OpenTracks 中存取)。 如何匯出/分享 紀錄的蹤跡? - 可以從設定匯出蹤跡為 KMZ、KML 或 GPX。KMZ/KML 全部資料;GPX 只有位置。通過分享圖示以分享蹤跡為 KMZ。 + 可從「設定」中將蹤跡匯出為 KMZ、KML 或 GPX。KMZ/KML 包含所有資料;GPX 僅包含位置資訊。透過 分享圖示 分享的蹤跡會以 KMZ 格式分享。 OpenTracks 是否需要/使用 Google Play 服務? 否 OpenTracks 是如何記錄資料的? @@ -47,15 +44,9 @@ limitations under the License. 如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。 OpenTracks 如何儲存感測器資料? - 感測器資料儲存在每個記錄的位置。 就心率感測器而言,這意味著只有一個心率測量值被依照位置儲存。 如果您是站著不動或在室內,沒有感測器資料會被記錄(但它會被顯示)。 - OpenTracks 沒有紀錄位置? - \u0020您能做什麼: -\n 一)檢查 GPS 是否啟用, -\n 二)改善 GPS 收訊(例如,前往戶外), -\n 三)使用 GPSTest app 檢查,以及 -\n 四)停用省電。 -\n -\n如果一切都沒有幫助,請回報 bug。 \u0020 + 感測器資料會儲存在每個記錄的位置中。以心率感測器為例,這表示每個位置只會儲存 一筆 心率測量值。如果您站著不動或位於室內,將不會記錄感測器資料(但仍會顯示)。 + OpenTracks 沒有記錄位置? + 您可以進行以下操作:\na) 確認 GPS 是否已啟用;\nb) 改善 GPS 訊號接收(例如到戶外);\nc) 使用 GPSTest app 進行檢查;以及\nd) 停用省電模式。\n\n如果上述方法都無法解決問題,請回報錯誤。 我如何支持 OpenTracks? 您可以:\n @@ -213,13 +204,13 @@ limitations under the License. 昨日 關閉 - 正在啟用 GPS + 正在啟動 GPS 正在等待更清晰的 GPS 訊號 正在等待 GPS 訊號 GPS 禁用 GPS 已修復並準備就緒 %1$s:%2$s - OpenTracks 紀錄中,不包含 GPS 資料。 + OpenTracks 正在進行無 GPS 資料記錄。 標記數 相片標記 @@ -326,7 +317,7 @@ limitations under the License. 當記錄時,在全螢幕中呈現。 預設活動類型 間隔 %1$s 檢查位置 - 系統會在兩個地點之間的距離大於 %1$s 時建立新的區段 + 如果兩個地點之間的距離大於 %1$s,則建立一個新路段 最遠距離 記錄位置的距離間隔 記錄位置的時間間隔 @@ -337,8 +328,8 @@ limitations under the License. 預設蹤跡名稱 界面主題 系統 - 日 - 夜 + 早 + 晚 時間間隔 單圈長度 平均心率 @@ -383,12 +374,12 @@ limitations under the License. 其他 APP 的遠端控制與統計資料 API 公開 API 的套件名稱 公開 API - 其他已安裝的應用程式可以開始或停止紀錄。 - 只有 OpenTracks 可以開始或停止紀錄。 + 其他已安裝的應用程式可以啟動或停止記錄。 + 僅 OpenTracks 可以啟動和停止記錄。 OpenTracks 公開 AIP 被禁用:它可以在設定中被啟用。 自動資料傳輸 - 開始紀錄的應用程式也可以存取紀錄的資料。 - 記錄的資料將不會自動與其他應用程式共用。 + 已啟動記錄的應用程式也可以存取已記錄的資料。 + 已記錄的資料不會自動與其他應用程式共用。 匯入 匯出 @@ -546,26 +537,26 @@ limitations under the License. 我想與你分享一張圖片 我認為你可能會對這張圖片感興趣。 按住以停止 - 在紀錄完成後匯出蹤跡至儲存區 + 在記錄完成後將蹤跡匯出至儲存區 即時運動後匯出 蹤跡匯出目錄 主要的 次要的 移除 - 沒有安裝相容的檔案管理器 + 未安裝相容的檔案管理器 %1$s 已崩潰 分享 回報 bug OpenTracks 儀表板 API - 沒有安裝支援 OpenTracks 儀表板 API 的應用程式。 - OpenTracks 本身不提供地圖。請安裝 OSMDashboard 以在地圖上檢視您的紀錄。 + 未安裝支援 OpenTracks 儀表板 API 的應用程式。 + OpenTracks 本身不提供地圖功能。請安裝 OSMDashboard,以便在地圖上查看您的記錄。 使用裝置的揚聲器 英制(英里、公尺) 全部匯出 (所有蹤跡於單一檔案) 如果定位的精度大於 %1$s,則它將被忽略 - 沒有安裝相容的圖片庫應用程式 - 沒有安裝相容的攝影機應用程式 + 未安裝相容的圖片相簿應用程式 + 未安裝相容的相機應用程式 篩選藍芽裝置 顯示所有可發現的藍芽裝置 { n, plural, other {{n,number,#.0} 海里} } @@ -608,12 +599,35 @@ limitations under the License. 單圈功率 無法建立標記。請再次嘗試。 主題變更也許需要手動重新啟動。 - 夜(OLED 友善的) + 暗色 OLED 友善 功率 {x, plural, =1 {1 瓦特} other {# 瓦特} } 圖表 收益&損失 GPS 禁用警告 - 記錄時,如果 GPS 被禁用則顯示警告。 + 當記錄時,如果 GPS 被禁用則顯示警告。 {n, plural, =1 {1} other {{n,number,#.0}} } {n, plural, =1 {1 } other {{n,number,#.0}} } + 碎石路騎行 + 溫度(攝氏度) + 匯出全部 + 體溫計 + 系統(動態色彩) + 早(動態色彩) + 匯出至 Health Connect + 晚(動態色彩) + 暗色 OLED 友善(動態色彩) + 平均速度/配速(路段) + 平均路段配速 + 路段經過時間 + 緯度/經度 + 溫度 + 時區偏移值 + 在記錄完成後將活動匯出至 Health Connect + 此設備不支援 Health Connect 功能 + Health Connect 設定 + 開啟 Health Connect 設定 + Health Connect 權限 + OpenTracks 如何使用 Health Connect + 如果在設定中啟用此功能,OpenTracks 會將運動資料寫入 Health Connect。 OpenTracks 不會從 Health Connect 讀取資料。 + %2$d 個中的 %1$d:%3$s From fbfe6f3ab86dbabf2fe17adab2557b7fbf46111a Mon Sep 17 00:00:00 2001 From: Bone NI Date: Wed, 2 Sep 2026 09:54:52 +0000 Subject: [PATCH 30/35] Added translation using Weblate (Lao) --- src/main/res/values-lo/strings.xml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/main/res/values-lo/strings.xml diff --git a/src/main/res/values-lo/strings.xml b/src/main/res/values-lo/strings.xml new file mode 100644 index 000000000..55344e519 --- /dev/null +++ b/src/main/res/values-lo/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 10273cd7725355b8f46ca0b19130ed1dd4b6fabe Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 2 Sep 2026 07:36:21 +0200 Subject: [PATCH 31/35] TrackRecordingManager: rather crash than try to load invalid data. --- .../opentracks/services/TrackRecordingManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index 57ab6214c..bb9b00147 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -131,6 +131,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen } RecordingData getDataForUI() { + if (trackId == null) { + throw new RuntimeException("Requesting data if not recording should not be done."); + } + Pair current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation); TrackPoint trackPoint = current.first; TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, trackPoint); @@ -139,8 +143,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB if (track == null) { - Log.w(TAG, "Requesting data if not recording is taking place, should not be done."); - return null; + throw new RuntimeException("Data could not be loaded for TrackId " + trackId); } TrackBuilder trackBuilder = new TrackBuilder(track); From 4dfd2485bff01b586f97918db67dc11f11e17ca1 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Thu, 3 Sep 2026 17:50:03 +0200 Subject: [PATCH 32/35] Bugfix: avoid crash in exporter. --- .../opentracks/io/file/exporter/ExportActivity.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java index 5b4343fe8..37c4f3e0c 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java @@ -105,7 +105,7 @@ public class ExportActivity extends AbstractActivity { private ContentProviderUtils contentProviderUtils; // List of tracks to be exported. - private ArrayList exportTasks; + private final ArrayList exportTasks = new ArrayList<>(); private final LinkedBlockingQueue conflictsQueue = new LinkedBlockingQueue<>(); private final Handler conflictsHandler = new Handler(); @@ -183,7 +183,7 @@ public class ExportActivity extends AbstractActivity { trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT); directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES); trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS); - exportTasks = new ArrayList<>(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS)); + exportTasks.addAll(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS)); setProgress(); nextExport(null); } @@ -226,7 +226,6 @@ public class ExportActivity extends AbstractActivity { return false; } - exportTasks = new ArrayList<>(); if (allInOneFile) { String filename = "OpenTracks-Backup"; exportTasks.add(new ExportTask(filename, trackFileFormat, tracks.stream().map(Track::id).toList())); From f38999a9703fb87f77b9096e24df816c47975033 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Wed, 2 Sep 2026 20:33:11 +0000 Subject: [PATCH 33/35] Translated using Weblate (Spanish) Currently translated at 23.5% (45 of 191 strings) Translation: OpenTracks/App store metadata files Translate-URL: https://translate.codeberg.org/projects/opentracks/app-store-metadata-files/es/ --- .../android/es-ES/changelogs/2916.txt | 20 ++++++------- .../android/es-ES/changelogs/2947.txt | 2 +- .../android/es-ES/changelogs/2991.txt | 16 +++++------ .../android/es-ES/changelogs/3063.txt | 7 +++++ .../android/es-ES/changelogs/3079.txt | 13 +++++++++ .../android/es-ES/changelogs/3101.txt | 14 ++++++++++ .../android/es-ES/changelogs/3103.txt | 5 ++++ .../android/es-ES/changelogs/3123.txt | 15 ++++++++++ .../android/es-ES/changelogs/3137.txt | 8 ++++++ .../android/es-ES/changelogs/3149.txt | 10 +++++++ .../android/es-ES/changelogs/3162.txt | 10 +++++++ .../android/es-ES/changelogs/3175.txt | 10 +++++++ .../android/es-ES/changelogs/3195.txt | 11 ++++++++ .../android/es-ES/changelogs/3213.txt | 12 ++++++++ .../android/es-ES/changelogs/3243.txt | 13 +++++++++ .../android/es-ES/changelogs/3251.txt | 11 ++++++++ .../android/es-ES/changelogs/3272.txt | 11 ++++++++ .../android/es-ES/changelogs/3287.txt | 12 ++++++++ .../android/es-ES/changelogs/3305.txt | 14 ++++++++++ .../android/es-ES/changelogs/3330.txt | 10 +++++++ .../android/es-ES/changelogs/3333.txt | 4 +++ .../android/es-ES/changelogs/3335.txt | 4 +++ .../android/es-ES/changelogs/3369.txt | 13 +++++++++ .../android/es-ES/changelogs/3372.txt | 7 +++++ .../android/es-ES/changelogs/3381.txt | 6 ++++ .../android/es-ES/changelogs/3386.txt | 7 +++++ .../android/es-ES/changelogs/3396.txt | 12 ++++++++ .../android/es-ES/changelogs/3427.txt | 11 ++++++++ .../android/es-ES/changelogs/3449.txt | 11 ++++++++ .../android/es-ES/changelogs/3466.txt | 16 +++++++++++ .../android/es-ES/changelogs/3474.txt | 8 ++++++ .../android/es-ES/changelogs/3522.txt | 14 ++++++++++ .../android/es-ES/changelogs/5909.txt | 6 ++-- .../android/es-ES/full_description.txt | 28 ++++++++++++++----- 34 files changed, 342 insertions(+), 29 deletions(-) create mode 100644 fastlane/metadata/android/es-ES/changelogs/3063.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3079.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3101.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3103.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3123.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3137.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3149.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3162.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3175.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3195.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3213.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3243.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3251.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3272.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3287.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3305.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3330.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3333.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3335.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3369.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3372.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3381.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3386.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3396.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3427.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3449.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3466.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3474.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/3522.txt diff --git a/fastlane/metadata/android/es-ES/changelogs/2916.txt b/fastlane/metadata/android/es-ES/changelogs/2916.txt index 795ab5ddd..890818ab3 100644 --- a/fastlane/metadata/android/es-ES/changelogs/2916.txt +++ b/fastlane/metadata/android/es-ES/changelogs/2916.txt @@ -1,15 +1,15 @@ v3.0.0: OpenTracks -Primer lanzamiento de OpenTracks (es decir, una bifurcación de MyTracks) +Primera versión de OpenTracks (es decir, una bifurcación dura de MyTracks) Cambios: - Cambio de nombre de MyTracks a OpenTracks -- Se agregó un sensor de frecuencia cardíaca Bluetooth LE -- Se eliminaron todas las funciones que requieren acceso a Internet. -- Se eliminó la integración de Google Maps -- Se eliminó el uso de los servicios de Google Play. -- Se eliminaron los sensores ANT+ -- Se eliminaron los sensores clásicos de Bluetooth. -- Se eliminó Protobuf -- Se eliminaron la estimación de calorías y la estimación de actividad. -- Se eliminó la API del servicio MyTracks (es decir, no hay integración con otras aplicaciones) +- Se ha añadido el sensor de frecuencia cardíaca Bluetooth LE +- Se han eliminado todas las funciones que requerían acceso a Internet +- Se ha eliminado la integración con Google Maps +- Se ha eliminado el uso de los servicios de Google Play +- Se han eliminado los sensores ANT+ +- Se han eliminado los sensores Bluetooth clásicos +- Se ha eliminado Protobuf +- Se han eliminado la estimación de calorías y la estimación de actividad +- Se ha eliminado la API del servicio MyTracks (es decir, no hay integración con otras aplicaciones) diff --git a/fastlane/metadata/android/es-ES/changelogs/2947.txt b/fastlane/metadata/android/es-ES/changelogs/2947.txt index bcdc95e75..cb3969e38 100644 --- a/fastlane/metadata/android/es-ES/changelogs/2947.txt +++ b/fastlane/metadata/android/es-ES/changelogs/2947.txt @@ -13,5 +13,5 @@ Correcciones de errores: - Se ha corregido la exportación a KML/KMZ Desarrollador: -- Las pruebas son ejecutables (pero muchas fallan) +- Las pruebas se pueden ejecutar (aunque muchas fallan) - Se ha habilitado la integración continua con TravisCI (sin pruebas) diff --git a/fastlane/metadata/android/es-ES/changelogs/2991.txt b/fastlane/metadata/android/es-ES/changelogs/2991.txt index 5bdd82f3f..96c8efcc0 100644 --- a/fastlane/metadata/android/es-ES/changelogs/2991.txt +++ b/fastlane/metadata/android/es-ES/changelogs/2991.txt @@ -1,14 +1,14 @@ v3.1.0: OpenTracks Cambios: -- Se agregó la capacidad de mostrar rutas en la aplicación de mapas (probado con OsmAnd y Maps.ME). -- Se ha añadido la posibilidad de visualizar las estadísticas de seguimiento sin desbloquear el dispositivo durante el mismo. -- Se ha eliminado el modo horizontal de la vista de detalles de la pista (debido a un error). -- El historial de sugerencias de búsqueda ha sido eliminado. +- Se ha añadido la opción de mostrar el recorrido en aplicaciones de mapas (probado con OsmAnd y Maps.ME) +- Se ha añadido la opción de mostrar las estadísticas del recorrido sin desbloquear el dispositivo mientras se realiza el seguimiento +- Se ha eliminado el modo horizontal para la vista detallada del recorrido (presentaba errores) +- Se ha eliminado el historial de sugerencias de búsqueda Corrección de errores: -- Solicitar permisos de almacenamiento para todas las importaciones/todas las exportaciones +- Se solicita permiso de almacenamiento para «Importar todo»/«Exportar todo» -Desarrolladores: -- Actualizar a Gradle 5.6 -- No minimice la versión de lanzamiento +Desarrollador: +- Actualización a Gradle 5.6 +- No se minimizan las compilaciones de lanzamiento diff --git a/fastlane/metadata/android/es-ES/changelogs/3063.txt b/fastlane/metadata/android/es-ES/changelogs/3063.txt new file mode 100644 index 000000000..aa8f6b945 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3063.txt @@ -0,0 +1,7 @@ +v3.2.1: OpenTracks + +Cambios: +- Los ajustes ahora están en una sola página + +Desarrollo: +-Los ajustes se migraron a Androidx compat (usa fragments) diff --git a/fastlane/metadata/android/es-ES/changelogs/3079.txt b/fastlane/metadata/android/es-ES/changelogs/3079.txt new file mode 100644 index 000000000..5a3533d03 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3079.txt @@ -0,0 +1,13 @@ +v3.2.2: OpenTracks + +Cambios: +- Se eliminaron las estadísticas agregadas +- Migrado a Material Design + +Corrección de errores: +- Se corrigió la exportación de datos de sensores +- Se corrigió la importación de datos de sensores incompletos + +Desarrollador: +- Se reemplazó la barra de acción (actionbar) por la barra de herramientas (toolbar) +- Migrado a androidx appcompat (incl. Material Design) diff --git a/fastlane/metadata/android/es-ES/changelogs/3101.txt b/fastlane/metadata/android/es-ES/changelogs/3101.txt new file mode 100644 index 000000000..07466602f --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3101.txt @@ -0,0 +1,14 @@ +v3.2.3: OpenTracks + +Cambios: +- Compartir: usar Android Sharesheet +- Compartir: compartir múltiples pistas como archivos individuales +- Usar Android ShareSheet +- Sensor de frecuencia cardíaca (Bluetooth LE): reconexión automática + +Corrección de errores: +- Sensor de frecuencia cardíaca (Bluetooth LE): desconexión correcta +- La frecuencia cardíaca no siempre se mostraba durante la grabación + +Desarrollador: +- Se habilitaron las pruebas de instrumentación en TravisCI diff --git a/fastlane/metadata/android/es-ES/changelogs/3103.txt b/fastlane/metadata/android/es-ES/changelogs/3103.txt new file mode 100644 index 000000000..acdf0eb26 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3103.txt @@ -0,0 +1,5 @@ +v3.2.4: OpenTracks + +Corrección de errores: +- Caída/cierre inesperado (crash) en la actividad de ajustes + Se eliminó la posibilidad de cambiar la frecuencia de voz/fracción desde el menú contextual diff --git a/fastlane/metadata/android/es-ES/changelogs/3123.txt b/fastlane/metadata/android/es-ES/changelogs/3123.txt new file mode 100644 index 000000000..d2b78bb73 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3123.txt @@ -0,0 +1,15 @@ +v3.2.5: OpenTracks + +Cambios: +- Deshabilitar el cambio de actividad +- Añadir permiso para Android 10: grabación de datos de ubicación + +Corrección de errores: +- Corregir el restablecimiento de los ajustes + +Desarrollador: +- Preferencias + - si no se pueden cargar los Ajustes, restablecer todas las preferencias + - se movió el acceso a las preferencias a PreferencesUtil + - se movieron los valores por defecto de las preferencias del código a XML + - usar SummaryProvider diff --git a/fastlane/metadata/android/es-ES/changelogs/3137.txt b/fastlane/metadata/android/es-ES/changelogs/3137.txt new file mode 100644 index 000000000..5011c7b44 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3137.txt @@ -0,0 +1,8 @@ +v3.2.6: OpenTracks + +Cambios: +- Monitor de frecuencia cardíaca: escanear dispositivos disponibles +- Eliminar acceso directo a los ajustes de Bluetooth + +Desarrollador: +- Preferencias: se movieron los valores por defecto de las preferencias del código a XML diff --git a/fastlane/metadata/android/es-ES/changelogs/3149.txt b/fastlane/metadata/android/es-ES/changelogs/3149.txt new file mode 100644 index 000000000..4e17f8483 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3149.txt @@ -0,0 +1,10 @@ +v3.2.7: OpenTracks + +Cambios: +- La notificación muestra la precisión del GPS +- Mostrar notificación emergente (heads-up) si la precisión del GPS cae por debajo del umbral +- Mostrar siempre el gráfico tanto por tiempo como por distancia +- Se añadió ayuda (en Ajustes) + +Corrección de errores: +- TrackDetailActivity: se deshabilitó el modo horizontal que no funcionaba (temporal) diff --git a/fastlane/metadata/android/es-ES/changelogs/3162.txt b/fastlane/metadata/android/es-ES/changelogs/3162.txt new file mode 100644 index 000000000..9840b426e --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3162.txt @@ -0,0 +1,10 @@ +v3.2.8: OpenTracks + +Cambios: +- Gráfico: zoom mediante gestos + +Corrección de errores: +- La exportación a través de KMZ no exportaba correctamente los nombres de archivo de las imágenes +- Compartir múltiples pistas no incluía los archivos KMZ +- No se mostraba la interfaz de usuario de MarkerDetailActivity +- Los ajustes se cerraban inesperadamente si la preferencia de frecuencia cardíaca no estaba inicializada diff --git a/fastlane/metadata/android/es-ES/changelogs/3175.txt b/fastlane/metadata/android/es-ES/changelogs/3175.txt new file mode 100644 index 000000000..a511e55e7 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3175.txt @@ -0,0 +1,10 @@ +v3.2.9: OpenTracks + +Cambios: +- Opción de mantener la pantalla encendida durante la grabación +- Marcadores: se eliminaron por completo los marcadores de división (split markers) +- Los anuncios de voz ahora funcionan sin el permiso READ_PHONE_STATE + +Corrección de errores: +- Estadísticas: no se mostraba la frecuencia cardíaca +- Estadísticas: el tiempo se restablecía brevemente a 0s diff --git a/fastlane/metadata/android/es-ES/changelogs/3195.txt b/fastlane/metadata/android/es-ES/changelogs/3195.txt new file mode 100644 index 000000000..f6a1cdc47 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3195.txt @@ -0,0 +1,11 @@ +v3.2.10: OpenTracks + +Cambios: +- ChartView ya no es configurable +- Después de reiniciar: se eliminó el reinicio automático de una grabación incompleta + +Corrección de errores: +- Los anuncios de voz usan MAY_DUCK (de nuevo) +- ChartView muestra marcadores (de nuevo) +- Se corrigió el diseño de grabación en horizontal (landscape recordingLayout) de TrackListActivity +- Se solicita el comportamiento heredado de la API de archivos (necesario para Android 10) diff --git a/fastlane/metadata/android/es-ES/changelogs/3213.txt b/fastlane/metadata/android/es-ES/changelogs/3213.txt new file mode 100644 index 000000000..6063eb85c --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3213.txt @@ -0,0 +1,12 @@ +v3.2.11: OpenTracks + +Error: +- La velocidad máxima es idéntica a la velocidad promedio + +Corrección de errores: +- Cierre inesperado (crash) al seleccionar el sensor de frecuencia cardíaca + +Desarrollador: +- Se reemplazó TabHost por TabsLayout +- Se eliminó el primer punto de ruta (waypoint) especial +- Se eliminaron las traducciones duplicadas diff --git a/fastlane/metadata/android/es-ES/changelogs/3243.txt b/fastlane/metadata/android/es-ES/changelogs/3243.txt new file mode 100644 index 000000000..9ac3447a7 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3243.txt @@ -0,0 +1,13 @@ +v3.3.0: OpenTracks + +Cambios importantes: +- Fotos en almacenamiento privado (ocultas para otras apps) + NOTA: No se verán fotos de versiones anteriores +- Español (Latinoamérica) requiere Android 7.0 o superior + +Cambios: +- Importar/exportar vía Storage Access Framework (sin permisos de almacenamiento) + +Desarrollador: +- Limpieza de estructuras de datos internas +- Icono/nombre distintos para versión debug diff --git a/fastlane/metadata/android/es-ES/changelogs/3251.txt b/fastlane/metadata/android/es-ES/changelogs/3251.txt new file mode 100644 index 000000000..a4f83efaf --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3251.txt @@ -0,0 +1,11 @@ +v3.3.1: OpenTracks + +Cambios: +- Android 10: permite conservar datos al desinstalar +- API Dashboard: icono de mapa para abrir la pista en app externa (ej. OSMDashboard) + +Corrección de errores: +- Se corrigió el nombre de la app (roto en v3.3.0) + +Desarrollador: +- Documentación de la API Dashboard en README_DEVELOPER.md diff --git a/fastlane/metadata/android/es-ES/changelogs/3272.txt b/fastlane/metadata/android/es-ES/changelogs/3272.txt new file mode 100644 index 000000000..e852468d5 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3272.txt @@ -0,0 +1,11 @@ +v3.3.2: OpenTracks + +Cambios: +- Exportación: ahora sobrescribe los archivos + +Corrección de errores: +- Se elimina siempre la notificación de grabación + +Desarrollador: +- En API 26+ se usa java.time para analizar y escribir fechas +- Refactorización de TrackRecordingService diff --git a/fastlane/metadata/android/es-ES/changelogs/3287.txt b/fastlane/metadata/android/es-ES/changelogs/3287.txt new file mode 100644 index 000000000..e92cb73d9 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3287.txt @@ -0,0 +1,12 @@ +v3.4.0: OpenTracks + +Cambios: +- API Dashboard: múltiples pistas +- Ayuda actualizada + +Corrección de errores: +- Se corrigió el cierre inesperado al recrear fragmentos +- En pausa: evita que la app se detenga + +Desarrollador: +- Pruebas habilitadas vía CI (Github Actions y TravisCI) diff --git a/fastlane/metadata/android/es-ES/changelogs/3305.txt b/fastlane/metadata/android/es-ES/changelogs/3305.txt new file mode 100644 index 000000000..59d726826 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3305.txt @@ -0,0 +1,14 @@ +v3.5.0: OpenTracks + +Cambios: +- Marcador: seleccionar imágenes de la galería +- Marcador: compartir imagen +- Utilizar el nombre de la ruta como nombre de archivo al compartir +- Se han eliminado los valores mínimos y máximos de altitud (eran imprecisos) + +Corrección de errores: +- Los gráficos utilizan las unidades y la escala preferidas + +Desarrollador: +- Base de datos: eliminar columnas en desuso +- Base de datos: añadir índices diff --git a/fastlane/metadata/android/es-ES/changelogs/3330.txt b/fastlane/metadata/android/es-ES/changelogs/3330.txt new file mode 100644 index 000000000..3d9780b07 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3330.txt @@ -0,0 +1,10 @@ +v3.6.0: OpenTracks + +Cambios: +- Ahora se pueden reanudar las rutas que se habían detenido + +Corrección de errores: +- La importación de archivos KMZ no procesaba correctamente la URL de las fotos + +Desarrollador: +- Se ha simplificado el procesamiento de datos diff --git a/fastlane/metadata/android/es-ES/changelogs/3333.txt b/fastlane/metadata/android/es-ES/changelogs/3333.txt new file mode 100644 index 000000000..a827ee710 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3333.txt @@ -0,0 +1,4 @@ +v3.6.1: OpenTracks + +Corrección de errores: +- Importación de KMZ: guardar la ruta correcta de la imagen diff --git a/fastlane/metadata/android/es-ES/changelogs/3335.txt b/fastlane/metadata/android/es-ES/changelogs/3335.txt new file mode 100644 index 000000000..3ceee7d93 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3335.txt @@ -0,0 +1,4 @@ +v3.6.2: OpenTracks + +Corrección de errores: +- Los gráficos no se mostraban correctamente (problema introducido en la v3.6.0) diff --git a/fastlane/metadata/android/es-ES/changelogs/3369.txt b/fastlane/metadata/android/es-ES/changelogs/3369.txt new file mode 100644 index 000000000..613b00a2e --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3369.txt @@ -0,0 +1,13 @@ +v3.7.0: OpenTracks + +Cambios: +- Informe de velocidad/ritmo por tipo de actividad +- Se ha añadido una solución provisional para el cambio de semana del GPS +- (Experimental) Compatibilidad con sensores de ciclismo Bluetooth LE (cadencia y velocidad) +- Se ha mejorado la importación de KMZ/KML + +Corrección de errores: +- Importación de KMZ: solo se importan las imágenes utilizadas + +Desarrollador: +- Se ha refactorizado TrackRecordingService diff --git a/fastlane/metadata/android/es-ES/changelogs/3372.txt b/fastlane/metadata/android/es-ES/changelogs/3372.txt new file mode 100644 index 000000000..a1eb95948 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3372.txt @@ -0,0 +1,7 @@ +v3.7.1: OpenTracks + +Solución provisional: +- Ocultar el botón de inicio del GPS + +Corrección de errores: +- Se ha corregido un puntero nulo diff --git a/fastlane/metadata/android/es-ES/changelogs/3381.txt b/fastlane/metadata/android/es-ES/changelogs/3381.txt new file mode 100644 index 000000000..cd80cacab --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3381.txt @@ -0,0 +1,6 @@ +v3.7.2: OpenTracks + +Corrección de errores: +- Se ha vuelto a añadir el botón «Iniciar GPS» +- El nombre de la ruta ya no se sobrescribe al final de una grabación +- Marcadores: al eliminar una foto, también se borra el archivo diff --git a/fastlane/metadata/android/es-ES/changelogs/3386.txt b/fastlane/metadata/android/es-ES/changelogs/3386.txt new file mode 100644 index 000000000..63efb3d6d --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3386.txt @@ -0,0 +1,7 @@ +v3.7.3: OpenTracks + +Corrección de errores: +- Se ha corregido el cálculo de la cadencia y la velocidad (mediante el sensor Bluetooth LE) + +Desarrollador: +- Se han eliminado las traducciones que no se utilizaban diff --git a/fastlane/metadata/android/es-ES/changelogs/3396.txt b/fastlane/metadata/android/es-ES/changelogs/3396.txt new file mode 100644 index 000000000..42a5be9fb --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3396.txt @@ -0,0 +1,12 @@ +v3.7.4: OpenTracks + +Cambios: +- Formato de compartir/exportar configurable + +Corrección de errores: +- El diámetro de la rueda no se almacenaba correctamente (para el sensor de velocidad) +- Ayuda: los enlaces son clicables + +Desarrollador: +- Se ha dividido TrackDetailActivity +- El sucesor de StatsFragment utiliza ConstraintLayout diff --git a/fastlane/metadata/android/es-ES/changelogs/3427.txt b/fastlane/metadata/android/es-ES/changelogs/3427.txt new file mode 100644 index 000000000..a9bcae53d --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3427.txt @@ -0,0 +1,11 @@ +v3.7.5: OpenTracks + +Cambios: +- Se ha añadido un resumen de estadísticas agregadas (desde el menú de la pantalla principal) +- API del panel de control: se pasan los ajustes «mostrar en la pantalla de bloqueo» y «mantener la pantalla encendida» +- Se han actualizado las traducciones +- Importación: se ha flexibilizado el formato de fecha ISO 8601 +- Mostrar ruta en el mapa: se puede seleccionar el formato de archivo y la apertura del panel de control funciona en Android R (API 30+) + +Corrección de errores: +- El icono de la aplicación ahora se muestra correctamente diff --git a/fastlane/metadata/android/es-ES/changelogs/3449.txt b/fastlane/metadata/android/es-ES/changelogs/3449.txt new file mode 100644 index 000000000..221d01a5e --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3449.txt @@ -0,0 +1,11 @@ +v3.7.6: OpenTracks + +Cambios: +- Modo horizontal +- La interfaz de usuario para borrar rutas ahora es solo un indicador giratorio +- La velocidad se exporta e importa (GPX/KML/KMZ) +- El formato de exportación predeterminado es KMZ, incluyendo todos los datos y las imágenes + +Desarrollador: +- Se han sustituido las AsyncTasks por hilos +- Sincronización: se genera un UUID por cada ruta (aún no se exporta) diff --git a/fastlane/metadata/android/es-ES/changelogs/3466.txt b/fastlane/metadata/android/es-ES/changelogs/3466.txt new file mode 100644 index 000000000..bd17770cc --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3466.txt @@ -0,0 +1,16 @@ +v3.8.0: OpenTracks + +Cambio importante: +- Importación/exportación de GPX de velocidad, cadencia y frecuencia cardíaca a través de TrackPointExtension/v2 + +Cambios: +- API del panel de control: permite acceder a los marcadores (incluida una llamada de retorno para mostrarlos en OpenTracks) +- Nuevo icono de marcador (chincheta naranja, SVG) +- Los archivos que se van a importar se pueden seleccionar a través del gestor de archivos + +Correcciones de errores: +- La interfaz de usuario de estadísticas agregadas respeta la frecuencia preferida +- Fallaba la edición de un marcador al cambiar la orientación + +Desarrollador: +- Se ha refactorizado TrackRecordingService diff --git a/fastlane/metadata/android/es-ES/changelogs/3474.txt b/fastlane/metadata/android/es-ES/changelogs/3474.txt new file mode 100644 index 000000000..e0438fac4 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3474.txt @@ -0,0 +1,8 @@ +v3.8.1: OpenTracks + +Cambios: +- Durante la grabación: se ha añadido la opción de pantalla completa +- El panel de control se inicia en la misma tarea + +Correcciones de errores: +- Los filtros de intención para importar archivos eran demasiado optimistas diff --git a/fastlane/metadata/android/es-ES/changelogs/3522.txt b/fastlane/metadata/android/es-ES/changelogs/3522.txt new file mode 100644 index 000000000..10d46a963 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/3522.txt @@ -0,0 +1,14 @@ +v3.9.0: OpenTracks + +Cambios: +- Exportación de rutas con su UUID +- Se evita la importación de rutas ya existentes por su UUID (configurable; por defecto, activado) +- Ahora hay que mantener pulsado el botón de pausa/parada +- El preinicio del GPS muestra el estado de la señal +- Se han añadido nuevos iconos de actividad +- Sensor de velocidad: ahora se puede configurar el tamaño de la rueda en milímetros + +Correcciones de errores: +- Se han corregido algunos fallos poco frecuentes (NPE) +- Se ha corregido el formato del ritmo +- Se ha cambiado la posición del icono de la aplicación en el lanzador diff --git a/fastlane/metadata/android/es-ES/changelogs/5909.txt b/fastlane/metadata/android/es-ES/changelogs/5909.txt index e2acf3973..671d8f9d1 100644 --- a/fastlane/metadata/android/es-ES/changelogs/5909.txt +++ b/fastlane/metadata/android/es-ES/changelogs/5909.txt @@ -1,7 +1,7 @@ v4.14.1: OpenTracks Cambios: -- Detección de inactividad se puede desactivar +- Se puede desactivar la detección de inactividad -Correcciones de errores: -- UI para añadir imágenes a los marcadores estaba oculto +Corrección de errores: +- La interfaz de usuario para añadir imágenes a los marcadores estaba oculta diff --git a/fastlane/metadata/android/es-ES/full_description.txt b/fastlane/metadata/android/es-ES/full_description.txt index 30c841fa5..29836da86 100644 --- a/fastlane/metadata/android/es-ES/full_description.txt +++ b/fastlane/metadata/android/es-ES/full_description.txt @@ -1,8 +1,22 @@ -OpenTracks es un tracker deportivo que respeta tu privacidad. +Un compañero de seguimiento deportivo que respeta tu privacidad. -Funciones: -* Tracker deportivo: haz un seguimiento de tus actividades al aire libre -* Marca lugares de interés, incluso agrega fotos a los marcadores -* Pulsómetro (compatible con dispositivos Bluetooth LE) -* Exporta tus rutas como KML o GPX -* Completamente offline, no necesitas internet +El deporte y las actividades al aire libre aportan felicidad, destreza y confianza. + +Cuida tu salud llevando un registro de tu entrenamiento. +Registra tus sesiones de carrera o caminata, y te ofrece un ciclocomputador con una pantalla más grande para el ciclismo. +Marca lugares interesantes a lo largo de tu recorrido con fotos. +Guarda estadísticas detalladas para su análisis. +Comparte solo los datos que quieras que vean los demás. + +* Avisos de voz. +* Compatible con sensores Bluetooth LE: frecuencia cardíaca, velocidad y distancia (ciclismo), cadencia (ciclismo) y medidor de potencia (ciclismo). +* Desnivel positivo y negativo: mediante sensor barométrico. +* Altitud mostrada en EGM2008 (por encima del nivel medio del mar); exportada como WGS84. +* Exporta los datos como rutas en formato KMZ (incluidas fotos), KML o GPX. +* No requiere acceso a Internet ni permisos adicionales. +* Temas oscuro y claro, respetando la configuración del sistema. +* Sin anuncios. + +Software libre / Software gratuito / Código abierto, +lo que significa que puedes utilizar, estudiar, modificar y compartir el código fuente. +Licencia Apache 2.0 From 465e3e9be84f37dbad815e6c3f66de9cc1d5508c Mon Sep 17 00:00:00 2001 From: Kito Date: Thu, 3 Sep 2026 09:20:37 +0000 Subject: [PATCH 34/35] Translated using Weblate (Indonesian) Currently translated at 3.6% (7 of 191 strings) Translation: OpenTracks/App store metadata files Translate-URL: https://translate.codeberg.org/projects/opentracks/app-store-metadata-files/id/ --- .../metadata/android/id/changelogs/2916.txt | 15 +++++++++++++++ .../metadata/android/id/changelogs/2947.txt | 17 +++++++++++++++++ .../metadata/android/id/changelogs/2991.txt | 14 ++++++++++++++ .../metadata/android/id/full_description.txt | 6 +++--- .../metadata/android/id/short_description.txt | 2 +- 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 fastlane/metadata/android/id/changelogs/2916.txt create mode 100644 fastlane/metadata/android/id/changelogs/2947.txt create mode 100644 fastlane/metadata/android/id/changelogs/2991.txt diff --git a/fastlane/metadata/android/id/changelogs/2916.txt b/fastlane/metadata/android/id/changelogs/2916.txt new file mode 100644 index 000000000..955dddaff --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/2916.txt @@ -0,0 +1,15 @@ +v3.0.0: OpenTracks + +Rilisan pertama OpenTracks (fork keras dari MyTracks) + +Perubahan: +- Perubahan merek MyTracks ke OpenTracks +- Menambahkan sensor detak jantung Bluetooth LE +- Menghapus semua fitur yang memerlukan koneksi internet +- Menghapus integrasi Google Maps +- Menghapus penggunaan layanan Google Play +- Menghapus sensor ANT+ +- Menghapus sensor klasik Bluetooth +- Menghapus Protobuf +- Menghapus estimasi kalori dan aktivitas +- Menghapus API MyTracks Service (dengan kata lain, tidak ada integrasi dengan aplikasi lain) diff --git a/fastlane/metadata/android/id/changelogs/2947.txt b/fastlane/metadata/android/id/changelogs/2947.txt new file mode 100644 index 000000000..2bd3ed73f --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/2947.txt @@ -0,0 +1,17 @@ +v3.0.1: OpenTracks + +Perubahan: +- Bagikan penanda +- Perubahan struktur pengaturan +- Menghapus tombol kembali di bilah aksi +- Ekspor: menghapus pilihan pengaturan ukuran foto +- Ekspor: KML sebagai format sekarang dapat dipilih + +Perbaikan Bug: +- direktori ekspor sekarang di /OpenTracks/ +- mode lanskap tidak akan mogok +- memperbaiki ekspor KML/KMZ + +Pengembang: +- Tes dapat dieksekusi (namun banyak yang gagal) +- Mengaktifkan integrasi terus menerus dengan TravisCI (tanpa tes) diff --git a/fastlane/metadata/android/id/changelogs/2991.txt b/fastlane/metadata/android/id/changelogs/2991.txt new file mode 100644 index 000000000..4952bb91a --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/2991.txt @@ -0,0 +1,14 @@ +v3.1.0: OpenTracks + +Perubahan: +- Menambahkan opsi tampilan jalur salju di peta aplikasi (telah diuji dengan OsmAnd dan Maps.ME) +- Menambahkan ketika melacak, akan menampilkan statistik jalur tanpa perlu membuka kunci perangkat +- Menghapus mode landkap untuk tampilan detail jalur (tidak stabil) +- Menghapus riwayat saran penelusuran + +Perbaikan Bug: +- Permintaan izin penyimpanan untuk impor/ekspor semua + +Pengembang: +- Pembaruan ke Gradle 5.6 +- Jangan mengecilkan (minify) rilisan build diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt index c0750c153..15f8af9de 100644 --- a/fastlane/metadata/android/id/full_description.txt +++ b/fastlane/metadata/android/id/full_description.txt @@ -1,4 +1,4 @@ -Teman pelacakan olahraga yang menghormati privasi Anda. +Sebuah teman pelacak olahraga yang benar-benar menghormati privasi Anda. Olahraga dan aktivitas luar ruangan memberikan kebahagiaan, keterampilan, dan kepercayaan diri. @@ -11,12 +11,12 @@ Hanya membagikan data yang Anda ingin orang lain memilikinya. * Notifikasi suara. * Mendukung sensor Bluetooth LE: detak jantung, kecepatan dan jarak (bersepeda), irama (bersepeda), dan pengukur daya (bersepeda). * Kenaikan dan penurunan ketinggian: melalui sensor barometrik. -* Ketinggian yang ditampilkan dalam EGM2008 (di atas permukaan laut); diekspor sebagai WGS84. +* Ketinggian ditampilkan dalam EGM2008 (di atas permukaan laut); diekspor sebagai WGS84. * Mengekspor data sebagai lintasan, baik sebagai KMZ (termasuk foto), KML, atau GPX. * Tidak ada akses Internet atau izin tambahan. * Tema gelap dan terang, dengan memperhatikan pengaturan sistem. * Tanpa iklan. -Perangkat lunak bebas / Perangkat Lunak Bebas / Sumber Terbuka +Perangkat Lunak Bebas / Sumber Terbuka yang berarti Anda dapat menggunakan, mempelajari, mengubah, dan berbagi kode sumber. Berlisensi Apache 2.0 diff --git a/fastlane/metadata/android/id/short_description.txt b/fastlane/metadata/android/id/short_description.txt index e437e8f14..6fff77bc8 100644 --- a/fastlane/metadata/android/id/short_description.txt +++ b/fastlane/metadata/android/id/short_description.txt @@ -1 +1 @@ -Sebuah pelacak olahraga yang benar-benar menghormati privasi Anda. +Sebuah teman pelacak olahraga yang benar-benar menghormati privasi Anda. From f8279c70958963eba308f641eb86f342d3e5f19d Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Wed, 2 Sep 2026 10:30:20 +0000 Subject: [PATCH 35/35] Translated using Weblate (Spanish) Currently translated at 100.0% (551 of 551 strings) Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/es/ --- src/main/res/values-es/strings.xml | 55 ++++++++++++++++++------------ 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 10370e1ae..f2d41c4ba 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -15,11 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. --> Acerca de - OpenTracks es una aplicación de seguimiento deportivo que respeta completamente su privacidad. + OpenTracks ies una aplicación de seguimiento deportivo que respeta por completo tu privacidad. Página web del proyecto: %1$s Nombre de la versión: %1$s Código de versión: %1$d - OpenTracks es una bifurcación de MyTracks de Google (descontinuado en 2016). + OpenTracks es una bifurcación de MyTracks de Google (descontinuada en 2016). Bibliotecas OpenTracks tiene las siguientes características: @@ -30,10 +30,7 @@ limitations under the License. \n* funciona completamente sin conexión \n* mostrar pistas en un mapa (ver más abajo) ¿Cómo mostrar un recorrido en un mapa\? - Utilice el pequeño icono de mapa. -\na) (actualizaciones en vivo) Instale OSMDashboard . Proporciona mapas en línea o fuera de línea a través de OpenStreetMap . -\nEn realidad, OSMDashboard es una aplicación separada que solo puede acceder (temporalmente) a los datos de los recorridos que se mostrarán. -\nb) A través de una aplicación de mapas normal (p. ej., OsmAnd y MAPS.ME ). Estas aplicaciones deben admitir el formato de archivo KMZ (solo ubicaciones y marcas de tiempo). De hecho, una aplicación debe admitir ACTION_VIEW con MIME application / vnd.google-earth.kmz. + Utiliza el icono del mapa pequeño.\n a) (actualizaciones en tiempo real) Instala OSMDashboard. Ofrece mapas en línea o sin conexión a través de OpenStreetMap.\n En realidad, OSMDashboard es una aplicación independiente que solo puede acceder (temporalmente) a los datos de las rutas que se van a mostrar.\n b) A través de una aplicación de mapas normal (por ejemplo, OsmAnd y MAPS.ME). Estas aplicaciones deben ser compatibles con el formato de archivo KMZ (solo ubicaciones y marcas de tiempo). De hecho, una aplicación debe ser compatible con ACTION_VIEW con el MIME application/vnd.google-earth.kmz. ¿Cómo almacena OpenTracks las imágenes que se tomaron como marcadores\? Estos se almacenan en el almacenamiento interno de la aplicación (es decir, solo se puede acceder a ellos dentro de OpenTracks). ¿Cómo puedo exportar/compartir recorridos grabados\? @@ -44,22 +41,9 @@ limitations under the License. ¿Cómo almacena OpenTracks los datos de los sensores? Los datos del sensor se almacenan por ubicación registrada. En términos de un sensor de frecuencia cardíaca, esto significa que solo se almacena una medición de frecuencia cardíaca por ubicación. Si está parado o está en interiores, no se registrarán los datos del sensor (pero se mostrarán). ¿OpenTracks no registra ubicaciones\? - 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 GPSTest , y -\nd) desactivar el ahorro de energía. -\n -\nSi todo no ayuda, informe un error. + ¿Qué puedes hacer?:\n a) comprueba si el GPS está activado,\n b) mejora la recepción del GPS (por ejemplo, sal al aire libre),\n c) compruébalo con la aplicación GPSTest, y\n d) desactiva el modo de ahorro de energía.\n \n Si nada de esto funciona, por favor, informa de un error. ¿Cómo puedo ayudar a OpenTracks\? - Usted puede: -\na) haga correr la voz sobre OpenTracks, -\nb) proponer funciones , -\nc) informar errores , -\nd) ayuda en el desarrollo de software, -\ne) comprar en Google Play Store , -\nf) donar, y -\ng) haga lo que crea que ayuda a OpenTracks. + Puedes:\n a) dar a conocer OpenTracks,\n b) proponer nuevas funciones,\n c) informar de errores,\n d) colaborar en el desarrollo del software,\n e) comprarlo en Google Play Store,\n f) hacer una donación, y\n g) hacer lo que creas que ayuda a OpenTracks. avión vehículos todo terreno @@ -655,4 +639,33 @@ limitations under the License. Idioma Predeterminado del sistema Ganancia y pérdida + Permisos de Health Connect + ¿Cómo usa OpenTracks Health Connect? + Si se activa en los ajustes, OpenTracks registra los entrenamientos en Health Connect. OpenTracks no lee datos de Health Connect. + ciclismo de grava + Temperatura (Celsius) + %1$d de %2$d: %3$s + Exportar todo + Termómetro + Gráficos + Mostrar altitud + Ver ritmo o velocidad + Ver pulso + GPS desactivado + Aviso de GPS desactivado al grabar. + Sistema (Color dinámico) + Día (Color dinámico) + Noche (Color dinámico) + Optimizado para OLED (Color dinámico) + Velocidad/ritmo medio (tramo) + Exportar a Health Connect + Ritmo medio del tramo + Tiempo de tramo transcurrido + Temperatura + Diferencia horaria + Pulso (vuelta) + Exportar la actividad a Health Connect al finalizar + Dispositivo no compatible con Health Connect + Ajustes de Health Connect + Abrir los ajustes de Health Connect