Compare commits

...

21 Commits

Author SHA1 Message Date
Dennis Guse f59ae51c6f Release: v4.8.2 2023-08-27 09:01:20 +02:00
Max Ch 19a5d46e88 Translated using Weblate (Ukrainian)
Currently translated at 97.8% (502 of 513 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2023-08-26 19:40:32 +02:00
josé m d2b64391d9 Translated using Weblate (Galician)
Currently translated at 10.3% (12 of 116 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/gl/
2023-08-26 19:40:32 +02:00
Stephan Paternotte fe920d5b5e Translated using Weblate (Dutch)
Currently translated at 100.0% (116 of 116 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2023-08-26 19:40:32 +02:00
Max Ch d1ba31ff3f Translated using Weblate (Ukrainian)
Currently translated at 93.1% (478 of 513 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2023-08-26 19:40:32 +02:00
josé m e7a01b1cd1 Translated using Weblate (Galician)
Currently translated at 100.0% (513 of 513 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/gl/
2023-08-26 19:40:32 +02:00
Stephan Paternotte 70b65d05c0 Translated using Weblate (Dutch)
Currently translated at 100.0% (115 of 115 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2023-08-26 19:40:32 +02:00
Max Ch c9ad1b45c6 Translated using Weblate (Ukrainian)
Currently translated at 89.4% (459 of 513 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2023-08-26 19:40:32 +02:00
ovl-1 4657a9faa0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.2% (499 of 513 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/nb_NO/
2023-08-26 19:40:32 +02:00
gallegonovato fba6cd7d4a Translated using Weblate (Spanish)
Currently translated at 100.0% (513 of 513 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/es/
2023-08-26 19:40:32 +02:00
Tea-NT 970239c614 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (513 of 513 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/zh_Hans/
2023-08-26 19:40:32 +02:00
Peter Storch f83c23e450 show postworkout error messages, #1669 2023-08-26 19:40:27 +02:00
Dennis Guse 326da494eb Java Record for Track.Id and TrackPoint.Id.
Part of #1526.
2023-08-25 23:52:10 +02:00
Dennis Guse fb85f2e976 Bump com.android.tools.build:gradle from 8.1.1 to 8.2.0-beta01 2023-08-25 23:52:10 +02:00
dependabot[bot] 60a34bfcbb Bump org.mockito:mockito-android from 5.4.0 to 5.5.0
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.4.0...v5.5.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-25 23:31:17 +02:00
Dennis Guse 95816937bc Bugfix: request ACCESS_BACKGROUND_LOCATION fails; removing it for now.
Introduced in 3910837d1a

Part of #1653.
2023-08-22 22:06:56 +02:00
Dennis Guse 4f745a94bf Release: v4.8.1 2023-08-22 07:15:53 +02:00
Dennis Guse 9b6df4bf99 Bugfix: request ACCESS_BACKGROUND_LOCATION fails; removing it for now.
Introduced in 3910837d1a

Part of #1653.
2023-08-22 07:15:08 +02:00
dependabot[bot] 73f325e633 Bump com.android.tools.build:gradle from 8.1.0 to 8.1.1
Bumps com.android.tools.build:gradle from 8.1.0 to 8.1.1.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-22 06:44:06 +02:00
Dennis Guse 28981611f1 GPX export: skip gain/loss if not present.
Part of #1671.
2023-08-20 19:38:37 +02:00
Dennis Guse 7d8c4dcb29 Bugfix: re-introduced race condition fix for newGpsStatus.
Follow-up to e9f24f6bae

Fixes #1668.
2023-08-20 19:28:37 +02:00
42 changed files with 253 additions and 177 deletions
+4 -4
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.1.0' classpath 'com.android.tools.build:gradle:8.2.0-beta01'
} }
} }
@@ -64,8 +64,8 @@ android {
defaultConfig { defaultConfig {
applicationId "de.dennisguse.opentracks" applicationId "de.dennisguse.opentracks"
versionCode 5361 versionCode 5382
versionName "v4.8.0" versionName "v4.8.2"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -141,7 +141,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'org.mockito:mockito-android:5.4.0' androidTestImplementation 'org.mockito:mockito-android:5.5.0'
androidTestUtil 'androidx.test:orchestrator:1.4.2' androidTestUtil 'androidx.test:orchestrator:1.4.2'
} }
@@ -0,0 +1,5 @@
v4.8.1: OpenTracks
Bugfix:
- Location permission was not granted due to ACCESS_LOCATION_BACKGROUND (removed)
@@ -0,0 +1,5 @@
v4.8.2: OpenTracks
Changes:
- After recording export: show error message if unsuccessful
@@ -0,0 +1,4 @@
v4.3.4: OpenTracks
Bugfix:
- Android 13: regional locales could not be selected (introduced in v4.3.0)
@@ -0,0 +1,5 @@
v4.3.5: OpenTracks
Bugfix:
- Sensor parsing had a overflow one off bug (#1559)
- Insert marker: don't crash if no camera app is installed (#1561)
@@ -0,0 +1,8 @@
v4.7.1: OpenTracks
Bugfixes:
- Android 14: crash on recording start (missing permissions)
- Android 14: crash on track deletion (ForegroundService without reason)
Developer:
- AggregatedStats is now a RecyclerView
@@ -0,0 +1,10 @@
v4.8.0: OpenTracks
Cambios:
- Permiso: ACCESS_BACKGROUND_LOCATION é requerido debido á API
- Preparación para a detección da pausa en función do tempoPreparation for time-based idle detection
- Retirada da distinción por orixe nos datos gardados (sensores GPS vs. BLE)
Desenvolvemento:
- retirados algúns atallos no código que entraban en conflito (TrackRecordingService e GpsStatusManager)
- TrackRecordingServiceConnection xestiona o apagado do servizo
@@ -0,0 +1,4 @@
v4.8.1: OpenTracks
Arranxo:
- Non se concedía o permiso de localización debido a ACCESS_LOCATION_BACKGROUND (eliminado)
@@ -0,0 +1,10 @@
v4.8.0: OpenTracks
Veranderingen:
- Toestemming: ACCESS_BACKGROUND_LOCATION is vereist vanwege API
- Voorbereiding voor tijdgebaseerde stationaire detectie
- Onderscheid bron (GPS vs BLE sensoren) vervallen voor opgeslagen gegevens
Ontwikkelaar:
- verwijderde raceconditie-werkplekken (TrackRecordingService en GpsStatusManager)
- TrackRecordingServiceConnection zorgt voor service-uitschakeling
@@ -0,0 +1,4 @@
v4.8.1: OpenTracks
Bugfix :
- Toestemming tot locatie is niet verleend vanwege ACCESS_LOCATION_BACKGROUND (verwijderd)
@@ -9,8 +9,8 @@ public class TestUtil {
public static GrantPermissionRule createGrantPermissionRule() { public static GrantPermissionRule createGrantPermissionRule() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION, android.Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.POST_NOTIFICATIONS); return GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.POST_NOTIFICATIONS);
} }
return GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION, android.Manifest.permission.BLUETOOTH_CONNECT); return GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_CONNECT);
} }
} }
@@ -37,7 +37,7 @@ public class TestDataUtil {
public static Track createTrack(Track.Id trackId) { public static Track createTrack(Track.Id trackId) {
Track track = new Track(); Track track = new Track();
track.setId(trackId); track.setId(trackId);
track.setName("Test: " + trackId.getId()); track.setName("Test: " + trackId.id());
return track; return track;
} }
@@ -119,7 +119,7 @@ public class CustomContentProviderUtilsTest {
assertNotNull(trackPoint); assertNotNull(trackPoint);
locations.add(trackPoint); locations.add(trackPoint);
// Make sure the IDs are returned in the right order. // Make sure the IDs are returned in the right order.
assertEquals(lastPointId.getId() - numPoints + locations.size(), trackPoint.getId().getId()); assertEquals(lastPointId.id() - numPoints + locations.size(), trackPoint.getId().id());
} }
assertEquals(numPoints, locations.size()); assertEquals(numPoints, locations.size());
} }
@@ -128,7 +128,7 @@ public class CustomContentProviderUtilsTest {
private TrackPoint.Id initializeTrack(Track.Id id, int numPoints) { private TrackPoint.Id initializeTrack(Track.Id id, int numPoints) {
Track track = new Track(); Track track = new Track();
track.setId(id); track.setId(id);
track.setName("Test: " + id.getId()); track.setName("Test: " + id.id());
contentProviderUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id); track = contentProviderUtils.getTrack(id);
assertNotNull(track); assertNotNull(track);
@@ -155,7 +155,7 @@ public class CustomContentProviderUtilsTest {
} }
} }
assertTrue(numPoints == 0 || lastPointId.getId() > 0); assertTrue(numPoints == 0 || lastPointId.id() > 0);
assertEquals(numPoints, counter); assertEquals(numPoints, counter);
return lastPointId; return lastPointId;
@@ -172,7 +172,7 @@ public class CustomContentProviderUtilsTest {
// Id // Id
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex); when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false); when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getLong(columnIndex)).thenReturn(trackId.getId()); when(cursorMock.getLong(columnIndex)).thenReturn(trackId.id());
//Uuid //Uuid
columnIndex++; columnIndex++;
@@ -182,7 +182,7 @@ public class CustomContentProviderUtilsTest {
// Name // Name
columnIndex++; columnIndex++;
String name = NAME_PREFIX + trackId.getId(); String name = NAME_PREFIX + trackId.id();
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex); when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false); when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getString(columnIndex)).thenReturn(name); when(cursorMock.getString(columnIndex)).thenReturn(name);
@@ -460,7 +460,7 @@ public class CustomContentProviderUtilsTest {
Marker marker = contentProviderUtils.createMarker(cursorMock); Marker marker = contentProviderUtils.createMarker(cursorMock);
assertEquals(id, marker.getId().id()); assertEquals(id, marker.getId().id());
assertEquals(name, marker.getName()); assertEquals(name, marker.getName());
assertEquals(trackId, marker.getTrackId().getId()); assertEquals(trackId, marker.getTrackId().id());
} }
/** /**
@@ -640,7 +640,7 @@ public class CustomContentProviderUtilsTest {
marker.setDescription(TEST_DESC); marker.setDescription(TEST_DESC);
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker))); Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId()); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.id());
assertTrue(dir.exists()); assertTrue(dir.exists());
assertTrue(dir.isDirectory()); assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length); assertEquals(1, dir.list().length);
@@ -675,7 +675,7 @@ public class CustomContentProviderUtilsTest {
marker.setDescription(TEST_DESC); marker.setDescription(TEST_DESC);
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker))); Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId()); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.id());
assertTrue(dir.exists()); assertTrue(dir.exists());
assertTrue(dir.isDirectory()); assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length); assertEquals(1, dir.list().length);
@@ -712,7 +712,7 @@ public class CustomContentProviderUtilsTest {
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker))); Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
contentProviderUtils.insertMarker(otherMarker); contentProviderUtils.insertMarker(otherMarker);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId()); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.id());
assertTrue(dir.exists()); assertTrue(dir.exists());
assertTrue(dir.isDirectory()); assertTrue(dir.isDirectory());
assertEquals(2, dir.list().length); assertEquals(2, dir.list().length);
@@ -889,7 +889,7 @@ public class CustomContentProviderUtilsTest {
for (int i = 0; i < trackpointIds.size(); i++) { for (int i = 0; i < trackpointIds.size(); i++) {
assertTrue(trackPointIterator.hasNext()); assertTrue(trackPointIterator.hasNext());
TrackPoint trackPoint = trackPointIterator.next(); TrackPoint trackPoint = trackPointIterator.next();
assertEquals(startTrackPointId.getId() + i, trackPoint.getId().getId()); assertEquals(startTrackPointId.id() + i, trackPoint.getId().id());
checkLocation(i, trackPoint.getLocation()); checkLocation(i, trackPoint.getLocation());
} }
@@ -75,6 +75,6 @@ public class TrackNameUtilsTest {
@Test @Test
public void testTrackName_number() { public void testTrackName_number() {
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value)); PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME)); assertEquals("Track " + TRACK_ID.id(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
} }
} }
-2
View File
@@ -31,8 +31,6 @@ xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/
<gpxtrkx:MovingTime>0</gpxtrkx:MovingTime> <gpxtrkx:MovingTime>0</gpxtrkx:MovingTime>
<gpxtrkx:StoppedTime>0</gpxtrkx:StoppedTime> <gpxtrkx:StoppedTime>0</gpxtrkx:StoppedTime>
<gpxtrkx:MaxSpeed>0.0</gpxtrkx:MaxSpeed> <gpxtrkx:MaxSpeed>0.0</gpxtrkx:MaxSpeed>
<gpxtrkx:Ascent>null</gpxtrkx:Ascent>
<gpxtrkx:Descent>null</gpxtrkx:Descent>
</gpxtrkx:TrackStatsExtension> </gpxtrkx:TrackStatsExtension>
</extensions> </extensions>
<trkseg> <trkseg>
+1 -1
View File
@@ -46,7 +46,7 @@ limitations under the License.
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />-->
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -66,7 +66,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
track = contentProviderUtils.getTrack(trackId); track = contentProviderUtils.getTrack(trackId);
if (track == null) { if (track == null) {
Log.e(TAG, "No track for " + trackId.getId()); Log.e(TAG, "No track for " + trackId.id());
finish(); finish();
return; return;
} }
@@ -276,7 +276,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
} }
private void requestRequiredPermissions() { private void requestRequiredPermissions() {
PermissionRequester.ALL.requestPermissionsIfNeeded(this, this, null, (requester) -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show()); PermissionRequester.ALL.requestPermissionsIfNeeded(this, this, null, (requester) -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_LONG).show());
} }
@Override @Override
@@ -189,13 +189,13 @@ public class ContentProviderUtils {
} }
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?"))); String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new)); contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(trackId -> Long.toString(trackId.id())).toArray(String[]::new));
} }
public void deleteTrack(Context context, @NonNull Track.Id trackId) { public void deleteTrack(Context context, @NonNull Track.Id trackId) {
// Delete track folder resources. // Delete track folder resources.
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId)); FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}); contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())});
} }
//TODO Only use for tests; also move to tests. //TODO Only use for tests; also move to tests.
@@ -229,7 +229,7 @@ public class ContentProviderUtils {
} }
public Track getTrack(@NonNull Track.Id trackId) { public Track getTrack(@NonNull Track.Id trackId) {
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) { try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())}, null)) {
if (cursor != null && cursor.moveToNext()) { if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor); return createTrack(cursor);
} }
@@ -278,7 +278,7 @@ public class ContentProviderUtils {
* @param track the track * @param track the track
*/ */
public void updateTrack(Track track) { public void updateTrack(Track track) {
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId().getId())}); contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId().id())});
} }
private ContentValues createContentValues(Track track) { private ContentValues createContentValues(Track track) {
@@ -286,7 +286,7 @@ public class ContentProviderUtils {
TrackStatistics trackStatistics = track.getTrackStatistics(); TrackStatistics trackStatistics = track.getTrackStatistics();
if (track.getId() != null) { if (track.getId() != null) {
values.put(TracksColumns._ID, track.getId().getId()); values.put(TracksColumns._ID, track.getId().id());
} }
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid())); values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
values.put(TracksColumns.NAME, track.getName()); values.put(TracksColumns.NAME, track.getName());
@@ -315,7 +315,7 @@ public class ContentProviderUtils {
} }
public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) { public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) {
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}); contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())});
} }
private ContentValues createContentValues(TrackStatistics trackStatistics) { private ContentValues createContentValues(TrackStatistics trackStatistics) {
@@ -413,7 +413,7 @@ public class ContentProviderUtils {
public Integer getNextMarkerNumber(@NonNull Track.Id trackId) { public Integer getNextMarkerNumber(@NonNull Track.Id trackId) {
String[] projection = {MarkerColumns._ID}; String[] projection = {MarkerColumns._ID};
String selection = MarkerColumns.TRACKID + "=?"; String selection = MarkerColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())}; String[] selectionArgs = new String[]{Long.toString(trackId.id())};
try (Cursor cursor = getMarkerCursor(projection, selection, selectionArgs, MarkerColumns._ID, -1)) { try (Cursor cursor = getMarkerCursor(projection, selection, selectionArgs, MarkerColumns._ID, -1)) {
if (cursor != null) { if (cursor != null) {
return cursor.getCount(); return cursor.getCount();
@@ -443,10 +443,10 @@ public class ContentProviderUtils {
String[] selectionArgs; String[] selectionArgs;
if (minMarkerId != null) { if (minMarkerId != null) {
selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?"; selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minMarkerId.id())}; selectionArgs = new String[]{Long.toString(trackId.id()), Long.toString(minMarkerId.id())};
} else { } else {
selection = MarkerColumns.TRACKID + "=?"; selection = MarkerColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId.getId())}; selectionArgs = new String[]{Long.toString(trackId.id())};
} }
return getMarkerCursor(null, selection, selectionArgs, MarkerColumns._ID, maxCount); return getMarkerCursor(null, selection, selectionArgs, MarkerColumns._ID, maxCount);
} }
@@ -510,7 +510,7 @@ public class ContentProviderUtils {
values.put(MarkerColumns.DESCRIPTION, marker.getDescription()); values.put(MarkerColumns.DESCRIPTION, marker.getDescription());
values.put(MarkerColumns.CATEGORY, marker.getCategory()); values.put(MarkerColumns.CATEGORY, marker.getCategory());
values.put(MarkerColumns.ICON, marker.getIcon()); values.put(MarkerColumns.ICON, marker.getIcon());
values.put(MarkerColumns.TRACKID, marker.getTrackId().getId()); values.put(MarkerColumns.TRACKID, marker.getTrackId().id());
values.put(MarkerColumns.LENGTH, marker.getLength().toM()); values.put(MarkerColumns.LENGTH, marker.getLength().toM());
values.put(MarkerColumns.DURATION, marker.getDuration().toMillis()); values.put(MarkerColumns.DURATION, marker.getDuration().toMillis());
@@ -632,7 +632,7 @@ public class ContentProviderUtils {
@Deprecated @Deprecated
public TrackPoint.Id getLastTrackPointId(@NonNull Track.Id trackId) { public TrackPoint.Id getLastTrackPointId(@NonNull Track.Id trackId) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)"; String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())}; String[] selectionArgs = new String[]{Long.toString(trackId.id())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) { try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID))); return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
@@ -651,7 +651,7 @@ public class ContentProviderUtils {
@Deprecated @Deprecated
public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) { public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)"; String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())}; String[] selectionArgs = new String[]{Long.toString(trackId.id()), Long.toString(location.getTime())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) { try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID))); return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
@@ -681,10 +681,10 @@ public class ContentProviderUtils {
String[] selectionArgs; String[] selectionArgs;
if (startTrackPointId != null) { if (startTrackPointId != null) {
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + ">=?"; selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId.getId())}; selectionArgs = new String[]{Long.toString(trackId.id()), Long.toString(startTrackPointId.id())};
} else { } else {
selection = TrackPointsColumns.TRACKID + "=?"; selection = TrackPointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId.getId())}; selectionArgs = new String[]{Long.toString(trackId.id())};
} }
return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER); return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER);
@@ -699,7 +699,7 @@ public class ContentProviderUtils {
@Deprecated @Deprecated
public TrackPoint getLastValidTrackPoint(Track.Id trackId) { public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC.type_db + "," + TrackPoint.Type.TRACKPOINT.type_db + "))"; String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC.type_db + "," + TrackPoint.Type.TRACKPOINT.type_db + "))";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())}; String[] selectionArgs = new String[]{Long.toString(trackId.id())};
return findTrackPointBy(selection, selectionArgs); return findTrackPointBy(selection, selectionArgs);
} }
@@ -722,7 +722,7 @@ public class ContentProviderUtils {
*/ */
private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) { private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId.getId()); values.put(TrackPointsColumns.TRACKID, trackId.id());
values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db); values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db);
if (trackPoint.hasLocation()) { if (trackPoint.hasLocation()) {
@@ -805,7 +805,7 @@ public class ContentProviderUtils {
public static String formatIdListForUri(Track.Id... trackIds) { public static String formatIdListForUri(Track.Id... trackIds) {
long[] ids = new long[trackIds.length]; long[] ids = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) { for (int i = 0; i < trackIds.length; i++) {
ids[i] = trackIds[i].getId(); ids[i] = trackIds[i].id();
} }
return formatIdListForUri(ids); return formatIdListForUri(ids);
@@ -834,7 +834,7 @@ public class ContentProviderUtils {
public SensorStatistics getSensorStats(@NonNull Track.Id trackId) { public SensorStatistics getSensorStats(@NonNull Track.Id trackId) {
SensorStatistics sensorStatistics = null; SensorStatistics sensorStatistics = null;
try (Cursor cursor = contentResolver.query(ContentUris.withAppendedId(TracksColumns.CONTENT_URI_SENSOR_STATS, trackId.getId()), null, null, null, null)) { try (Cursor cursor = contentResolver.query(ContentUris.withAppendedId(TracksColumns.CONTENT_URI_SENSOR_STATS, trackId.id()), null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
final int MAX_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_HR); final int MAX_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_HR);
final int AVG_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_HR); final int AVG_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_HR);
@@ -177,7 +177,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex)); Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID())); cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID()));
db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId.getId())}); db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId.id())});
} while (cursor.moveToNext()); } while (cursor.moveToNext());
} }
} }
@@ -455,7 +455,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("starttime_offset", zoneRules.getOffset(Instant.ofEpochMilli(startTime)).getTotalSeconds()); cv.put("starttime_offset", zoneRules.getOffset(Instant.ofEpochMilli(startTime)).getTotalSeconds());
db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId.getId())}); db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId.id())});
} while (cursor.moveToNext()); } while (cursor.moveToNext());
} }
} }
@@ -86,7 +86,7 @@ public class ShareContentProvider extends CustomContentProvider {
StringBuilder trackIdBuilder = new StringBuilder(); StringBuilder trackIdBuilder = new StringBuilder();
for (Track.Id trackId : trackIds) { for (Track.Id trackId : trackIds) {
trackIdBuilder.append(trackId.getId()).append(TRACKID_DELIMITER); trackIdBuilder.append(trackId.id()).append(TRACKID_DELIMITER);
} }
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER)); trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
@@ -199,7 +199,7 @@ public class ShareContentProvider extends CustomContentProvider {
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
Set<Track.Id> trackIds = parseURI(uri); Set<Track.Id> trackIds = parseURI(uri);
final ArrayList<Track> tracks = new ArrayList<>(); final ArrayList<Track> tracks = new ArrayList<>();
String[] trackIdsString = trackIds.stream().map(Track.Id::toString).toArray(String[]::new); String[] trackIdsString = trackIds.stream().map(id -> String.valueOf(id.id())).toArray(String[]::new);
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?"))); String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
try (Cursor cursor = super.query(TracksColumns.CONTENT_URI, null, whereClause, trackIdsString, TracksColumns._ID)) { try (Cursor cursor = super.query(TracksColumns.CONTENT_URI, null, whereClause, trackIdsString, TracksColumns._ID)) {
@@ -166,7 +166,7 @@ public class TrackDataHub {
public void loadTrack(final @NonNull Track.Id trackId) { public void loadTrack(final @NonNull Track.Id trackId) {
handler.post(() -> { handler.post(() -> {
if (trackId.equals(selectedTrackId)) { if (trackId.equals(selectedTrackId)) {
Log.i(TAG, "Not reloading track " + trackId.getId()); Log.i(TAG, "Not reloading track " + trackId.id());
return; return;
} }
selectedTrackId = trackId; selectedTrackId = trackId;
@@ -326,7 +326,7 @@ public class TrackDataHub {
TrackPoint.Id next = null; TrackPoint.Id next = null;
if (localLastSeenTrackPointIdId != null) { if (localLastSeenTrackPointIdId != null) {
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB. next = new TrackPoint.Id(localLastSeenTrackPointIdId.id() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
} }
TrackPoint trackPoint = null; TrackPoint trackPoint = null;
@@ -344,7 +344,7 @@ public class TrackDataHub {
TrackPoint.Id trackPointId = trackPoint.getId(); TrackPoint.Id trackPointId = trackPoint.getId();
// Stop if past the last wanted point // Stop if past the last wanted point
if (maxPointId != null && trackPointId.getId() > maxPointId.getId()) { if (maxPointId != null && trackPointId.id() > maxPointId.id()) {
break; break;
} }
@@ -355,7 +355,7 @@ public class TrackDataHub {
} }
if (samplingFrequency == -1) { if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId.getId() - localFirstSeenTrackPointId.getId()); //TODO That is an assumption; should be derived from the DB. long numTotalPoints = Math.max(0L, lastTrackPointId.id() - localFirstSeenTrackPointId.id()); //TODO That is an assumption; should be derived from the DB.
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints); samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
} }
@@ -67,7 +67,7 @@ public class TrackSelection implements ContentProviderUtils.ContentProviderSelec
} }
// Builds selection arguments. // Builds selection arguments.
ArrayList<String> args = trackIds.stream().map(id -> Long.toString(id.getId())).collect(Collectors.toCollection(ArrayList::new)); ArrayList<String> args = trackIds.stream().map(id -> Long.toString(id.id())).collect(Collectors.toCollection(ArrayList::new));
args.addAll(categories); args.addAll(categories);
args.addAll(fromToArgs); args.addAll(fromToArgs);
selectionArgs = args.stream().toArray(String[]::new); selectionArgs = args.stream().toArray(String[]::new);
@@ -143,53 +143,20 @@ public class Track {
return Objects.hash(id); return Objects.hash(id);
} }
public static class Id implements Parcelable {
private final long id; public record Id(long id) implements Parcelable {
public Id(long id) {
this.id = id;
}
protected Id(Parcel in) {
id = in.readLong();
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Id id1 = (Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
} }
@NonNull
@Override
public String toString() {
return String.valueOf(id);
}
@Override @Override
public void writeToParcel(Parcel parcel, int i) { public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id); parcel.writeLong(id);
} }
public static final Parcelable.Creator<Track.Id> CREATOR = new Parcelable.Creator<>() { public static final Creator<Track.Id> CREATOR = new Creator<>() {
public Track.Id createFromParcel(Parcel in) { public Track.Id createFromParcel(Parcel in) {
return new Track.Id(in.readLong()); return new Track.Id(in.readLong());
} }
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.data.models;
import android.location.Location; import android.location.Location;
import android.os.Build; import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -25,7 +26,6 @@ import androidx.annotation.VisibleForTesting;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Objects;
/** /**
* Sensor and/or location information for a specific point in time. * Sensor and/or location information for a specific point in time.
@@ -462,40 +462,26 @@ public class TrackPoint {
'}'; '}';
} }
public static class Id { public record Id(long id) implements Parcelable {
private final long id; @Override
public int describeContents() {
public Id(long id) { return 0;
this.id = id;
}
protected Id(Parcel in) {
id = in.readLong();
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
} }
@Override @Override
public boolean equals(Object o) { public void writeToParcel(Parcel parcel, int i) {
if (this == o) return true; parcel.writeLong(id);
if (o == null || getClass() != o.getClass()) return false;
TrackPoint.Id id1 = (TrackPoint.Id) o;
return id == id1.id;
} }
@Override public static final Creator<Id> CREATOR = new Creator<>() {
public int hashCode() { public Id createFromParcel(Parcel in) {
return Objects.hash(id); return new Id(in.readLong());
} }
@NonNull public Id[] newArray(int size) {
@Override return new Id[size];
public String toString() { }
return String.valueOf(id); };
}
} }
} }
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks.io.file.exporter;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
@@ -340,7 +341,7 @@ public class ExportActivity extends FragmentActivity implements ExportService.Ex
} }
@Override @Override
public void onExportError(ExportTask exportTask) { public void onExportError(ExportTask exportTask, String errorMessage) {
trackExportErrorCount++; trackExportErrorCount++;
String name; String name;
if (exportTask.isMultiExport()) { if (exportTask.isMultiExport()) {
@@ -348,6 +349,7 @@ public class ExportActivity extends FragmentActivity implements ExportService.Ex
} else { } else {
name = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0)).getName(); name = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0)).getName();
} }
Log.e(TAG, "Error exporting " + name + ": " + errorMessage);
trackErrors.add(name); trackErrors.add(name);
nextExport(exportTask); nextExport(exportTask);
@@ -7,12 +7,12 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService; import androidx.core.app.JobIntentService;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.ExportUtils; import de.dennisguse.opentracks.util.ExportUtils;
public class ExportService extends JobIntentService { public class ExportService extends JobIntentService {
@@ -22,7 +22,6 @@ public class ExportService extends JobIntentService {
private static final String EXTRA_RECEIVER = "extra_receiver"; private static final String EXTRA_RECEIVER = "extra_receiver";
private static final String EXTRA_EXPORT_TASK = "export_task"; private static final String EXTRA_EXPORT_TASK = "export_task";
private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri"; private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri";
private static final String TAG = ExportService.class.getSimpleName();
public static void enqueue(Context context, ExportServiceResultReceiver receiver, ExportTask exportTask, Uri directoryUri) { public static void enqueue(Context context, ExportServiceResultReceiver receiver, ExportTask exportTask, Uri directoryUri) {
Intent intent = new Intent(context, JobService.class); Intent intent = new Intent(context, JobService.class);
@@ -46,17 +45,19 @@ public class ExportService extends JobIntentService {
// Build directory file. // Build directory file.
DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri); DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri);
if (directoryFile == null || !directoryFile.canWrite()) { if (directoryFile == null || !directoryFile.canWrite()) {
Log.e(TAG, "Can't write to directory: " + directoryFile); bundle.putString(ExportServiceResultReceiver.EXTRA_EXPORT_ERROR_MESSAGE, getString(R.string.export_cannot_write_to_dir) + ": " + directoryFile);
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle); resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle);
return; return;
} }
// Export. // Export and send result
boolean success = ExportUtils.exportTrack(this, directoryFile, exportTask); try {
ExportUtils.exportTrack(this, directoryFile, exportTask);
// Send result to the receiver. resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_SUCCESS, bundle);
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR; } catch (Exception e) {
resultReceiver.send(resultCode, bundle); bundle.putString(ExportServiceResultReceiver.EXTRA_EXPORT_ERROR_MESSAGE, e.getMessage());
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle);
}
} }
public static class ExportServiceResultReceiver extends ResultReceiver { public static class ExportServiceResultReceiver extends ResultReceiver {
@@ -66,6 +67,8 @@ public class ExportService extends JobIntentService {
public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task"; public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task";
public static final String EXTRA_EXPORT_ERROR_MESSAGE = "extra_export_error_message";
private final Receiver receiver; private final Receiver receiver;
public ExportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) { public ExportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) {
@@ -75,10 +78,10 @@ public class ExportService extends JobIntentService {
@Override @Override
protected void onReceiveResult(int resultCode, Bundle resultData) { protected void onReceiveResult(int resultCode, Bundle resultData) {
ExportTask exportTask = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK); ExportTask exportTask = resultData.getParcelable(RESULT_EXTRA_EXPORT_TASK);
switch (resultCode) { switch (resultCode) {
case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(exportTask); case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(exportTask);
case RESULT_CODE_ERROR -> receiver.onExportError(exportTask); case RESULT_CODE_ERROR -> receiver.onExportError(exportTask, resultData.getString(EXTRA_EXPORT_ERROR_MESSAGE));
default -> throw new RuntimeException("Unknown resultCode."); default -> throw new RuntimeException("Unknown resultCode.");
} }
} }
@@ -87,7 +90,7 @@ public class ExportService extends JobIntentService {
default void onExportSuccess(ExportTask exportTask) { default void onExportSuccess(ExportTask exportTask) {
} }
default void onExportError(ExportTask exportTask) { default void onExportError(ExportTask exportTask, String errorMessage) {
} }
} }
} }
@@ -279,8 +279,12 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.getMovingTime().getSeconds() + "</gpxtrkx:MovingTime>"); printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.getMovingTime().getSeconds() + "</gpxtrkx:MovingTime>");
printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>"); printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>");
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.getMaxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>"); printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.getMaxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
if (trackStatistics.hasTotalAltitudeGain()) {
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.getTotalAltitudeGain() + "</gpxtrkx:Ascent>"); printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.getTotalAltitudeGain() + "</gpxtrkx:Ascent>");
}
if (trackStatistics.hasTotalAltitudeLoss()) {
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.getTotalAltitudeLoss() + "</gpxtrkx:Descent>"); printWriter.println("<gpxtrkx:Descent>" + trackStatistics.getTotalAltitudeLoss() + "</gpxtrkx:Descent>");
}
printWriter.println("</gpxtrkx:TrackStatsExtension>"); printWriter.println("</gpxtrkx:TrackStatsExtension>");
printWriter.println("</extensions>"); printWriter.println("</extensions>");
@@ -88,7 +88,7 @@ class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChang
trackId = resumeTrackId; trackId = resumeTrackId;
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
if (track == null) { if (track == null) {
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists."); Log.e(TAG, "Ignore resumeTrack. Track " + trackId.id() + " does not exists.");
return false; return false;
} }
@@ -32,6 +32,8 @@ import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Duration; import java.time.Duration;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
@@ -244,6 +246,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
public void newGpsStatus(GpsStatusValue gpsStatusValue) { public void newGpsStatus(GpsStatusValue gpsStatusValue) {
Log.e(TAG, "newGpsStatus: " + gpsStatusValue.message); Log.e(TAG, "newGpsStatus: " + gpsStatusValue.message);
if (notificationManager == null) {
StringWriter writer = new StringWriter();
Exception e = new RuntimeException("TrackRecording.newGpsStatus() called after onDestroy(); objectID: " + this + " with thread: " + Thread.currentThread().toString());
e.printStackTrace(new PrintWriter(writer));
Log.e(TAG, e.getMessage() + " " + writer);
return;
}
notificationManager.updateContent(getString(gpsStatusValue.message)); notificationManager.updateContent(getString(gpsStatusValue.message));
gpsStatusObservable.postValue(gpsStatusValue); gpsStatusObservable.postValue(gpsStatusValue);
} }
@@ -16,8 +16,9 @@ import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
public static final String EXTRAS_CHECK_EXPORT_DIRECTORY = "Check Export Directory"; public static final String EXTRAS_EXPORT_ERROR_MESSAGE = "Export error message";
private boolean checkExportDirectory = false;
private String exportErrorMessage = null;
public static final String FRAGMENT_KEY = "fragmentKey"; public static final String FRAGMENT_KEY = "fragmentKey";
@@ -30,8 +31,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Intent intent = getIntent(); Intent intent = getIntent();
if (intent != null && intent.hasExtra(EXTRAS_CHECK_EXPORT_DIRECTORY)) { if (intent != null) {
checkExportDirectory = true; exportErrorMessage = intent.getStringExtra(EXTRAS_EXPORT_ERROR_MESSAGE);
} }
if (savedInstanceState != null) { if (savedInstanceState != null) {
@@ -48,15 +49,15 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
@Override @Override
protected void onResume() { protected void onResume() {
if (checkExportDirectory) { if (exportErrorMessage != null) {
checkExportDirectory = false;
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setIcon(R.drawable.ic_logo_24dp) .setIcon(R.drawable.ic_logo_24dp)
.setTitle(R.string.app_name) .setTitle(R.string.app_name)
.setMessage(R.string.export_error_post_workout) .setMessage(getString(R.string.export_error_post_workout) + "\n" + exportErrorMessage)
.setNeutralButton(android.R.string.ok, null) .setNeutralButton(android.R.string.ok, null)
.create() .create()
.show(); .show();
exportErrorMessage = null;
} }
super.onResume(); super.onResume();
@@ -50,7 +50,7 @@ public class ShareUtils {
for (Track.Id trackId : trackIds) { for (Track.Id trackId : trackIds) {
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
if (track == null) { if (track == null) {
Log.e(TAG, "TrackId " + trackId.getId() + " could not be resolved."); Log.e(TAG, "TrackId " + trackId.id() + " could not be resolved.");
continue; continue;
} }
@@ -327,7 +327,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
if (searchQuery == null) { if (searchQuery == null) {
if (track != null) { if (track != null) {
return new CursorLoader(MarkerListActivity.this, MarkerColumns.CONTENT_URI, PROJECTION, MarkerColumns.TRACKID + "=?", new String[]{String.valueOf(track.getId().getId())}, null); return new CursorLoader(MarkerListActivity.this, MarkerColumns.CONTENT_URI, PROJECTION, MarkerColumns.TRACKID + "=?", new String[]{String.valueOf(track.getId().id())}, null);
} else { } else {
return new CursorLoader(MarkerListActivity.this, MarkerColumns.CONTENT_URI, PROJECTION, null, null, null); return new CursorLoader(MarkerListActivity.this, MarkerColumns.CONTENT_URI, PROJECTION, null, null, null);
} }
@@ -8,7 +8,6 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
@@ -19,7 +18,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -40,17 +38,12 @@ public class ExportUtils {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(); TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
DocumentFile directory = IntentUtils.toDocumentFile(context, PreferencesUtils.getDefaultExportDirectoryUri()); DocumentFile directory = IntentUtils.toDocumentFile(context, PreferencesUtils.getDefaultExportDirectoryUri());
if (directory == null || !directory.canWrite()) {
Toast.makeText(context, R.string.export_cannot_write_to_dir, Toast.LENGTH_LONG).show();
return;
}
ExportServiceResultReceiver resultReceiver = new ExportServiceResultReceiver(new Handler(), new ExportServiceResultReceiver.Receiver() { ExportServiceResultReceiver resultReceiver = new ExportServiceResultReceiver(new Handler(), new ExportServiceResultReceiver.Receiver() {
@Override @Override
public void onExportError(ExportTask unused) { public void onExportError(ExportTask unused, String errorMessage) {
Intent intent = new Intent(context, SettingsActivity.class); Intent intent = new Intent(context, SettingsActivity.class);
intent.putExtra(SettingsActivity.EXTRAS_CHECK_EXPORT_DIRECTORY, true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(SettingsActivity.EXTRAS_EXPORT_ERROR_MESSAGE, errorMessage);
context.startActivity(intent); context.startActivity(intent);
} }
}); });
@@ -59,7 +52,7 @@ public class ExportUtils {
} }
} }
public static boolean exportTrack(Context context, DocumentFile directory, ExportTask exportTask) { public static void exportTrack(Context context, DocumentFile directory, ExportTask exportTask) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
List<Track> tracks = exportTask.getTrackIds().stream().map(contentProviderUtils::getTrack).collect(Collectors.toList()); List<Track> tracks = exportTask.getTrackIds().stream().map(contentProviderUtils::getTrack).collect(Collectors.toList());
Uri exportDocumentFileUri; Uri exportDocumentFileUri;
@@ -71,27 +64,21 @@ public class ExportUtils {
} }
if (exportDocumentFileUri == null) { if (exportDocumentFileUri == null) {
Log.e(TAG, "Couldn't create document file for export"); throw new RuntimeException("Couldn't create document file for export");
return false;
} }
TrackExporter trackExporter = exportTask.getTrackFileFormat().createTrackExporter(context, contentProviderUtils); TrackExporter trackExporter = exportTask.getTrackFileFormat().createTrackExporter(context, contentProviderUtils);
try (OutputStream outputStream = context.getContentResolver().openOutputStream(exportDocumentFileUri)) { try (OutputStream outputStream = context.getContentResolver().openOutputStream(exportDocumentFileUri)) {
if (trackExporter.writeTrack(tracks, outputStream)) { if (!trackExporter.writeTrack(tracks, outputStream)) {
return true;
} else {
if (!DocumentFile.fromSingleUri(context, exportDocumentFileUri).delete()) { if (!DocumentFile.fromSingleUri(context, exportDocumentFileUri).delete()) {
Log.e(TAG, "Unable to delete exportDocumentFile"); throw new RuntimeException("Unable to delete exportDocumentFile");
} }
Log.e(TAG, "Unable to export track"); throw new RuntimeException("Unable to export track");
return false;
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Log.e(TAG, "Unable to open exportDocumentFile " + exportDocumentFileUri, e); throw new RuntimeException("Unable to open exportDocumentFile " + exportDocumentFileUri, e);
return false;
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Unable to close exportDocumentFile output stream", e); throw new RuntimeException("Unable to close exportDocumentFile output stream", e);
return false;
} }
} }
@@ -59,7 +59,7 @@ public class FileUtils {
} }
public static File getPhotoDir(Context context, Track.Id trackId) { public static File getPhotoDir(Context context, Track.Id trackId) {
File photoDirectory = new File(getPhotoDir(context), "" + trackId.getId()); File photoDirectory = new File(getPhotoDir(context), "" + trackId.id());
photoDirectory.mkdirs(); photoDirectory.mkdirs();
return photoDirectory; return photoDirectory;
} }
@@ -60,11 +60,12 @@ public class PermissionRequester {
private static final List<String> GPS_PERMISSION; private static final List<String> GPS_PERMISSION;
static { static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //TODO ACCESS_BACKGROUND_LOCATION is required for API, but the permission is not properly granted. See #1653.
GPS_PERMISSION = List.of(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION); // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
} else { // GPS_PERMISSION = List.of(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION);
// } else {
GPS_PERMISSION = List.of(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); GPS_PERMISSION = List.of(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);
} // }
} }
private static final List<String> BLUETOOTH_PERMISSIONS; private static final List<String> BLUETOOTH_PERMISSIONS;
@@ -44,7 +44,7 @@ public class TrackNameUtils {
} else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) { } else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
return startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mmX")); return startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mmX"));
} else { } else {
return context.getString(R.string.track_name_format, trackId.getId()); return context.getString(R.string.track_name_format, trackId.id());
} }
} }
} }
+1 -2
View File
@@ -634,11 +634,9 @@ limitations under the License.
<string name="introduction_osm_dashboard">OpenTracks en sí no proporciona un mapa. Por favor, instale OSMDashboard para ver sus grabaciones en un mapa.</string> <string name="introduction_osm_dashboard">OpenTracks en sí no proporciona un mapa. Por favor, instale OSMDashboard para ver sus grabaciones en un mapa.</string>
<string name="share_crash_report">Compartir informe de fallo</string> <string name="share_crash_report">Compartir informe de fallo</string>
<string name="settings_stats_units_imperial_meter">Imperial (millas, metros)</string> <string name="settings_stats_units_imperial_meter">Imperial (millas, metros)</string>
<string name="settings_recording_min_required_accuracy_summary">La ubicación se ignora si la precisión es superior a %1$s</string> <string name="settings_recording_min_required_accuracy_summary">La ubicación se ignora si la precisión es superior a %1$s</string>
<string name="settings_sensor_heart_rate_max">Zonas: frecuencia cardíaca máxima (lpm)</string> <string name="settings_sensor_heart_rate_max">Zonas: frecuencia cardíaca máxima (lpm)</string>
<string name="settings_api_summary">API de control remoto y estadísticas para otras aplicaciones</string> <string name="settings_api_summary">API de control remoto y estadísticas para otras aplicaciones</string>
<string name="no_compatible_camera_installed">No hay ninguna aplicación de cámara compatible instalada</string> <string name="no_compatible_camera_installed">No hay ninguna aplicación de cámara compatible instalada</string>
<string name="no_compatible_gallery_installed">No hay ninguna aplicación de galería de imágenes compatible instalada</string> <string name="no_compatible_gallery_installed">No hay ninguna aplicación de galería de imágenes compatible instalada</string>
<string name="sensor_state_cadence_unit">rpm</string> <string name="sensor_state_cadence_unit">rpm</string>
@@ -647,4 +645,5 @@ limitations under the License.
<string name="settings_sensor_bluetooth_service_filter_title">Filtrar dispositivos Bluetooth</string> <string name="settings_sensor_bluetooth_service_filter_title">Filtrar dispositivos Bluetooth</string>
<string name="settings_sensor_bluetooth_service_filter_off">Mostrar todos los dispositivos Bluetooth detectados</string> <string name="settings_sensor_bluetooth_service_filter_off">Mostrar todos los dispositivos Bluetooth detectados</string>
<string name="voice_on_device_speaker_title">Utiliza el altavoz del dispositivo</string> <string name="voice_on_device_speaker_title">Utiliza el altavoz del dispositivo</string>
<string name="menu_export_all_one_file">Exportar todo (un archivo para todas las pistas)</string>
</resources> </resources>
+1
View File
@@ -592,4 +592,5 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="sensor_state_cadence_unit">rpm</string> <string name="sensor_state_cadence_unit">rpm</string>
<string name="sensor_state_heart_rate_unit">lpm</string> <string name="sensor_state_heart_rate_unit">lpm</string>
<string name="sensor_state_power_unit">W</string> <string name="sensor_state_power_unit">W</string>
<string name="show_on_dashboard">Taboleiro da API OpenTracks</string>
</resources> </resources>
+2 -1
View File
@@ -542,7 +542,7 @@ limitations under the License.
<string name="show_on_map_format_ask">Spør</string> <string name="show_on_map_format_ask">Spør</string>
<string name="show_on_dashboard">OpenTracks-API for oversikten</string> <string name="show_on_dashboard">OpenTracks-API for oversikten</string>
<string name="always">Alltid</string> <string name="always">Alltid</string>
<string name="settings_announcements_average_heart_rate">Gjen. puls</string> <string name="settings_announcements_average_heart_rate">Gjennomsnittlig puls</string>
<string name="settings_announcements_lap_heart_rate">Runde-puls</string> <string name="settings_announcements_lap_heart_rate">Runde-puls</string>
<string name="settings_announcements_average_speed_pace">Gjen. hastighet/tempo</string> <string name="settings_announcements_average_speed_pace">Gjen. hastighet/tempo</string>
<string name="image_discard">Forkast</string> <string name="image_discard">Forkast</string>
@@ -580,4 +580,5 @@ limitations under the License.
<string name="voiceDistanceNauticalMilesPlural">{n, plural, =1 {1 nautisk mil} other {{n,number,#.0} nautiske mil} }</string> <string name="voiceDistanceNauticalMilesPlural">{n, plural, =1 {1 nautisk mil} other {{n,number,#.0} nautiske mil} }</string>
<string name="sensor_state_cadence_unit">o/min</string> <string name="sensor_state_cadence_unit">o/min</string>
<string name="sensor_state_heart_rate_unit">slag/min</string> <string name="sensor_state_heart_rate_unit">slag/min</string>
<string name="average_heart_rate">Gjennomsnittlig puls</string>
</resources> </resources>
+66 -7
View File
@@ -152,7 +152,7 @@ limitations under the License.
<string name="menu_aggregated_statistics">Сукупна статистика</string> <string name="menu_aggregated_statistics">Сукупна статистика</string>
<string name="menu_delete">Видалити</string> <string name="menu_delete">Видалити</string>
<string name="menu_edit">Редагувати</string> <string name="menu_edit">Редагувати</string>
<string name="menu_export_all">Експортувати все</string> <string name="menu_export_all">Експортувати все (один файл на трек)</string>
<string name="menu_help">Довідка</string> <string name="menu_help">Довідка</string>
<string name="menu_import_all">Імпортувати все</string> <string name="menu_import_all">Імпортувати все</string>
<string name="menu_insert_marker">Вставити маркер</string> <string name="menu_insert_marker">Вставити маркер</string>
@@ -352,9 +352,9 @@ limitations under the License.
<string name="activity_type_escooter">електро-байк</string> <string name="activity_type_escooter">електро-байк</string>
<string name="settings_sensor_wheel_circumference">Окружність колеса (мм)</string> <string name="settings_sensor_wheel_circumference">Окружність колеса (мм)</string>
<string name="description_altitude_loss">Зменшення висоти: %1$d м (%2$d фут.)</string> <string name="description_altitude_loss">Зменшення висоти: %1$d м (%2$d фут.)</string>
<string name="export_progress_summary_new_msg">Нові треки експортовано</string> <string name="export_progress_summary_new_msg">Нові файли експортовано</string>
<string name="export_progress_summary_overwrite_msg">Треки перезаписано</string> <string name="export_progress_summary_overwrite_msg">Файли перезаписано</string>
<string name="export_progress_summary_skip_msg">Треки пропущено</string> <string name="export_progress_summary_skip_msg">Пропущені файли</string>
<string name="export_progress_summary_errors_msg">Помилки</string> <string name="export_progress_summary_errors_msg">Помилки</string>
<string name="generic_open_track">Відкрити Трек</string> <string name="generic_open_track">Відкрити Трек</string>
<string name="import_file_imported">Файл %1$s імпортовано</string> <string name="import_file_imported">Файл %1$s імпортовано</string>
@@ -364,9 +364,9 @@ limitations under the License.
<string name="import_progress_summary_errors_msg">Помилки</string> <string name="import_progress_summary_errors_msg">Помилки</string>
<string name="export_with_photos">з фото</string> <string name="export_with_photos">з фото</string>
<string name="export_without_photos">без</string> <string name="export_without_photos">без</string>
<string name="export_track_already_exists_msg">Трек %1$s вже існує в цільовій директорії.</string> <string name="export_track_already_exists_msg">Файл %1$s вже існує в цільовій директорії.</string>
<string name="export_all_do_it_for_all">робити це для усіх конфліктів</string> <string name="export_all_do_it_for_all">робити це для усіх конфліктів</string>
<string name="export_track_errors">Треки не експортовано</string> <string name="export_track_errors">Файли не експортовано</string>
<string name="generic_overwrite">Перезаписати</string> <string name="generic_overwrite">Перезаписати</string>
<string name="generic_skip">Пропустити</string> <string name="generic_skip">Пропустити</string>
<string name="generic_show_errors">Показати помилки</string> <string name="generic_show_errors">Показати помилки</string>
@@ -482,7 +482,7 @@ limitations under the License.
<string name="settings_import_section">Імпорт</string> <string name="settings_import_section">Імпорт</string>
<string name="stats_sensors_power">Сила</string> <string name="stats_sensors_power">Сила</string>
<string name="stats_sensors_cadence">Каденція</string> <string name="stats_sensors_cadence">Каденція</string>
<string name="stats_sensors_heart_rate">частота серцебиття</string> <string name="stats_sensors_heart_rate">Частота серцебиття</string>
<string name="settings_defaults_summary">Переважні одиниці виміру, активність за замовчуванням, Ім\'я треку за замовчуванням</string> <string name="settings_defaults_summary">Переважні одиниці виміру, активність за замовчуванням, Ім\'я треку за замовчуванням</string>
<string name="settings_open_tracks_summary">Все про проект OpenTracks</string> <string name="settings_open_tracks_summary">Все про проект OpenTracks</string>
<string name="settings_defaults_title">Одиниці виміру і дії за замовчуванням</string> <string name="settings_defaults_title">Одиниці виміру і дії за замовчуванням</string>
@@ -518,4 +518,63 @@ limitations under the License.
<string name="activity_type_workout">тренування</string> <string name="activity_type_workout">тренування</string>
<string name="activity_type_swimming_open">плавання у відкритих водоймах</string> <string name="activity_type_swimming_open">плавання у відкритих водоймах</string>
<string name="activity_type_swimming">плавання</string> <string name="activity_type_swimming">плавання</string>
<string name="generic_dismiss">Відхилити</string>
<string name="settings_export_filename_title">Формат імені файлу</string>
<string name="description_pace_nautical">Темп (хв/мм)</string>
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="description_speed_nautical">Швидкість (вузли)</string>
<string name="image_discard">Скасувати</string>
<string name="sensor_state_cadence_unit">об/хв</string>
<string name="sensor_state_heart_rate_unit">уд/хв</string>
<string name="sensor_state_power_unit">Вт</string>
<string name="settings_recording_min_required_accuracy_summary">Місцезнаходження ігнорується, якщо його точність перевищує %1$s</string>
<string name="settings_announcements_average_heart_rate">Середній пульс</string>
<string name="settings_announcements_lap_heart_rate">Пульс за коло</string>
<string name="settings_announcements_average_speed_pace">Середня швидкість/темп</string>
<string name="gps_recording_without_signal">OpenTracks записує без даних GPS.</string>
<string name="settings_announcements_lap_speed_pace">Швидкість/темп за коло</string>
<string name="settings_sensor_bluetooth_service_filter_title">Фільтрувати пристрої Bluetooth</string>
<string name="settings_sensor_bluetooth_service_filter_off">Показати всі видимі пристрої Bluetooth</string>
<string name="menu_export_all_one_file">Експортувати все (один файл для всіх треків)</string>
<string name="settings_stats_units_imperial_meter">Британська (милі, метри)</string>
<string name="settings_api_title">Публічний API</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Записані дані не будуть автоматично передаватись іншим програмам.</string>
<string name="settings_public_api_package_title">Назва пакета для публічного API</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Програма, яка почала запис, також може отримати доступ до записаних даних.</string>
<string name="unit_nautical_mile">Морські милі</string>
<string name="settings_public_api_enabled_summary_on">Інші встановлені програми можуть запускати або зупиняти записи.</string>
<string name="settings_public_api_enabled_summary_off">Тільки OpenTracks може запускати та зупиняти записи.</string>
<string name="track_delete_number_of_tracks">%1$d треків</string>
<string name="settings_public_api_dashboard_enabled_title">Автоматична передача даних</string>
<string name="settings_public_api_enabled_title">Публічний API</string>
<string name="settings_public_api_disabled_toast">Публічний API OpenTracks вимкнено: його можна ввімкнути в налаштуваннях.</string>
<string name="track_discarding">Скасування Доріжки</string>
<string name="unit_knots">вузли</string>
<string name="settings_sensor_heart_rate_max">Зони: максимальна частота серцевих скорочень (уд/хв)</string>
<string name="settings_stats_units_nautical">Морська (морські милі, фут)</string>
<string name="settings_api_summary">API дистанційного керування та статистики для інших програм</string>
<string name="voice_per_kilometer">за кілометр</string>
<string name="pace">Темп</string>
<string name="lap_speed">Швидкість за коло</string>
<string name="speed">Середня швидкість руху</string>
<string name="average_heart_rate">Середній пульс</string>
<string name="voice_per_mile">за милю</string>
<string name="voice_on_device_speaker_title">Використовувати динамік пристрою</string>
<string name="lap_time">Час за коло</string>
<string name="unit_minute_per_nautical_mile">хв/морська миля</string>
<string name="no_compatible_camera_installed">Не встановлено сумісну програму камери</string>
<string name="no_compatible_gallery_installed">Не встановлено сумісну програму галереї зображень</string>
<string name="report_bug">Повідомити про помилку</string>
<string name="select_show_on_map_behavior">Виберіть поведінку \"показати на карті\"</string>
<string name="show_on_map_format_ask">Запитувати</string>
<string name="voice_per_nautical_mile">за морську милю</string>
<string name="always">Завжди</string>
<string name="value_integer_knots">%1$d вузлів</string>
<string name="value_float_knots">%1$.1f вузлів</string>
<string name="total_distance">Загальна відстань</string>
<string name="current_heart_rate">Поточний пульс</string>
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
<string name="introduction_osm_dashboard">Сам OpenTracks не надає карти. Установіть OSMDashboard, щоб переглядати свої записи на карті.</string>
<string name="show_on_dashboard_not_installed">Не встановлено програму, що підтримує OpenTracks Dashboard API.</string>
<string name="value_float_knots_recommended">%1$.1f вузлів (рекомендовано)</string>
</resources> </resources>
+1
View File
@@ -623,4 +623,5 @@ limitations under the License.
<string name="sensor_state_heart_rate_unit">bpm</string> <string name="sensor_state_heart_rate_unit">bpm</string>
<string name="sensor_state_power_unit">瓦</string> <string name="sensor_state_power_unit">瓦</string>
<string name="settings_sensor_bluetooth_service_filter_off">显示所有可发现的蓝牙设备</string> <string name="settings_sensor_bluetooth_service_filter_off">显示所有可发现的蓝牙设备</string>
<string name="help_data_google_play_services_content">否</string>
</resources> </resources>