forked from upstream-mirrors/OpenTracks
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53f9432d45 | |||
| b4ccdcf4a2 | |||
| 681b987927 | |||
| ed163b2083 | |||
| 77ccbe7004 | |||
| 247a5f342a | |||
| 7b9b1d3c95 | |||
| 44fe4f8818 | |||
| 1f55729b71 | |||
| 8592375edc | |||
| 2c1ffd5fa1 | |||
| 2672602a71 | |||
| 9e0553d5d6 | |||
| 5b7bc4ea24 | |||
| f4a477b99d | |||
| c9a2b31866 | |||
| 4412a6a698 | |||
| 92da9bd598 | |||
| 3df45c54d6 | |||
| c9f7ef810e | |||
| ebd4030869 | |||
| 074484393e | |||
| 08a231c64f | |||
| 07cbda5faa |
@@ -27,7 +27,7 @@ jobs:
|
|||||||
sudo udevadm trigger --name-match=kvm
|
sudo udevadm trigger --name-match=kvm
|
||||||
|
|
||||||
- name: run tests
|
- name: run tests
|
||||||
uses: reactivecircus/android-emulator-runner@v2.33.0
|
uses: reactivecircus/android-emulator-runner@v2.34.0
|
||||||
with:
|
with:
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
api-level: 34
|
api-level: 34
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ The Public API is disabled by default to protect the user's privacy, but it can
|
|||||||
| [FitTrackee](https://github.com/SamR1/FitTrackee) | yes | n/a | n/a |
|
| [FitTrackee](https://github.com/SamR1/FitTrackee) | yes | n/a | n/a |
|
||||||
| [SportsTracker](https://github.com/ssaring/sportstracker) | yes, [single tracks only](https://github.com/ssaring/sportstracker/issues/260) | no | no |
|
| [SportsTracker](https://github.com/ssaring/sportstracker) | yes, [single tracks only](https://github.com/ssaring/sportstracker/issues/260) | no | no |
|
||||||
| [ExifTool](https://exiftool.org) | [yes](https://exiftool.org/forum/index.php?topic=15972.0) | no | no |
|
| [ExifTool](https://exiftool.org) | [yes](https://exiftool.org/forum/index.php?topic=15972.0) | no | no |
|
||||||
|
| [Wanderer](https://wanderer.to/) | yes | yes | yes |
|
||||||
|
|
||||||
|
|
||||||
## Dashboard API (incl. map)
|
## Dashboard API (incl. map)
|
||||||
@@ -153,6 +154,19 @@ slow and does not provide updates while recording.
|
|||||||
|
|
||||||
The developer documentation is in [README_API.md](README_API.md).
|
The developer documentation is in [README_API.md](README_API.md).
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
|
OpenTracks stores the data in the app-internal space:
|
||||||
|
`/data/data/de.dennisguse.opentracks[|.playstore]`
|
||||||
|
|
||||||
|
There is a SQLite database `database.db` that contains the tracks as well as the markers.
|
||||||
|
Pictures attached to markers are stored as separate files.
|
||||||
|
|
||||||
|
For backup and recovery, the best approach is to export as one KMZ file.
|
||||||
|
This will contain all the tracks, pictures and the data as similar as possible as stored internally.
|
||||||
|
In difference to backing up the database directly, a KMZ allows to be imported into a different (
|
||||||
|
e.g., newer) version of OpenTracks.
|
||||||
|
|
||||||
## Project history
|
## Project history
|
||||||
|
|
||||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||||
|
|||||||
+8
-8
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.9.0'
|
classpath 'com.android.tools.build:gradle:8.9.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_17
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
compileSdk = 35
|
compileSdk = 36
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
@@ -46,13 +46,13 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'de.dennisguse.opentracks'
|
applicationId 'de.dennisguse.opentracks'
|
||||||
versionCode 6095
|
versionCode 6102
|
||||||
versionName "v4.18.0"
|
versionName "v4.19.0"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 36
|
||||||
|
|
||||||
testInstrumentationRunner 'de.dennisguse.opentracks.TestRunner'
|
testInstrumentationRunner 'de.dennisguse.opentracks.TestRunner'
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
@@ -145,10 +145,10 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||||
implementation 'androidx.core:core:1.15.0'
|
implementation 'androidx.core:core:1.16.0'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
implementation 'androidx.mediarouter:mediarouter:1.7.0'
|
implementation 'androidx.mediarouter:mediarouter:1.7.0'
|
||||||
implementation 'androidx.core:core-location-altitude:1.0.0-alpha03'
|
implementation 'androidx.core:core-location-altitude:1.0.0-alpha03'
|
||||||
@@ -158,7 +158,7 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test:rules:1.6.1'
|
androidTestImplementation 'androidx.test:rules:1.6.1'
|
||||||
androidTestImplementation 'androidx.test:runner:1.6.2'
|
androidTestImplementation 'androidx.test:runner:1.6.2'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
androidTestImplementation 'org.mockito:mockito-android:5.16.0'
|
androidTestImplementation 'org.mockito:mockito-android:5.17.0'
|
||||||
|
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.5.1'
|
androidTestUtil 'androidx.test:orchestrator:1.5.1'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v4.18.0: OpenTracks
|
||||||
|
|
||||||
|
Změny:
|
||||||
|
- Varování o zakázané poloze lze vypnout
|
||||||
|
|
||||||
|
Oprava chyby:
|
||||||
|
- BottomAppBar překrýval obsah
|
||||||
|
|
||||||
|
Vývojář:
|
||||||
|
- Použití zaznamenané pozice namísto polohy systému Android
|
||||||
|
- Předělány agregátory senzorů
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v4.19.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- GPX export was not compliant (TrackPointExtension order and content)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- pre-v4.18.0 markers without pictures could not be deleted
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v4.18.0: OpenTracks
|
||||||
|
|
||||||
|
Athruithe:
|
||||||
|
- Is féidir rabhadh faoi mhíchumas GPS a dhíchumasú
|
||||||
|
|
||||||
|
Ceartúchán Bug:
|
||||||
|
- Ábhar forluiteach BottomAppBar
|
||||||
|
|
||||||
|
Forbróir:
|
||||||
|
- Úsáid taifead Seasamh in ionad Android suíomh
|
||||||
|
- Comhbhailitheoirí braite athfhachtóirithe
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v4.18.0: OpenTracks
|
||||||
|
|
||||||
|
Wijzigingen:
|
||||||
|
- GPS uitgeschakeld waarschuwing kan worden uitgeschakeld
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- BottomAppBar overlapte inhoud
|
||||||
|
|
||||||
|
Ontwikkelaar:
|
||||||
|
- Gebruik recordpositie in plaats van Android-locatie
|
||||||
|
- Gerefactoreerde sensor-aggregators
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v4.18.0: OpenTracks
|
||||||
|
|
||||||
|
更改:
|
||||||
|
- GPS 禁用警告可被禁用
|
||||||
|
|
||||||
|
错误修复:
|
||||||
|
- BottomAppBar 重叠内容
|
||||||
|
|
||||||
|
开发相关:
|
||||||
|
- 使用记录位置代替 Android 位置
|
||||||
|
- 重构了传感器聚合器
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package de.dennisguse.opentracks.io.file.exporter;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class FITv2TrackExporterTest {
|
||||||
|
|
||||||
|
private final byte FIT_PROTOCOL_VERSION = 2 << 4 + 0;
|
||||||
|
private final short FIT_PROFILE_VERSION = 21 * 1000 + 158;
|
||||||
|
|
||||||
|
private long checksum;
|
||||||
|
private final byte FILE_HEADER_SIZE = 14;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void run() {
|
||||||
|
ByteBuffer littleEndian = ByteBuffer
|
||||||
|
.allocate(FILE_HEADER_SIZE)
|
||||||
|
.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
littleEndian.put(0, FILE_HEADER_SIZE);
|
||||||
|
littleEndian.put(1, FIT_PROTOCOL_VERSION);
|
||||||
|
|
||||||
|
// Profile version: 1513
|
||||||
|
littleEndian.putShort(2, FIT_PROFILE_VERSION);
|
||||||
|
|
||||||
|
// Data size: 32bit
|
||||||
|
littleEndian.putInt(4, 0); // We do not calculate the data size.
|
||||||
|
|
||||||
|
// .FIT
|
||||||
|
littleEndian.putChar(8, '.');
|
||||||
|
littleEndian.putChar(9, 'F');
|
||||||
|
littleEndian.putChar(10, 'I');
|
||||||
|
littleEndian.putChar(11, 'T');
|
||||||
|
|
||||||
|
// CRC (not implemented)
|
||||||
|
littleEndian.put(12, (byte) 0);
|
||||||
|
littleEndian.put(13, (byte) 0);
|
||||||
|
|
||||||
|
var a = littleEndian.array();
|
||||||
|
littleEndian.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -312,7 +312,7 @@ public class ExportImportTest {
|
|||||||
contentProviderUtils.deleteTrack(context, trackId);
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
KmzTrackImporter importer = new KmzTrackImporter(context, trackImporter);
|
KMZTrackImporter importer = new KMZTrackImporter(context, trackImporter);
|
||||||
importTrackId = importer.importFile(tmpFileUri).get(0);
|
importTrackId = importer.importFile(tmpFileUri).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -358,7 +358,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
|
||||||
importTrackId = importer.importFile(inputStream).get(0);
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -383,7 +383,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
importTrackId = importer.importFile(inputStream).get(0);
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -503,7 +503,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
importTrackId = importer.importFile(inputStream).get(0);
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
|||||||
+79
-4
@@ -68,7 +68,7 @@ public class GPXTrackImporterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void gpx_with_pause_resume() throws IOException {
|
public void gpx_with_pause_resume() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_gpx_pause_resume);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_gpx_pause_resume);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -132,7 +132,7 @@ public class GPXTrackImporterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void gpx_without_speed() throws IOException {
|
public void gpx_without_speed() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_without_speed);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_without_speed);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -187,7 +187,7 @@ public class GPXTrackImporterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void gpx_speed_no_namespace() throws IOException {
|
public void gpx_speed_no_namespace() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_with_speed_no_namespace);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_with_speed_no_namespace);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -238,11 +238,86 @@ public class GPXTrackImporterTest {
|
|||||||
), importedTrackPoints);
|
), importedTrackPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* until v4.18.0: some extensions where incorrectly added to gpxtpx:TrackPointExtension
|
||||||
|
* We only need to check the trackpoints.
|
||||||
|
*/
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void gpx_legacy_trackpointextension() throws IOException {
|
||||||
|
// given
|
||||||
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_gpx_trackpointextensions_incorrect);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. import
|
||||||
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
|
// then: We only need to check the trackpoints.
|
||||||
|
|
||||||
|
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||||
|
|
||||||
|
TrackPointAssert a = new TrackPointAssert()
|
||||||
|
.setDelta(0.05); // speed is not fully
|
||||||
|
a.assertEquals(List.of(
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2020-02-02T02:02:03Z"),
|
||||||
|
3d, 14d, Distance.of(10),
|
||||||
|
Altitude.WGS84.of(10), null,
|
||||||
|
null,
|
||||||
|
Speed.of(15)))
|
||||||
|
.setAltitudeLoss(1f)
|
||||||
|
.setAltitudeGain(1f),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2020-02-02T02:02:17Z"),
|
||||||
|
3d, 14.001, Distance.of(10),
|
||||||
|
Altitude.WGS84.of(10), null,
|
||||||
|
null,
|
||||||
|
Speed.of(5)))
|
||||||
|
.setAltitudeLoss(1f)
|
||||||
|
.setAltitudeGain(1f)
|
||||||
|
.setSensorDistance(Distance.of(12))
|
||||||
|
.setHeartRate(69)
|
||||||
|
.setPower(50f)
|
||||||
|
.setCadence(3f),
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2020-02-02T02:03:21Z"),
|
||||||
|
3d, 14.002, Distance.of(10),
|
||||||
|
Altitude.WGS84.of(10), null,
|
||||||
|
null,
|
||||||
|
Speed.of(15)))
|
||||||
|
.setAltitudeLoss(0f)
|
||||||
|
.setAltitudeGain(0f),
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2020-02-02T02:03:22Z"),
|
||||||
|
3d, 16d, Distance.of(10),
|
||||||
|
Altitude.WGS84.of(10), null,
|
||||||
|
null,
|
||||||
|
Speed.of(15)))
|
||||||
|
.setAltitudeLoss(0f)
|
||||||
|
.setAltitudeGain(0f),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2020-02-02T02:03:50Z"),
|
||||||
|
3d, 16.001, Distance.of(10),
|
||||||
|
Altitude.WGS84.of(10), null,
|
||||||
|
null,
|
||||||
|
Speed.of(10)))
|
||||||
|
.setAltitudeLoss(0f)
|
||||||
|
.setAltitudeGain(0f)
|
||||||
|
.setSpeed(Speed.of(15))
|
||||||
|
), actual);
|
||||||
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void importExportTest_timezone() throws IOException {
|
public void importExportTest_timezone() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = 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.gpx_timezone);
|
||||||
InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
|
InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -62,7 +62,7 @@ public class KMLTrackImporterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void kml22_order_location_and_when() throws IOException {
|
public void kml22_order_location_and_when() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_order_location_and_when);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_order_location_and_when);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -118,7 +118,7 @@ public class KMLTrackImporterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void kml22_time_decreases() throws IOException {
|
public void kml22_time_decreases() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_time_decreases);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_time_decreases);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -159,7 +159,7 @@ public class KMLTrackImporterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void kml22_with_statistics_marker() throws IOException {
|
public void kml22_with_statistics_marker() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_kml22_statistics_marker);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_kml22_statistics_marker);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -243,7 +243,7 @@ public class KMLTrackImporterTest {
|
|||||||
@Test(expected = ImportParserException.class)
|
@Test(expected = ImportParserException.class)
|
||||||
public void kml_without_locations() throws IOException {
|
public void kml_without_locations() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_without_locations);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_without_locations);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -254,7 +254,7 @@ public class KMLTrackImporterTest {
|
|||||||
@Test(expected = ImportParserException.class)
|
@Test(expected = ImportParserException.class)
|
||||||
public void kml_when_locations_different() throws IOException {
|
public void kml_when_locations_different() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_when_locations_different);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_when_locations_different);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
|
|||||||
xmlns:gpxtrkx="http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
|
xmlns:gpxtrkx="http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
|
||||||
xmlns:cluetrust="http://www.cluetrust.com/Schemas/"
|
xmlns:cluetrust="http://www.cluetrust.com/Schemas/"
|
||||||
xmlns:pwr="http://www.garmin.com/xmlschemas/PowerExtension/v1"
|
xmlns:pwr="http://www.garmin.com/xmlschemas/PowerExtension/v1"
|
||||||
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 http://www.cluetrust.com/Schemas http://www.cluetrust.com/Schemas/gpxdata10.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
|
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 https://www8.garmin.com/xmlschemas/TrackStatsExtension.xsd http://www.cluetrust.com/Schemas https://www.cluetrust.com/Schemas/gpxdata10.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
|
||||||
<wpt lat="3" lon="14">
|
<wpt lat="3" lon="14">
|
||||||
<ele>10</ele>
|
<ele>10</ele>
|
||||||
<time>2021-01-07T21:51:59.179+01:15</time>
|
<time>2021-01-07T21:51:59.179+01:15</time>
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gpx
|
||||||
|
version="1.1"
|
||||||
|
creator="OpenTracks (Debug)"
|
||||||
|
xmlns="http://www.topografix.com/GPX/1/1"
|
||||||
|
xmlns:topografix="http://www.topografix.com/GPX/Private/TopoGrafix/0/1"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1"
|
||||||
|
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
|
||||||
|
xmlns:gpxtrkx="http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
|
||||||
|
xmlns:cluetrust="http://www.cluetrust.com/Schemas/"
|
||||||
|
xmlns:pwr="http://www.garmin.com/xmlschemas/PowerExtension/v1"
|
||||||
|
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 http://www.cluetrust.com/Schemas https://www.cluetrust.com/Schemas/gpxdata10.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
|
||||||
|
<wpt lat="3" lon="14">
|
||||||
|
<ele>10</ele>
|
||||||
|
<time>2020-02-02T03:02:03+01:00</time>
|
||||||
|
<name><![CDATA[Marker 1]]></name>
|
||||||
|
<desc><![CDATA[Marker 1 desc]]></desc>
|
||||||
|
<type><![CDATA[Marker 1 category]]></type>
|
||||||
|
</wpt>
|
||||||
|
<wpt lat="3" lon="14.001">
|
||||||
|
<ele>10</ele>
|
||||||
|
<time>2020-02-02T03:02:17+01:00</time>
|
||||||
|
<name><![CDATA[Marker 2]]></name>
|
||||||
|
<desc><![CDATA[Marker 2 desc]]></desc>
|
||||||
|
<type><![CDATA[Marker 2 category]]></type>
|
||||||
|
</wpt>
|
||||||
|
<trk>
|
||||||
|
<name><![CDATA[2020-02-02T03:02+01]]></name>
|
||||||
|
<desc><![CDATA[the description]]></desc>
|
||||||
|
<type><![CDATA[mountain biking]]></type>
|
||||||
|
<extensions>
|
||||||
|
<topografix:color>c0c0c0</topografix:color>
|
||||||
|
<opentracks:trackid>75c581d0-eef5-4aa2-91a3-66d2c6de4f6d</opentracks:trackid>
|
||||||
|
<opentracks:typeTranslated><![CDATA[the activity type]]></opentracks:typeTranslated>
|
||||||
|
<gpxtrkx:TrackStatsExtension>
|
||||||
|
<gpxtrkx:Distance>222125.53125</gpxtrkx:Distance>
|
||||||
|
<gpxtrkx:TimerTime>56</gpxtrkx:TimerTime>
|
||||||
|
<gpxtrkx:MovingTime>26</gpxtrkx:MovingTime>
|
||||||
|
<gpxtrkx:StoppedTime>30</gpxtrkx:StoppedTime>
|
||||||
|
<gpxtrkx:MaxSpeed>8543.2900390625</gpxtrkx:MaxSpeed>
|
||||||
|
<gpxtrkx:Ascent>2.0</gpxtrkx:Ascent>
|
||||||
|
<gpxtrkx:Descent>2.0</gpxtrkx:Descent>
|
||||||
|
</gpxtrkx:TrackStatsExtension>
|
||||||
|
</extensions>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="3" lon="14">
|
||||||
|
<ele>10</ele>
|
||||||
|
<time>2020-02-02T03:02:03+01:00</time>
|
||||||
|
<extensions><gpxtpx:TrackPointExtension>
|
||||||
|
<gpxtpx:speed>15</gpxtpx:speed>
|
||||||
|
<opentracks:gain>1</opentracks:gain>
|
||||||
|
<opentracks:loss>1</opentracks:loss>
|
||||||
|
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="3" lon="14.001">
|
||||||
|
<ele>10</ele>
|
||||||
|
<time>2020-02-02T03:02:17+01:00</time>
|
||||||
|
<extensions><gpxtpx:TrackPointExtension>
|
||||||
|
<gpxtpx:speed>5</gpxtpx:speed>
|
||||||
|
<gpxtpx:hr>69</gpxtpx:hr>
|
||||||
|
<gpxtpx:cad>3</gpxtpx:cad>
|
||||||
|
<pwr:PowerInWatts>50</pwr:PowerInWatts>
|
||||||
|
<opentracks:gain>1</opentracks:gain>
|
||||||
|
<opentracks:loss>1</opentracks:loss>
|
||||||
|
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal><opentracks:distance>12</opentracks:distance>
|
||||||
|
<cluetrust:distance>12</cluetrust:distance>
|
||||||
|
</gpxtpx:TrackPointExtension></extensions>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="3" lon="14.002">
|
||||||
|
<ele>10</ele>
|
||||||
|
<time>2020-02-02T03:03:21+01:00</time>
|
||||||
|
<extensions><gpxtpx:TrackPointExtension>
|
||||||
|
<gpxtpx:speed>15</gpxtpx:speed>
|
||||||
|
<opentracks:gain>0</opentracks:gain>
|
||||||
|
<opentracks:loss>0</opentracks:loss>
|
||||||
|
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="3" lon="16">
|
||||||
|
<ele>10</ele>
|
||||||
|
<time>2020-02-02T03:03:22+01:00</time>
|
||||||
|
<extensions><gpxtpx:TrackPointExtension>
|
||||||
|
<gpxtpx:speed>15</gpxtpx:speed>
|
||||||
|
<opentracks:gain>0</opentracks:gain>
|
||||||
|
<opentracks:loss>0</opentracks:loss>
|
||||||
|
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="3" lon="16.001">
|
||||||
|
<ele>10</ele>
|
||||||
|
<time>2020-02-02T03:03:50+01:00</time>
|
||||||
|
<extensions><gpxtpx:TrackPointExtension>
|
||||||
|
<gpxtpx:speed>15</gpxtpx:speed>
|
||||||
|
<opentracks:gain>0</opentracks:gain>
|
||||||
|
<opentracks:loss>0</opentracks:loss>
|
||||||
|
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
@@ -434,7 +434,14 @@ public class ContentProviderUtils {
|
|||||||
marker.setIcon(cursor.getString(iconIndex));
|
marker.setIcon(cursor.getString(iconIndex));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(photoUrlIndex)) {
|
if (!cursor.isNull(photoUrlIndex)) {
|
||||||
marker.setPhotoUrl(Uri.parse(cursor.getString(photoUrlIndex)));
|
String photoUrl = cursor.getString(photoUrlIndex);
|
||||||
|
if (photoUrl.isEmpty()) {
|
||||||
|
// Before v4.18.0: a marker without a picture as URL ""
|
||||||
|
// TODO Data should be migrated.
|
||||||
|
marker.setPhotoUrl(null);
|
||||||
|
} else {
|
||||||
|
marker.setPhotoUrl(Uri.parse(cursor.getString(photoUrlIndex)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
|
|||||||
import de.dennisguse.opentracks.io.file.exporter.CSVTrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.CSVTrackExporter;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.KMZTrackExporter;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +33,7 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
return MIME_KML;
|
return "application/vnd.google-earth.kml+xml";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
@@ -49,12 +49,12 @@ public enum TrackFileFormat {
|
|||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||||
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||||
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
return new KMZTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
return MIME_KMZ;
|
return "application/vnd.google-earth.kmz";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
@@ -74,12 +74,12 @@ public enum TrackFileFormat {
|
|||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||||
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||||
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
return new KMZTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
return MIME_KMZ;
|
return KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getMimeType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
@@ -126,17 +126,13 @@ public enum TrackFileFormat {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String MIME_KMZ = "application/vnd.google-earth.kmz";
|
|
||||||
|
|
||||||
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
|
||||||
|
|
||||||
private final String preferenceId;
|
private final String preferenceId;
|
||||||
|
|
||||||
TrackFileFormat(String preferenceId) {
|
TrackFileFormat(String preferenceId) {
|
||||||
this.preferenceId = preferenceId;
|
this.preferenceId = preferenceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, String> toPreferenceIdLabelMap(final Resources resources, final TrackFileFormat ... trackFileFormats) {
|
public static Map<String, String> toPreferenceIdLabelMap(final Resources resources, final TrackFileFormat... trackFileFormats) {
|
||||||
Map<String, String> preferenceIdLabelMap = new LinkedHashMap<>(trackFileFormats.length);
|
Map<String, String> preferenceIdLabelMap = new LinkedHashMap<>(trackFileFormats.length);
|
||||||
for (TrackFileFormat trackFileFormat : trackFileFormats) {
|
for (TrackFileFormat trackFileFormat : trackFileFormats) {
|
||||||
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
|
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package de.dennisguse.opentracks.io.file.exporter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIT v2.0
|
||||||
|
* https://developer.garmin.com/fit/
|
||||||
|
* <p>
|
||||||
|
* Encoding is little endian.
|
||||||
|
*/
|
||||||
|
public class FITv2TrackExporter implements TrackExporter {
|
||||||
|
|
||||||
|
private final byte FIT_PROTOCOL_VERSION = 2 << 4 + 0;
|
||||||
|
private final short FIT_PROFILE_VERSION = 21 * 1000 + 158;
|
||||||
|
|
||||||
|
private long checksum;
|
||||||
|
private final byte FILE_HEADER_SIZE = 14;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||||
|
WritableByteChannel channel = Channels.newChannel(outputStream);
|
||||||
|
|
||||||
|
writeFileHeader(channel);
|
||||||
|
// writeFileId(channel);
|
||||||
|
writeFooter(channel);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.garmin.com/fit/protocol/
|
||||||
|
@VisibleForTesting
|
||||||
|
void writeFileHeader(@NonNull WritableByteChannel channel) {
|
||||||
|
ByteBuffer littleEndian = ByteBuffer
|
||||||
|
.allocate(FILE_HEADER_SIZE)
|
||||||
|
.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
littleEndian.put(0, FILE_HEADER_SIZE);
|
||||||
|
littleEndian.put(1, FIT_PROTOCOL_VERSION);
|
||||||
|
|
||||||
|
// Profile version
|
||||||
|
littleEndian.putShort(2, FIT_PROFILE_VERSION);
|
||||||
|
|
||||||
|
// Data size: 32bit
|
||||||
|
littleEndian.putInt(4, 0); // We do not calculate the data size.
|
||||||
|
|
||||||
|
// .FIT
|
||||||
|
littleEndian.putChar(8, '.');
|
||||||
|
littleEndian.putChar(9, 'F');
|
||||||
|
littleEndian.putChar(10, 'I');
|
||||||
|
littleEndian.putChar(11, 'T');
|
||||||
|
|
||||||
|
// CRC (not implemented)
|
||||||
|
littleEndian.put(12, (byte) 0);
|
||||||
|
littleEndian.put(13, (byte) 0);
|
||||||
|
|
||||||
|
var a = littleEndian.array();
|
||||||
|
littleEndian.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void writeFooter(@NonNull WritableByteChannel channel) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,7 +102,6 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
try {
|
try {
|
||||||
prepare(outputStream);
|
prepare(outputStream);
|
||||||
writeHeader();
|
writeHeader();
|
||||||
|
|
||||||
for (Track track : tracks) {
|
for (Track track : tracks) {
|
||||||
writeMarkers(track);
|
writeMarkers(track);
|
||||||
}
|
}
|
||||||
@@ -200,17 +199,17 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepare(OutputStream outputStream) {
|
private void prepare(OutputStream outputStream) {
|
||||||
this.printWriter = new PrintWriter(outputStream);
|
this.printWriter = new PrintWriter(outputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
private void close() {
|
||||||
printWriter.flush();
|
printWriter.flush();
|
||||||
printWriter = null;
|
printWriter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void writeHeader() {
|
private void writeHeader() {
|
||||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||||
printWriter.println("<gpx");
|
printWriter.println("<gpx");
|
||||||
printWriter.println("version=\"1.1\"");
|
printWriter.println("version=\"1.1\"");
|
||||||
@@ -228,12 +227,12 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
|
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
|
||||||
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
|
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
|
||||||
+ " http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd"
|
+ " http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd"
|
||||||
+ " http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
|
+ " http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 https://www8.garmin.com/xmlschemas/TrackStatsExtension.xsd"
|
||||||
+ " http://www.cluetrust.com/Schemas http://www.cluetrust.com/Schemas/gpxdata10.xsd"
|
+ " http://www.cluetrust.com/Schemas https://www.cluetrust.com/Schemas/gpxdata10.xsd"
|
||||||
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
|
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeFooter() {
|
private void writeFooter() {
|
||||||
printWriter.println("</gpx>");
|
printWriter.println("</gpx>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +252,7 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeMarker(ZoneOffset zoneOffset, Marker marker) {
|
private void writeMarker(ZoneOffset zoneOffset, Marker marker) {
|
||||||
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
|
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
|
||||||
if (marker.hasAltitude()) {
|
if (marker.hasAltitude()) {
|
||||||
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
|
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
|
||||||
@@ -265,7 +264,7 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
printWriter.println("</wpt>");
|
printWriter.println("</wpt>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeBeginTrack(Track track) {
|
private void writeBeginTrack(Track track) {
|
||||||
printWriter.println("<trk>");
|
printWriter.println("<trk>");
|
||||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||||
@@ -297,19 +296,19 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
printWriter.println("</extensions>");
|
printWriter.println("</extensions>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeEndTrack() {
|
private void writeEndTrack() {
|
||||||
printWriter.println("</trk>");
|
printWriter.println("</trk>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeOpenSegment() {
|
private void writeOpenSegment() {
|
||||||
printWriter.println("<trkseg>");
|
printWriter.println("<trkseg>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeCloseSegment() {
|
private void writeCloseSegment() {
|
||||||
printWriter.println("</trkseg>");
|
printWriter.println("</trkseg>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) {
|
private Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) {
|
||||||
Distance cumulativeDistance;
|
Distance cumulativeDistance;
|
||||||
|
|
||||||
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
||||||
@@ -321,51 +320,62 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime(), zoneOffset) + "</time>");
|
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime(), zoneOffset) + "</time>");
|
||||||
|
|
||||||
{
|
{
|
||||||
String trackPointExtensionContent = "";
|
String trackPointExtensionV2Content = "";
|
||||||
|
|
||||||
if (trackPoint.hasSpeed()) {
|
|
||||||
trackPointExtensionContent += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackPoint.hasHeartRate()) {
|
if (trackPoint.hasHeartRate()) {
|
||||||
trackPointExtensionContent += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate().getBPM()) + "</gpxtpx:hr>\n";
|
trackPointExtensionV2Content += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate().getBPM()) + "</gpxtpx:hr>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasCadence()) {
|
if (trackPoint.hasCadence()) {
|
||||||
trackPointExtensionContent += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence().getRPM()) + "</gpxtpx:cad>\n";
|
trackPointExtensionV2Content += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence().getRPM()) + "</gpxtpx:cad>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackPoint.hasSpeed()) {
|
||||||
|
trackPointExtensionV2Content += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
String extensionContent = "";
|
||||||
if (trackPoint.hasPower()) {
|
if (trackPoint.hasPower()) {
|
||||||
trackPointExtensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower().getW()) + "</pwr:PowerInWatts>\n";
|
extensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower().getW()) + "</pwr:PowerInWatts>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null);
|
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null);
|
||||||
if (cumulativeGain != null) {
|
if (cumulativeGain != null) {
|
||||||
trackPointExtensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
|
extensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null);
|
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null);
|
||||||
if (cumulativeLoss != null) {
|
if (cumulativeLoss != null) {
|
||||||
trackPointExtensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
|
extensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasHorizontalAccuracy()) {
|
if (trackPoint.hasHorizontalAccuracy()) {
|
||||||
trackPointExtensionContent += ("<opentracks:accuracy_horizontal>" + DISTANCE_FORMAT.format(trackPoint.getHorizontalAccuracy().toM()) + "</opentracks:accuracy_horizontal>");
|
extensionContent += ("<opentracks:accuracy_horizontal>" + DISTANCE_FORMAT.format(trackPoint.getHorizontalAccuracy().toM()) + "</opentracks:accuracy_horizontal>");
|
||||||
}
|
}
|
||||||
if (trackPoint.hasVerticalAccuracy()) {
|
if (trackPoint.hasVerticalAccuracy()) {
|
||||||
trackPointExtensionContent += ("<opentracks:accuracy_vertical>" + DISTANCE_FORMAT.format(trackPoint.getVerticalAccuracy().toM()) + "</opentracks:accuracy_vertical>");
|
extensionContent += ("<opentracks:accuracy_vertical>" + DISTANCE_FORMAT.format(trackPoint.getVerticalAccuracy().toM()) + "</opentracks:accuracy_vertical>");
|
||||||
}
|
}
|
||||||
|
|
||||||
cumulativeDistance = Distance.ofOrNull(cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null));
|
cumulativeDistance = Distance.ofOrNull(cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null));
|
||||||
if (cumulativeDistance != null) {
|
if (cumulativeDistance != null) {
|
||||||
trackPointExtensionContent += ("<opentracks:distance>" + DISTANCE_FORMAT.format(cumulativeDistance.toM()) + "</opentracks:distance>\n");
|
extensionContent += ("<opentracks:distance>" + DISTANCE_FORMAT.format(cumulativeDistance.toM()) + "</opentracks:distance>\n");
|
||||||
trackPointExtensionContent += ("<cluetrust:distance>" + DISTANCE_FORMAT.format(trackDistance.plus(cumulativeDistance).toM()) + "</cluetrust:distance>\n");
|
extensionContent += ("<cluetrust:distance>" + DISTANCE_FORMAT.format(trackDistance.plus(cumulativeDistance).toM()) + "</cluetrust:distance>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!trackPointExtensionContent.isEmpty()) {
|
if (!extensionContent.isEmpty() || !trackPointExtensionV2Content.isEmpty()) {
|
||||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
printWriter.println("<extensions>");
|
||||||
printWriter.print(trackPointExtensionContent);
|
|
||||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
if (!trackPointExtensionV2Content.isEmpty()) {
|
||||||
|
printWriter.println("<gpxtpx:TrackPointExtension>");
|
||||||
|
printWriter.print(trackPointExtensionV2Content);
|
||||||
|
printWriter.println("</gpxtpx:TrackPointExtension>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extensionContent.isEmpty()) {
|
||||||
|
printWriter.print(extensionContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
printWriter.println("</extensions>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
this.contentProviderUtils = contentProviderUtils;
|
this.contentProviderUtils = contentProviderUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||||
try {
|
try {
|
||||||
prepare(outputStream);
|
prepare(outputStream);
|
||||||
@@ -444,7 +445,7 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
writeTypeLocalized(marker.getCategory());
|
writeTypeLocalized(marker.getCategory());
|
||||||
|
|
||||||
if (exportPhotos) {
|
if (exportPhotos) {
|
||||||
printWriter.println("<Icon><href>" + KmzTrackExporter.buildKmzImageFilePath(marker) + "</href></Icon>");
|
printWriter.println("<Icon><href>" + KMZTrackExporter.buildKmzImageFilePath(marker) + "</href></Icon>");
|
||||||
}
|
}
|
||||||
|
|
||||||
printWriter.print("<ViewVolume>");
|
printWriter.print("<ViewVolume>");
|
||||||
|
|||||||
+3
-3
@@ -43,12 +43,12 @@ import de.dennisguse.opentracks.util.FileUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class KmzTrackExporter implements TrackExporter {
|
public class KMZTrackExporter implements TrackExporter {
|
||||||
|
|
||||||
private static final String KMZ_IMAGES_DIR = "images";
|
private static final String KMZ_IMAGES_DIR = "images";
|
||||||
public static final String KMZ_KML_FILE = "doc.kml";
|
public static final String KMZ_KML_FILE = "doc.kml";
|
||||||
|
|
||||||
private static final String TAG = KmzTrackExporter.class.getSimpleName();
|
private static final String TAG = KMZTrackExporter.class.getSimpleName();
|
||||||
private static final int BUFFER_SIZE = 4096;
|
private static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
@@ -57,7 +57,7 @@ public class KmzTrackExporter implements TrackExporter {
|
|||||||
private final boolean exportPhotos;
|
private final boolean exportPhotos;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
public KmzTrackExporter(Context context, ContentProviderUtils contentProviderUtils, KMLTrackExporter trackExporter, boolean exportPhotos) {
|
public KMZTrackExporter(Context context, ContentProviderUtils contentProviderUtils, KMLTrackExporter trackExporter, boolean exportPhotos) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.contentProviderUtils = contentProviderUtils;
|
this.contentProviderUtils = contentProviderUtils;
|
||||||
this.fileTrackExporter = trackExporter;
|
this.fileTrackExporter = trackExporter;
|
||||||
+3
-3
@@ -52,9 +52,9 @@ import de.dennisguse.opentracks.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class GpxTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
public class GPXTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
||||||
|
|
||||||
private static final String TAG = GpxTrackImporter.class.getSimpleName();
|
private static final String TAG = GPXTrackImporter.class.getSimpleName();
|
||||||
|
|
||||||
private static final String TAG_DESCRIPTION = "desc";
|
private static final String TAG_DESCRIPTION = "desc";
|
||||||
private static final String TAG_ALTITUDE = "ele";
|
private static final String TAG_ALTITUDE = "ele";
|
||||||
@@ -123,7 +123,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
|
|
||||||
private final TrackImporter trackImporter;
|
private final TrackImporter trackImporter;
|
||||||
|
|
||||||
public GpxTrackImporter(Context context, TrackImporter trackImporter) {
|
public GPXTrackImporter(Context context, TrackImporter trackImporter) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.trackImporter = trackImporter;
|
this.trackImporter = trackImporter;
|
||||||
}
|
}
|
||||||
@@ -54,17 +54,16 @@ public class ImportService extends JobIntentService {
|
|||||||
String fileExtension = FileUtils.getExtension(file);
|
String fileExtension = FileUtils.getExtension(file);
|
||||||
try {
|
try {
|
||||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
|
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
|
||||||
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
|
|
||||||
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
|
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
|
||||||
|
|
||||||
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), maxRecordingDistance, preventReimport);
|
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), maxRecordingDistance, preventReimport);
|
||||||
|
|
||||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||||
trackIds.addAll(new XMLImporter(new GpxTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
|
trackIds.addAll(new XMLImporter(new GPXTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
|
||||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||||
trackIds.addAll(new XMLImporter(new KmlTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
|
trackIds.addAll(new XMLImporter(new KMLTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
|
||||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||||
trackIds.addAll(new KmzTrackImporter(this, trackImporter).importFile(file.getUri()));
|
trackIds.addAll(new KMZTrackImporter(this, trackImporter).importFile(file.getUri()));
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Unsupported file format.");
|
Log.d(TAG, "Unsupported file format.");
|
||||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
||||||
|
|||||||
+3
-3
@@ -47,9 +47,9 @@ import de.dennisguse.opentracks.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class KmlTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
public class KMLTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
||||||
|
|
||||||
private static final String TAG = KmlTrackImporter.class.getSimpleName();
|
private static final String TAG = KMLTrackImporter.class.getSimpleName();
|
||||||
|
|
||||||
private static final String MARKER_STYLE = "#" + KMLTrackExporter.MARKER_STYLE;
|
private static final String MARKER_STYLE = "#" + KMLTrackExporter.MARKER_STYLE;
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
|
|
||||||
private final TrackImporter trackImporter;
|
private final TrackImporter trackImporter;
|
||||||
|
|
||||||
public KmlTrackImporter(Context context, TrackImporter trackImporter) {
|
public KMLTrackImporter(Context context, TrackImporter trackImporter) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.trackImporter = trackImporter;
|
this.trackImporter = trackImporter;
|
||||||
}
|
}
|
||||||
+4
-4
@@ -44,9 +44,9 @@ import de.dennisguse.opentracks.util.FileUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class KmzTrackImporter {
|
public class KMZTrackImporter {
|
||||||
|
|
||||||
private static final String TAG = KmzTrackImporter.class.getSimpleName();
|
private static final String TAG = KMZTrackImporter.class.getSimpleName();
|
||||||
|
|
||||||
private static final String KML_FILE_EXTENSION = ".kml";
|
private static final String KML_FILE_EXTENSION = ".kml";
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ public class KmzTrackImporter {
|
|||||||
private final Context context;
|
private final Context context;
|
||||||
private final TrackImporter trackImporter;
|
private final TrackImporter trackImporter;
|
||||||
|
|
||||||
public KmzTrackImporter(Context context, TrackImporter trackImporter) {
|
public KMZTrackImporter(Context context, TrackImporter trackImporter) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.trackImporter = trackImporter;
|
this.trackImporter = trackImporter;
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ public class KmzTrackImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Track.Id> parseKml(ZipInputStream zipInputStream) throws IOException {
|
private List<Track.Id> parseKml(ZipInputStream zipInputStream) throws IOException {
|
||||||
XMLImporter kmlFileTrackImporter = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
XMLImporter kmlFileTrackImporter = new XMLImporter(new KMLTrackImporter(context, trackImporter));
|
||||||
|
|
||||||
InputStream nonClosableInputStream = new FilterInputStream(zipInputStream) {
|
InputStream nonClosableInputStream = new FilterInputStream(zipInputStream) {
|
||||||
@Override
|
@Override
|
||||||
@@ -233,7 +233,7 @@ public class TrackImporter {
|
|||||||
* @param externalPhotoUrl the file name
|
* @param externalPhotoUrl the file name
|
||||||
*/
|
*/
|
||||||
private Uri getInternalPhotoUrl(@NonNull Track.Id trackId, @NonNull Uri externalPhotoUrl) {
|
private Uri getInternalPhotoUrl(@NonNull Track.Id trackId, @NonNull Uri externalPhotoUrl) {
|
||||||
String importFileName = KmzTrackImporter.importNameForFilename(externalPhotoUrl.toString());
|
String importFileName = KMZTrackImporter.importNameForFilename(externalPhotoUrl.toString());
|
||||||
File file = MarkerUtils.buildInternalPhotoFile(context, trackId, Uri.parse(importFileName));
|
File file = MarkerUtils.buildInternalPhotoFile(context, trackId, Uri.parse(importFileName));
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
return FileUtils.getUriForFile(context, file);
|
return FileUtils.getUriForFile(context, file);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class XMLImporter {
|
|||||||
return parser.getImportTrackIds();
|
return parser.getImportTrackIds();
|
||||||
} catch (SAXException | ParserConfigurationException | ParsingException e) {
|
} catch (SAXException | ParserConfigurationException | ParsingException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
if (parser.getImportTrackIds().size() > 0) {
|
if (!parser.getImportTrackIds().isEmpty()) {
|
||||||
parser.cleanImport();
|
parser.cleanImport();
|
||||||
}
|
}
|
||||||
throw new ImportParserException(e);
|
throw new ImportParserException(e);
|
||||||
|
|||||||
@@ -3,24 +3,14 @@
|
|||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/export_activity_toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:title="@string/custom_layout_list_title" />
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<LinearLayout android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="60dp">
|
android:paddingBottom="?attr/actionBarSize">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/rows_options_layout"
|
android:id="@+id/rows_options_layout"
|
||||||
@@ -50,16 +40,16 @@
|
|||||||
android:id="@+id/recycler_view_visible"
|
android:id="@+id/recycler_view_visible"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintBottom_toTopOf="@id/recycler_view_not_visible"
|
||||||
app:layout_constraintBottom_toTopOf="@id/recycler_view_not_visible" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view_not_visible"
|
android:id="@+id/recycler_view_not_visible"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/recycler_view_visible"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintTop_toBottomOf="@id/recycler_view_visible" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/export_activity_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:title="@string/custom_layout_list_title" />
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:paddingTop="?attr/actionBarSize"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:fitsSystemWindows="true"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
android:orientation="vertical">
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingBottom="60dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/custom_layout_list_add_linear_layout"
|
android:id="@+id/custom_layout_list_add_linear_layout"
|
||||||
@@ -18,25 +30,24 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_marginBottom="50dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/custom_layout_list_input_layout"
|
android:id="@+id/custom_layout_list_input_layout"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
android:layout_margin="16dp"
|
||||||
app:errorEnabled="true"
|
app:errorEnabled="true">
|
||||||
android:layout_margin="16dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/custom_layout_list_edit_name"
|
android:id="@+id/custom_layout_list_edit_name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="textCapSentences"
|
android:hint="@string/custom_layout_list_edit_name"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:hint="@string/custom_layout_list_edit_name" />
|
android:inputType="textCapSentences" />
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
@@ -50,9 +61,9 @@
|
|||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/custom_layout_list_cancel_button"
|
android:id="@+id/custom_layout_list_cancel_button"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/Widget.Material3.Button.TextButton"
|
|
||||||
android:text="@android:string/cancel" />
|
android:text="@android:string/cancel" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ limitations under the License.
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="90dp"
|
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
@@ -32,6 +31,7 @@ limitations under the License.
|
|||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/track_detail_activity_view_pager"
|
android:id="@+id/track_detail_activity_view_pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:paddingBottom="?attr/actionBarSize"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="90dp"
|
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
@@ -20,6 +19,7 @@
|
|||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/track_recording_coordinator_layout"
|
android:id="@+id/track_recording_coordinator_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:paddingBottom="?attr/actionBarSize"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
|||||||
@@ -110,16 +110,16 @@
|
|||||||
<string name="activity_type_workout">பயிற்சி</string>
|
<string name="activity_type_workout">பயிற்சி</string>
|
||||||
<string name="description_activity_type">செயல்பாட்டு வகை: %1$s</string>
|
<string name="description_activity_type">செயல்பாட்டு வகை: %1$s</string>
|
||||||
<string name="description_average_moving_pace_in_minute">சராசரி நகரும் வேகம்: %1$s min/km ( %2$s min/mi)</string>
|
<string name="description_average_moving_pace_in_minute">சராசரி நகரும் வேகம்: %1$s min/km ( %2$s min/mi)</string>
|
||||||
<string name="description_average_moving_speed">சராசரி நகரும் வேகம்: %1 $ .2f கிமீ/மணி ( %2 $ .1f மைல்/எச்)</string>
|
<string name="description_average_moving_speed">சராசரி நகரும் வேகம்: %1$.2f கிமீ/மணி ( %2$.1f மைல்/எச்)</string>
|
||||||
<string name="description_average_pace_in_minute">சராசரி வேகம்: %1$s min/km ( %2$s min/mi)</string>
|
<string name="description_average_pace_in_minute">சராசரி வேகம்: %1$s min/km ( %2$s min/mi)</string>
|
||||||
<string name="description_average_speed">சராசரி வேகம்: %1 $ .2f கிமீ/மணி ( %2 $ .1f Mi/h)</string>
|
<string name="description_average_speed">சராசரி வேகம்: %1$.2f கிமீ/மணி ( %2$.1f Mi/h)</string>
|
||||||
<string name="description_altitude_gain">உயர்வு ஆதாயம்: %1$d மீ ( %2$d அடி)</string>
|
<string name="description_altitude_gain">உயர்வு ஆதாயம்: %1$d மீ ( %2$d அடி)</string>
|
||||||
<string name="description_altitude_loss">உயர இழப்பு: %1$d மீ ( %2$d அடி)</string>
|
<string name="description_altitude_loss">உயர இழப்பு: %1$d மீ ( %2$d அடி)</string>
|
||||||
<string name="description_altitude_imperial">உயரம் (அடி)</string>
|
<string name="description_altitude_imperial">உயரம் (அடி)</string>
|
||||||
<string name="description_altitude_metric">உயரம் (மீ)</string>
|
<string name="description_altitude_metric">உயரம் (மீ)</string>
|
||||||
<string name="description_fastest_pace_in_minute">வேகமான வேகம்: %1$s min/km ( %2$s min/mi)</string>
|
<string name="description_fastest_pace_in_minute">வேகமான வேகம்: %1$s min/km ( %2$s min/mi)</string>
|
||||||
<string name="description_max_altitude">அதிகபட்ச உயர்வு: %1$d மீ ( %2$d அடி)</string>
|
<string name="description_max_altitude">அதிகபட்ச உயர்வு: %1$d மீ ( %2$d அடி)</string>
|
||||||
<string name="description_max_speed">அதிகபட்ச வேகம்: %1 $ .2f கிமீ/மணி ( %2 $ .1f Mi/h)</string>
|
<string name="description_max_speed">அதிகபட்ச வேகம்: %1$.2f கிமீ/மணி ( %2$.1f Mi/h)</string>
|
||||||
<string name="description_moving_time">நகரும் நேரம்: %1$s</string>
|
<string name="description_moving_time">நகரும் நேரம்: %1$s</string>
|
||||||
<string name="description_pace_imperial">விரைவு (நிமிடம்/மை)</string>
|
<string name="description_pace_imperial">விரைவு (நிமிடம்/மை)</string>
|
||||||
<string name="description_pace_nautical">விரைவு (நிமிடம்/என்.எம்)</string>
|
<string name="description_pace_nautical">விரைவு (நிமிடம்/என்.எம்)</string>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
<string name="description_speed_nautical">விரைவு (முடிச்சுகள்)</string>
|
<string name="description_speed_nautical">விரைவு (முடிச்சுகள்)</string>
|
||||||
<string name="description_speed_metric">விரைவு (கிமீ/மணிநேரம்)</string>
|
<string name="description_speed_metric">விரைவு (கிமீ/மணிநேரம்)</string>
|
||||||
<string name="description_time">நேரம்</string>
|
<string name="description_time">நேரம்</string>
|
||||||
<string name="description_total_distance">மொத்த தூரம்: %1 $ .2f கி.மீ ( %2 $ .1f மை)</string>
|
<string name="description_total_distance">மொத்த தூரம்: %1$.2f கி.மீ ( %2$.1f மை)</string>
|
||||||
<string name="description_total_time">மொத்த நேரம்: %1$s</string>
|
<string name="description_total_time">மொத்த நேரம்: %1$s</string>
|
||||||
<string name="description_speed_source_gps">உலக இடம் காட்டும் அமைப்பு</string>
|
<string name="description_speed_source_gps">உலக இடம் காட்டும் அமைப்பு</string>
|
||||||
<string name="export_with_photos">புகைப்படங்களுடன்</string>
|
<string name="export_with_photos">புகைப்படங்களுடன்</string>
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
<string name="sensor_state_power_avg">ஏ.வி.சி ஆற்றல்</string>
|
<string name="sensor_state_power_avg">ஏ.வி.சி ஆற்றல்</string>
|
||||||
<string name="sensor_state_power_max">அதிகபட்ச ஆற்றல்</string>
|
<string name="sensor_state_power_max">அதிகபட்ச ஆற்றல்</string>
|
||||||
<string name="sensor_state_power_unit">W</string>
|
<string name="sensor_state_power_unit">W</string>
|
||||||
<string name="sensor_could_not_scan">ஊடலை சாதனங்களுக்கு ச்கேன் செய்ய முடியவில்லை (பிழை %1 $ i).</string>
|
<string name="sensor_could_not_scan">ஊடலை சாதனங்களுக்கு ச்கேன் செய்ய முடியவில்லை (பிழை %1$i).</string>
|
||||||
<string name="bluetooth_disabled">ப்ளூடூத்தை இயக்கவும்.</string>
|
<string name="bluetooth_disabled">ப்ளூடூத்தை இயக்கவும்.</string>
|
||||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||||
<string name="settings_defaults_title">இயல்புநிலை அலகுகள் மற்றும் செயல்பாடு</string>
|
<string name="settings_defaults_title">இயல்புநிலை அலகுகள் மற்றும் செயல்பாடு</string>
|
||||||
@@ -394,16 +394,16 @@
|
|||||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||||
<string name="value_integer_feet_recommended">%1$d அடி (பரிந்துரைக்கப்படுகிறது)</string>
|
<string name="value_integer_feet_recommended">%1$d அடி (பரிந்துரைக்கப்படுகிறது)</string>
|
||||||
<string name="value_integer_kilometer_hour">%1$d கிமீ/மணி</string>
|
<string name="value_integer_kilometer_hour">%1$d கிமீ/மணி</string>
|
||||||
<string name="value_float_kilometer_hour">%1 $ .1f கிமீ/மணி</string>
|
<string name="value_float_kilometer_hour">%1$.1f கிமீ/மணி</string>
|
||||||
<string name="value_integer_meter_excellent_gps">%1$d மீ (சிறந்த சி.பி.எச்)</string>
|
<string name="value_integer_meter_excellent_gps">%1$d மீ (சிறந்த சி.பி.எச்)</string>
|
||||||
<string name="value_integer_meter_poor_gps">%1$d மீ (மோசமான சி.பி.எச்)</string>
|
<string name="value_integer_meter_poor_gps">%1$d மீ (மோசமான சி.பி.எச்)</string>
|
||||||
<string name="value_integer_meter_recommended">%1$d மீ (பரிந்துரைக்கப்படுகிறது)</string>
|
<string name="value_integer_meter_recommended">%1$d மீ (பரிந்துரைக்கப்படுகிறது)</string>
|
||||||
<string name="value_integer_mile_hour">%1$d Mi/h</string>
|
<string name="value_integer_mile_hour">%1$d Mi/h</string>
|
||||||
<string name="value_integer_knots">%1$d முடிச்சுகள்</string>
|
<string name="value_integer_knots">%1$d முடிச்சுகள்</string>
|
||||||
<string name="value_float_mile_hour">%1 $ .1f Mi/h</string>
|
<string name="value_float_mile_hour">%1$.1f Mi/h</string>
|
||||||
<string name="value_float_knots">%1 $ .1f முடிச்சுகள்</string>
|
<string name="value_float_knots">%1$.1f முடிச்சுகள்</string>
|
||||||
<string name="value_float_mile_hour_recommended">%1 $ .1f Mi/h (பரிந்துரைக்கப்படுகிறது)</string>
|
<string name="value_float_mile_hour_recommended">%1$.1f Mi/h (பரிந்துரைக்கப்படுகிறது)</string>
|
||||||
<string name="value_float_knots_recommended">%1 $ .1f முடிச்சுகள் (பரிந்துரைக்கப்படுகிறது)</string>
|
<string name="value_float_knots_recommended">%1$.1f முடிச்சுகள் (பரிந்துரைக்கப்படுகிறது)</string>
|
||||||
<string name="value_integer_second">%1$d கள்</string>
|
<string name="value_integer_second">%1$d கள்</string>
|
||||||
<string name="value_none">எதுவுமில்லை</string>
|
<string name="value_none">எதுவுமில்லை</string>
|
||||||
<string name="value_off">அணை</string>
|
<string name="value_off">அணை</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user