Compare commits

...

43 Commits

Author SHA1 Message Date
Dennis Guse 4c44ea12a4 Release: v4.0.4 2022-07-15 21:41:46 +02:00
dependabot[bot] 9d09865907 Bump reactivecircus/android-emulator-runner from 2.24.0 to 2.25.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.24.0 to 2.25.0.
- [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases)
- [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/v2.24.0...v2.25.0)

---
updated-dependencies:
- dependency-name: reactivecircus/android-emulator-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-13 07:39:30 +02:00
Dennis Guse 5667d2d83e Store sensor data without distance.
Fixes #1088.
2022-07-11 22:56:11 +02:00
Dennis Guse 386f017fb7 VoiceAnnouncement: avoid using deprecated API. 2022-07-10 21:46:15 +02:00
Román Ginés 87594e822d Bugfixed: adding a mark with a photo after rotating in camera resulted in a crash. Fixes #1321. 2022-07-10 19:47:45 +02:00
Dennis Guse ee832c7bd2 Revert upgrade ot AGP 7.3.0-beta. 2022-07-10 17:59:32 +02:00
Dennis Guse 30042d5bc3 Merge branch 'translations' 2022-07-09 19:49:28 +02:00
Hiajen 73cbb7a29c Translated using Weblate (German)
Currently translated at 75.5% (68 of 90 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/de/
2022-07-09 19:48:52 +02:00
Hiajen c100fdde4f Translated using Weblate (German)
Currently translated at 94.8% (475 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2022-07-09 19:48:52 +02:00
Dennis Guse 04e1496084 Cleanup: adjusted Interval uses constructor.
Fixes #1320.
2022-07-09 15:52:57 +02:00
Dennis Guse 21d1852505 Test: import+export results in same timezone.
Part of #1296.
2022-07-08 00:16:48 +02:00
Dennis Guse 1502b0cfa7 Gain/loss: handle events in our TrackRecordingService.handler (avoids potential race conditions). 2022-07-07 18:27:56 +02:00
Dennis Guse e7d6f65508 Gain/loss: use 5s as sensor interval to save some energy. 2022-07-07 18:27:56 +02:00
Dennis Guse 0e69a444a5 Add a comma. 2022-07-07 18:26:27 +02:00
Román Ginés 3b62999fde Announcement computes intervals from last track point. Fixes #645.
Bugfixed: intervals distance wasn't computed correctly.
2022-07-06 22:54:45 +02:00
Dennis Guse 97431233ac Fix translations. 2022-07-05 21:01:09 +02:00
Uglješa d49675afc8 Translated using Weblate (Serbian)
Currently translated at 56.6% (284 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sr/
2022-07-03 18:29:42 +02:00
vsevjednom-cz c1f7ab36a8 Translated using Weblate (Czech)
Currently translated at 94.2% (472 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2022-07-03 18:29:42 +02:00
FTno eb14cc7bc6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (500 of 500 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-07-03 18:29:42 +02:00
Xosé M a47b9164af Translated using Weblate (Galician)
Currently translated at 100.0% (500 of 500 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2022-07-03 18:29:42 +02:00
Surfoo 502e3ecb21 Translated using Weblate (French)
Currently translated at 100.0% (500 of 500 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2022-07-03 18:29:42 +02:00
metezd 71903f174a Translated using Weblate (Turkish)
Currently translated at 98.0% (490 of 500 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/tr/
2022-07-03 18:29:41 +02:00
Dennis Guse ab6606a295 Github actions: rename APK build job. 2022-07-03 18:29:38 +02:00
Dennis Guse c4e2023992 Public API: allow setting track properties (name, category, description) on StartRecording.
Fixes #1282.
2022-07-03 17:32:21 +02:00
Dennis Guse cb10bdda3f Settings: show package id in Public API settings. 2022-07-03 10:28:43 +02:00
Dennis Guse f913472aae Settings: idle speed threshold has now a summary. 2022-07-03 10:28:31 +02:00
Dennis Guse af4826c578 TrackStoppedActivity: update track data on resume track.
Fixes #1249.
2022-07-02 08:51:31 +02:00
Dennis Guse 63172ec66f Github Actions: downgrade triggers. 2022-07-01 20:40:38 +02:00
Dennis Guse 3d6c339509 Android13: use new Image picker.
Part of #1210.
2022-07-01 20:24:20 +02:00
Dennis Guse 49f86c4eb9 Android13: upgrade target sdk to 33.
Part of #1210.
2022-07-01 20:24:20 +02:00
Dennis Guse 7664bd248d Release: v4.0.3 2022-07-01 13:27:50 +02:00
Dennis Guse 73615aa0d6 Statistics: remove speed filtering (fix tests).
Part of #1003.
2022-07-01 13:24:18 +02:00
Dennis Guse 81bb6699d8 Statistics: remove speed filtering.
Part of #1003.
2022-06-30 14:03:33 +02:00
dependabot[bot] 93c67e3728 Bump desugar_jdk_libs from 1.1.5 to 1.1.6
Bumps [desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 1.1.5 to 1.1.6.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-29 08:45:59 +02:00
Dennis Guse 993816a34a Github issue template for data not recorded.
Fixes #1295.
2022-06-28 16:18:41 +02:00
Šimon Hořánek 3c0999c3a0 Translated using Weblate (Czech)
Currently translated at 94.3% (471 of 499 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2022-06-27 23:03:27 +02:00
metezd 4528e7148f Translated using Weblate (Turkish)
Currently translated at 98.1% (490 of 499 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/tr/
2022-06-27 23:03:27 +02:00
FTno 74042f5d85 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (499 of 499 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-06-27 23:03:27 +02:00
Dennis Guse 92f6fbe04a Prevent crash if no dashboard is installed. 2022-06-27 23:03:23 +02:00
Dennis Guse 5fa50250de Cleanup tests for StringUtils.parseTime().
Part of #1295.
2022-06-27 19:30:23 +02:00
Dennis Guse 8ee9daf7af Github issue template for data not recorded.
Fixes #1295.
2022-06-27 12:58:17 +02:00
Dennis Guse a54c8c1d25 Use Set.of(). 2022-06-22 20:31:41 +02:00
Dennis Guse 8137ec8de2 GPX exporter: only export trkseq if it actually contains data.
Fixes #1291.
2022-06-21 19:56:19 +02:00
47 changed files with 614 additions and 393 deletions
@@ -0,0 +1,24 @@
---
name: Data not recorded
about: Somehow OpenTracks did not record GPS data
title: ''
labels: potential bug
assignees: ''
---
**Please describe the problem and how to reproduce it:**
...
**Checklist**
- [ ] Consulted [https://dontkillmyapp.com/]
- [ ] Power management: battery optimization is disabled for OpenTracks
**Technical information**
- Device: [e.g. Nexus 7 (2013)]
- OS: [e.g. LineageOS 15.1]
- OpenTracks version: [e.g. v3.2.4]
- OpenTracks commit id (only for nightly builds): [e.g. 4b333bb]
@@ -1,9 +1,10 @@
#https://github.com/marketplace/actions/android-emulator-runner
name: Build nightly
on: [ push ]
name: Build APK
on:
push
jobs:
nightly-build:
apk-build:
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -14,7 +15,7 @@ jobs:
distribution: 'adopt'
java-version: '11'
- name: Assemble nighlty APK
- name: Assemble APK
shell: bash
run: |
echo ${NIGHTLY_STORE_FILE} | base64 -d > KEY_NIGHTLY.jks
@@ -28,7 +29,7 @@ jobs:
if: always()
uses: actions/upload-artifact@v3
with:
name: OpenTracks-nightly.apk
name: OpenTracks-APK.apk
path: ./build/outputs/apk/nightly/*.apk
retention-days: 7
+1 -1
View File
@@ -2,7 +2,7 @@ name: Linting
on:
push:
pull_request_target:
pull_request:
jobs:
analyse:
+2 -2
View File
@@ -2,7 +2,7 @@
name: Test
on:
push:
pull_request_target:
pull_request:
jobs:
test:
@@ -23,7 +23,7 @@ jobs:
java-version: '11'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.24.0
uses: reactivecircus/android-emulator-runner@v2.25.0
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
+24 -7
View File
@@ -95,7 +95,7 @@ OpenTracks can be used with [Gadgetbridge](https://www.gadgetbridge.org/):
__Only required permission:__
* _ACCESS_FINE_LOCATION_: required to use the GPS.
### API
### Public API
OpenTracks includes an API for starting/stopping recording by another installed application (e.g., [Automate](https://llamalab.com/automate/), [Tasker](https://tasker.joaoapps.com), or [Easer](https://github.com/renyuneyun/Easer)).
The API can be invoked by sending an explicit Intent to start an activity.
@@ -107,20 +107,37 @@ The API can be invoked by sending an explicit Intent to start an activity.
* Nightly: `de.dennisguse.opentracks.nightly`
`Class`:
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
* **Stop a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
For testing via adb: `adb shell am start -n "package/class"`
The Start/Stop API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings.
`StartRecording` supports the following parameters:
## Custom Dashboards (incl. map)
As of v3.3.1, OpenTracks supports custom dashboards for displaying previously recorded and live tracks.
* Set track data: `TRACK_NAME`, `TRACK_CATEGORY`, and `TRACK_DESCRIPTION`
* Send recorded data to another application via _Dashboard API_: `STATS_TARGET_PACKAGE`
and `STATS_TARGET_CLASS`
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates). The Dashboard API is also used by [Gadgetbridge](https://codeberg.org/Freeyourgadget/Gadgetbridge/) for displaying live track statistics on supported wearables.
The Public API is disabled by default to protect the user's privacy, but it can easily be enabled in
the settings.
Alternatively, recorded tracks can be shared as KMZ/GPX with installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)).
However, this is rather slow and does not provide updates while recording.
__IMPORTANT__: triggering `StartRecording` does not check if Android permissions (location or
Bluetooth) were granted. If they are not granted`
## Dashboard API (incl. map)
As of v3.3.1, OpenTracks supports custom dashboards for displaying previously recorded and live
tracks.
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which
presents an OpenStreetMap map (showing the current track, incl. updates). The Dashboard API is also
used by [Gadgetbridge](https://codeberg.org/Freeyourgadget/Gadgetbridge/) for displaying live track
statistics on supported wearables.
Alternatively, recorded tracks can be shared as KMZ/GPX with installed applications (
e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)). However, this is rather
slow and does not provide updates while recording.
The developer documentation is in [README_API.md](README_API.md).
+6 -6
View File
@@ -51,7 +51,7 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
compileSdkVersion 32
compileSdk 33
buildFeatures {
viewBinding true
@@ -59,13 +59,13 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4938
versionName "v4.0.2"
versionCode 4981
versionName "v4.0.4"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdkVersion 21
targetSdkVersion 32
minSdk 21
targetSdk 33
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
@@ -117,7 +117,7 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.preference:preference:1.2.0'
@@ -7,4 +7,4 @@ Funktionen & Features:
* zeichnet deine Herzfrequenz auf (Bluetooth LE)
* nutze beim Radfahren deine Trittfrequenz- und Geschwindigkeitssensoren sowie Leistungsmesser (Bluetooth LE)
* exportiert deine Daten als KML oder GPX
* funktioniert ganz ohne Internet
* funktioniert ganz ohne Internet
@@ -1 +1 @@
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
@@ -0,0 +1,8 @@
v4.0.3: OpenTracks
Changes:
- Max. speed as reported by sensor (GPS or BLE)
Bugfix:
- Prevent crash if OSM Dashboard is not installed
- GPX track export: do not export empty track segments
@@ -0,0 +1,13 @@
v4.0.4: OpenTracks
Changes:
- Store BLE sensor data even without distance (every 10s)
- Public API: allows setting name, category, and description
Bugfix:
- Crash when taking a picture and rotating the phone
-
Developer:
- Target SDK 33
- Barometer uses interval of 5s
@@ -7,7 +7,6 @@ import android.util.Pair;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.data.models.Track;
@@ -24,10 +23,7 @@ public class ShareContentProviderTest {
@Test
public void testCreateAndParseURI_valid() {
Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
trackIds.add(new Track.Id(3));
trackIds.add(new Track.Id(5));
Set<Track.Id> trackIds = Set.of(new Track.Id(1), new Track.Id(3), new Track.Id(5));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "filename.suffix", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
@@ -36,9 +32,7 @@ public class ShareContentProviderTest {
@Test
public void testCreateURIescapeFilename() {
Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(Set.of(new Track.Id(1)), "../../&1=1", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/KML_WITH_TRACKDETAIL_AND_SENSORDATA/1/..%2F..%2F%261%3D1"), shareURIandMIME.first);
}
@@ -155,26 +155,31 @@ public class ExportImportTest {
mockAltitudeChange(trackPointCreator, 1);
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
trackPointCreator.setClock("2020-02-02T02:02:05Z");
trackPointCreator.setClock("2020-02-02T02:02:14Z");
mockBLESensorData(trackPointCreator, 15f, null, 66f, 3f, 50f);
trackPointCreator.setClock("2020-02-02T02:02:15Z");
mockBLESensorData(trackPointCreator, null, null, 66f, 3f, 50f);
trackPointCreator.setClock("2020-02-02T02:02:16Z");
mockBLESensorData(trackPointCreator, 5f, Distance.of(2), 66f, 3f, 50f); // Distance will be added to next TrackPoint
sendLocation(trackPointCreator, "2020-02-02T02:02:05Z", 3, 14.001, 10, 13, 15, 10, 0);
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3, 14.001, 10, 13, 15, 10, 0);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
trackPointCreator.setClock("2020-02-02T02:02:06Z");
trackPointCreator.setClock("2020-02-02T02:02:18Z");
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
service.endCurrentTrack();
trackPointCreator.setClock("2020-02-02T02:02:20Z");
trackPointCreator.setClock("2020-02-02T02:03:20Z");
service.resumeTrack(trackId);
sendLocation(trackPointCreator, "2020-02-02T02:02:21Z", 3, 14.002, 10, 13, 15, 10, 0);
sendLocation(trackPointCreator, "2020-02-02T02:03:21Z", 3, 14.002, 10, 13, 15, 10, 0);
sendLocation(trackPointCreator, "2020-02-02T02:02:22Z", 3, 16, 10, 13, 15, 10, 0);
sendLocation(trackPointCreator, "2020-02-02T02:03:22Z", 3, 16, 10, 13, 15, 10, 0);
sendLocation(trackPointCreator, "2020-02-02T02:02:23Z", 3, 16.001, 10, 27, 15, 10, 0);
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
trackPointCreator.setClock("2020-02-02T02:02:24Z");
trackPointCreator.setClock("2020-02-02T02:03:24Z");
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
service.endCurrentTrack();
@@ -187,7 +192,7 @@ public class ExportImportTest {
track = contentProviderUtils.getTrack(trackId);
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
markers = contentProviderUtils.getMarkers(trackId);
assertEquals(10, trackPoints.size());
assertEquals(12, trackPoints.size());
assertEquals(2, markers.size());
}
@@ -231,29 +236,29 @@ public class ExportImportTest {
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:24Z"), importedTrackStatistics.getStopTime());
assertEquals(Instant.parse("2020-02-02T02:03:24Z"), importedTrackStatistics.getStopTime());
TrackStatistics originalTrackStatistics = track.getTrackStatistics();
assertEquals(originalTrackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(7), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(19), importedTrackStatistics.getTotalTime());
assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance());
assertEquals(123.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
assertEquals(125.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed());
assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(41.72, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed());
assertEquals(17.59, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(6.58, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(41.72, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
// Altitude
assertEquals(originalTrackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01);
@@ -345,19 +350,19 @@ public class ExportImportTest {
.setAltitudeLoss(1f)
.setAltitudeGain(1f)
.setHorizontalAccuracy(Distance.of(10)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z"))
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:17Z"))
.setLatitude(3)
.setLongitude(14.001)
.setAltitude(10)
.setSpeed(Speed.of(5))
.setAltitudeLoss(1f)
.setAltitudeGain(1f)
.setSensorDistance(Distance.of(12))
.setSensorDistance(Distance.of(14))
.setHeartRate(66f)
.setPower(50f)
.setCadence(3f)
.setHorizontalAccuracy(Distance.of(10)),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:21Z"))
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:03:21Z"))
.setLatitude(3)
.setLongitude(14.002)
.setAltitude(10)
@@ -365,7 +370,7 @@ public class ExportImportTest {
.setAltitudeGain(0f)
.setSpeed(Speed.of(15))
.setHorizontalAccuracy(Distance.of(10)),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:22Z"))
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:03:22Z"))
.setLatitude(3)
.setLongitude(16)
.setAltitude(10)
@@ -373,7 +378,7 @@ public class ExportImportTest {
.setAltitudeGain(0f)
.setSpeed(Speed.of(15))
.setHorizontalAccuracy(Distance.of(10)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:23Z"))
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:03:23Z"))
.setLatitude(3)
.setLongitude(16.001)
.setAltitude(10)
@@ -390,18 +395,18 @@ public class ExportImportTest {
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
assertEquals(Instant.parse("2020-02-02T02:03:23Z"), importedTrackStatistics.getStopTime());
assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getMovingTime());
assertEquals(Duration.ofSeconds(15), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(15), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(123.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
assertEquals(125.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(41.05, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(41.05, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(8.34, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(8.34, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8.34, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
@@ -15,11 +15,16 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
@@ -27,6 +32,8 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
@@ -181,7 +188,7 @@ public class GPXTrackImporterTest {
// 3. trackstatistics
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
assertEquals(4, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(4.0, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime());
// 4. trackpoints
@@ -208,4 +215,31 @@ public class GPXTrackImporterTest {
.setSpeed(Speed.of(3))
), importedTrackPoints);
}
@LargeTest
@Test
public void importExportTest_timezone() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx_timezone);
InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx_timezone);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
trackExporter.writeTrack(importedTrack, outputStream);
// then
String expected = new BufferedReader(
new InputStreamReader(inputStreamExpected, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n")) + "\n";
assertEquals(expected, outputStream.toString()); //TODO inputStream.readAllBytes() ?
}
}
@@ -31,12 +31,14 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -257,6 +259,47 @@ public class TrackRecordingServiceTestRecording {
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_blesensor_only_no_distance() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
// when
String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1);
remoteSensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(5))); //Should be ignored
String sensor3 = "2020-02-02T02:02:13Z";
trackPointCreator.setClock(sensor3);
remoteSensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(7)));
String stopTime = "2020-02-02T02:02:15Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor3))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate(HeartRate.of(7)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate(HeartRate.of(7))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_recordingDistance_above() {
@@ -11,6 +11,7 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.models.Altitude;
@@ -245,6 +246,70 @@ public class TrackStatisticsUpdaterTest {
public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() {
}
@Test
public void addTrackPoint_maxSpeed_ignore_above_acceleration() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)));
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
// Ignore as we set max speed if two consecutive trackpoints were considered moving
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1))
.setSpeed(Speed.of(1f)));
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
// Update max speed
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2))
.setSpeed(Speed.of(1f)));
assertEquals(Speed.of(1f), subject.getTrackStatistics().getMaxSpeed());
// Update max speed
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(12))
.setSpeed(Speed.of(50f)));
assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed());
// Ignore; we were getting slower
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(13))
.setSpeed(Speed.of(5f)));
assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed());
// Ignore acceleration above 2g
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(14))
.setSpeed(Speed.of(500f)));
assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed());
}
@Test
public void addTrackPoint_maxSpeed_multiple_segments() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
subject.addTrackPoints(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1))
.setSpeed(Speed.of(2f)),
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2))
.setSpeed(Speed.of(2f)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(4))
));
assertEquals(Speed.of(2f), subject.getTrackStatistics().getMaxSpeed());
// when
subject.addTrackPoints(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(5)),
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(6))
.setSpeed(Speed.of(1f)),
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(7))
.setSpeed(Speed.of(1f)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(8))
));
// then
assertEquals(Speed.of(2f), subject.getTrackStatistics().getMaxSpeed());
}
@Test
public void copy_constructor() {
// given
@@ -41,12 +41,12 @@ import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.LocaleRule;
import de.dennisguse.opentracks.TimezoneRule;
/**
* Tests for {@link StringUtils}.
@@ -59,6 +59,9 @@ public class StringUtilsTest {
@Rule
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
@Rule
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
private final Context context = ApplicationProvider.getApplicationContext();
/**
@@ -99,51 +102,13 @@ public class StringUtilsTest {
}
@Test
public void testGetTime_fractional() {
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 352);
}
public void testParseTime() {
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T00:00:00.352").toInstant());
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T00:00:00.352Z").toInstant());
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T00:00:00.352+00:00").toInstant());
@Test
public void testGetTime_timezone() {
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01+01:00", 2010, 5, 4, 2, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01+10:30", 2010, 5, 3, 16, 32, 1, 0);
assertGetTime("2010-05-04T03:02:01-09:30", 2010, 5, 4, 12, 32, 1, 0);
assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0);
}
@Test
public void testGetTime_fractionalAndTimezone() {
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
assertGetTime("2010-05-04T03:02:01.47+00:00", 2010, 5, 4, 3, 2, 1, 470);
assertGetTime("2010-05-04T03:02:01.5791+03:00", 2010, 5, 4, 0, 2, 1, 579);
assertGetTime("2010-05-04T03:02:01.8-05:30", 2010, 5, 4, 8, 32, 1, 800);
}
/**
* Asserts the {@link StringUtils#parseTime(String)} returns the expected values.
*
* @param xmlDateTime the xml date time string
* @param year the expected year
* @param month the expected month
* @param day the expected day
* @param hour the expected hour
* @param minute the expected minute
* @param second the expected second
* @param millisecond the expected milliseconds
*/
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute, int second, int millisecond) {
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.set(year, month - 1, day, hour, minute, second);
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
// This comparision tends to be flaky (difference of 1ms)
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime).toInstant().toEpochMilli()) <= 1);
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T01:00:00.352+01:00").toInstant());
assertEquals(Instant.ofEpochMilli(352).plus(Duration.ofHours(1)), StringUtils.parseTime("1970-01-01T00:00:00.352-01:00").toInstant());
}
@Test
+45
View File
@@ -0,0 +1,45 @@
<?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 http://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>2021-01-07T21:51:59.179+01:15</time>
<name><![CDATA[Marker 1]]></name>
<desc><![CDATA[Marker 1 desc]]></desc>
<type><![CDATA[Marker 1 category]]></type>
</wpt>
<trk>
<name><![CDATA[2021-01-07 22:51]]></name>
<desc><![CDATA[the description]]></desc>
<type><![CDATA[the category]]></type>
<extensions>
<topografix:color>c0c0c0</topografix:color>
<opentracks:trackid>7002101e-4198-4613-8c24-544e01ca3981</opentracks:trackid>
<gpxtrkx:TrackStatsExtension>
<gpxtrkx:Distance>0.0</gpxtrkx:Distance>
<gpxtrkx:TimerTime>0</gpxtrkx:TimerTime>
<gpxtrkx:MovingTime>0</gpxtrkx:MovingTime>
<gpxtrkx:StoppedTime>0</gpxtrkx:StoppedTime>
<gpxtrkx:MaxSpeed>0.0</gpxtrkx:MaxSpeed>
<gpxtrkx:Ascent>null</gpxtrkx:Ascent>
<gpxtrkx:Descent>null</gpxtrkx:Descent>
</gpxtrkx:TrackStatsExtension>
</extensions>
<trkseg>
<trkpt lat="3" lon="14">
<ele>10</ele>
<time>2021-01-07T21:51:59.179+01:15</time>
</trkpt>
</trkseg>
</trk>
</gpx>
@@ -90,18 +90,25 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
}
viewBinding.finishButton.setOnClickListener(v -> {
TrackUtils.updateTrack(TrackStoppedActivity.this, track, viewBinding.trackEditName.getText().toString(),
viewBinding.trackEditActivityType.getText().toString(), viewBinding.trackEditDescription.getText().toString(),
contentProviderUtils);
storeTrackMetaData(contentProviderUtils, track);
ExportUtils.postWorkoutExport(this, trackId);
finish();
});
viewBinding.resumeButton.setOnClickListener(v -> resumeTrackAndFinish());
viewBinding.resumeButton.setOnClickListener(v -> {
storeTrackMetaData(contentProviderUtils, track);
resumeTrackAndFinish();
});
viewBinding.discardButton.setOnClickListener(v -> ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackId));
}
private void storeTrackMetaData(ContentProviderUtils contentProviderUtils, Track track) {
TrackUtils.updateTrack(TrackStoppedActivity.this, track, viewBinding.trackEditName.getText().toString(),
viewBinding.trackEditActivityType.getText().toString(), viewBinding.trackEditDescription.getText().toString(),
contentProviderUtils);
}
@Override
public void onBackPressed() {
if (isDiscarding) {
@@ -188,9 +188,9 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
}
}
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed) {
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
if (isResumed()) {
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, chartByDistance, viewBinding.chartView.getUnitSystem());
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, trackPoint.getSpeed(), chartByDistance, viewBinding.chartView.getUnitSystem());
pendingPoints.add(point);
}
}
@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.tables.TracksColumns;
@@ -72,9 +73,7 @@ public class ShareContentProvider extends CustomContentProvider {
* @return An URI for one file containing this tracks.
*/
public static Pair<Uri, String> createURI(Track.Id trackId, String trackName, @NonNull TrackFileFormat trackFileFormat) {
Set<Track.Id> trackIds = new HashSet<>(1);
trackIds.add(trackId);
return createURI(trackIds, trackName, trackFileFormat);
return createURI(Set.of(trackId), trackName, trackFileFormat);
}
/**
@@ -108,11 +107,7 @@ public class ShareContentProvider extends CustomContentProvider {
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
Set<Track.Id> trackIds = new HashSet<>();
for (String uriTrackId : uriTrackIds) {
trackIds.add(new Track.Id(Long.parseLong(uriTrackId)));
}
return trackIds;
return Arrays.stream(uriTrackIds).map(uriTrackId -> new Track.Id(Long.parseLong(uriTrackId))).collect(Collectors.toSet());
}
/**
@@ -25,14 +25,12 @@ import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
@@ -367,7 +365,7 @@ public class TrackDataHub {
// Also include the last point if the selected track is not recording.
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
for (Listener trackDataListener : listeners) {
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics(), currentUpdater.getSmoothedSpeed());
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics());
}
} else {
for (Listener trackDataListener : listeners) {
@@ -432,7 +430,7 @@ public class TrackDataHub {
*
* @param trackPoint the trackPoint
*/
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed) {
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
}
/**
@@ -56,7 +56,7 @@ public class TrackPoint {
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
TRACKPOINT(0), //Just GPS data and may contain BLE sensor data
SENSORPOINT(2), //Just BLE sensor data; required to have speed and sensorDistance
SENSORPOINT(2), //Just BLE sensor data; may have speed and sensorDistance
SEGMENT_END_MANUAL(1); //End of a segment
@@ -24,6 +24,7 @@ public class UnitConversions {
// Time //TODO Use Duration
// multiplication factor to convert seconds to milliseconds
@Deprecated
public static final long S_TO_MS = 1000;
// multiplication factor to convert milliseconds to seconds
@@ -146,13 +146,10 @@ public class GPXTrackExporter implements TrackExporter {
switch (trackPoint.getType()) {
case SEGMENT_START_MANUAL:
if (wroteSegment) writeCloseSegment();
writeOpenSegment();
wroteSegment = true;
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
break;
case SEGMENT_END_MANUAL:
writeCloseSegment();
if (wroteSegment) writeCloseSegment();
wroteSegment = false;
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_END_MANUAL.name() + " is not supported.");
break;
@@ -2,26 +2,57 @@ package de.dennisguse.opentracks.publicapi;
import android.os.Bundle;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.TrackUtils;
public class StartRecording extends AbstractAPIActivity {
public static final String EXTRA_TRACK_NAME = "TRACK_NAME";
public static final String EXTRA_TRACK_CATEGORY = "TRACK_CATEGORY";
public static final String EXTRA_TRACK_DESCRIPTION = "TRACK_DESCRIPTION";
public static final String EXTRA_STATS_TARGET_PACKAGE = "STATS_TARGET_PACKAGE";
public static final String EXTRA_STATS_TARGET_CLASS = "STATS_TARGET_CLASS";
private static final String TAG = StartRecording.class.getSimpleName();
protected void execute(TrackRecordingService service) {
Track.Id trackId = service.startNewTrack();
if ((trackId != null) && (PreferencesUtils.isPublicAPIDashboardEnabled())) {
if (trackId != null) {
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
String targetPackage = bundle.getString("STATS_TARGET_PACKAGE", null);
String targetClass = bundle.getString("STATS_TARGET_CLASS", null);
IntentDashboardUtils.startDashboard(this, true, targetPackage, targetClass, trackId);
updateTrackMetadata(trackId, bundle);
if (PreferencesUtils.isPublicAPIDashboardEnabled()) {
startDashboardAPI(trackId, bundle);
}
}
}
}
private void updateTrackMetadata(@NonNull Track.Id trackId, @NonNull Bundle bundle) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Track track = contentProviderUtils.getTrack(trackId);
TrackUtils.updateTrack(this, track, bundle.getString(EXTRA_TRACK_NAME, null),
bundle.getString(EXTRA_TRACK_CATEGORY, null), bundle.getString(EXTRA_TRACK_DESCRIPTION, null),
contentProviderUtils);
}
private void startDashboardAPI(@NonNull Track.Id trackId, @NonNull Bundle bundle) {
String targetPackage = bundle.getString(EXTRA_STATS_TARGET_PACKAGE, null);
String targetClass = bundle.getString(EXTRA_STATS_TARGET_CLASS, null);
if (targetClass != null && targetPackage != null) {
IntentDashboardUtils.startDashboard(this, true, targetPackage, targetClass, trackId);
}
}
@Override
protected boolean isPostExecuteStopService() {
return false;
@@ -5,12 +5,15 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.concurrent.TimeUnit;
import de.dennisguse.opentracks.data.models.TrackPoint;
/**
@@ -29,7 +32,7 @@ public class AltitudeSumManager implements SensorEventListener {
private Float altitudeGain_m;
private Float altitudeLoss_m;
public void start(Context context) {
public void start(Context context, Handler handler) {
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
@@ -37,7 +40,7 @@ public class AltitudeSumManager implements SensorEventListener {
Log.w(TAG, "No pressure sensor available.");
isConnected = false;
} else {
isConnected = sensorManager.registerListener(this, pressureSensor, SensorManager.SENSOR_DELAY_FASTEST);
isConnected = sensorManager.registerListener(this, pressureSensor, (int) TimeUnit.SECONDS.toMicros(5), handler);
}
lastAcceptedPressureValue_hPa = Float.NaN;
@@ -10,6 +10,7 @@ import android.util.Pair;
import androidx.annotation.NonNull;
import java.time.Duration;
import java.time.ZoneOffset;
import de.dennisguse.opentracks.R;
@@ -180,8 +181,16 @@ class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChang
}
if (!trackPoint.hasLocation() && !trackPoint.hasSensorDistance()) {
Log.d(TAG, "Ignoring TrackPoint as it has no distance.");
return false;
Duration minStorageInterval = Duration.ofSeconds(10); // TODO Should be configurable.
boolean shouldStore = lastStoredTrackPoint.getTime().plus(minStorageInterval)
.isBefore(trackPoint.getTime());
if (!shouldStore) {
Log.d(TAG, "Ignoring TrackPoint as it has no distance (and sensor data is not new enough).");
return false;
} else {
insertTrackPoint(trackPoint, true);
return true;
}
}
Distance distanceToLastStoredTrackPoint;
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.services.announcement;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.speech.tts.TextToSpeech;
@@ -34,8 +35,8 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
@@ -56,6 +57,10 @@ public class VoiceAnnouncement {
private final AudioManager audioManager;
private final ContentProviderUtils contentProviderUtils;
private TrackPoint.Id startTrackPointId = null;
private IntervalStatistics intervalStatistics;
private Distance intervalDistance;
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
@@ -116,6 +121,8 @@ public class VoiceAnnouncement {
this.context = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
contentProviderUtils = new ContentProviderUtils(context);
intervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
intervalStatistics = new IntervalStatistics(intervalDistance);
}
public void start() {
@@ -132,7 +139,10 @@ public class VoiceAnnouncement {
if (ttsFallback == null) {
Log.w(TAG, "MediaPlayer for ttsFallback could not be created.");
} else {
ttsFallback.setAudioStreamType(AUDIO_STREAM);
ttsFallback.setAudioAttributes(
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build());
ttsFallback.setLooping(false);
}
}
@@ -165,21 +175,22 @@ public class VoiceAnnouncement {
return;
}
Distance currentIntervalDistance = PreferencesUtils.getVoiceAnnouncementDistance();
if (currentIntervalDistance != intervalDistance) {
intervalStatistics = new IntervalStatistics(currentIntervalDistance);
intervalDistance = currentIntervalDistance;
startTrackPointId = null;
}
UnitSystem unitSystem = PreferencesUtils.getUnitSystem();
boolean isReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
//TODO Do not load all trackpoints for every announcement
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.one(unitSystem));
intervalStatistics.addTrackPoints(trackPointIterator);
TrackPointIterator trackPointIterator = new TrackPointIterator(contentProviderUtils, track.getId(), startTrackPointId);
startTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator);
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
SensorStatistics sensorStatistics = null;
if (track.getId() != null) {
sensorStatistics = contentProviderUtils.getSensorStats(track.getId());
}
Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), unitSystem, isReportSpeed, lastInterval, sensorStatistics);
Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track.getCategory()), lastInterval, sensorStatistics);
if (announcement.length() > 0) {
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
@@ -58,7 +58,7 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
altitudeSumManager = new AltitudeSumManager();
remoteSensorManager.start();
altitudeSumManager.start(context);
altitudeSumManager.start(context, handler);
}
@@ -4,6 +4,7 @@ import android.os.Bundle;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R;
public class PublicAPISettingsFragment extends PreferenceFragmentCompat {
@@ -17,5 +18,7 @@ public class PublicAPISettingsFragment extends PreferenceFragmentCompat {
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_api_title);
findPreference(getString(R.string.publicapi_package_key))
.setSummary(BuildConfig.APPLICATION_ID);
}
}
@@ -1,138 +0,0 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.stats;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
/**
* This class maintains a ring buffer of doubles.
* This buffer is a convenient class for storing a series of doubles and calculating information about them.
* This is a FIFO buffer.
*
* @author Sandor Dornbush
*/
abstract class RingBuffer<T> {
private final ArrayList<T> buffer;
// The location that the next write will occur at.
private int index;
// True if the buffer is full
private boolean isFull;
/**
* Creates a buffer with a certain size.
*
* @param size the size
*/
RingBuffer(int size) {
if (size < 1) {
throw new IllegalArgumentException("The buffer size must be greater than 1.");
}
buffer = new ArrayList<T>(size);
reset();
}
RingBuffer(RingBuffer<T> toCopy) {
this.buffer = new ArrayList<>(toCopy.buffer);
this.index = toCopy.index;
this.isFull = toCopy.isFull;
}
/**
* Resets the buffer.
*/
public void reset() {
index = 0;
isFull = false;
}
/**
* Returns true if the buffer is full.
*/
boolean isFull() {
return isFull;
}
/**
* Gets the average of the buffer.
*/
public T getAverage() {
int numberOfEntries = isFull ? buffer.size() : index;
if (numberOfEntries == 0) {
return null;
}
Double sum = null;
int numberOfUsedEntries = 0;
for (int i = 0; i < numberOfEntries; i++) {
Number value = from(buffer.get(i));
if (value != null) {
if (sum == null) {
sum = 0.0;
}
sum += value.doubleValue();
numberOfUsedEntries++;
}
}
if (sum == null) {
return null;
} else {
return to(sum / numberOfUsedEntries);
}
}
@Nullable
protected abstract Number from(T object);
protected abstract T to(double object);
/**
* Adds a double to the buffer.
* If the buffer is full the oldest element is overwritten.
*
* @param value the double to add
*/
public void setNext(T value) {
if (index == buffer.size()) {
index = 0;
}
buffer.add(index, value);
index++;
if (index == buffer.size()) {
isFull = true;
}
}
@NonNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Full: ");
builder.append(isFull);
builder.append("\n");
for (int i = 0; i < buffer.size(); i++) {
builder.append((i == index) ? "<<" : "[");
builder.append(buffer.get(i));
builder.append((i == index) ? ">> " : "] ");
}
return builder.toString();
}
}
@@ -1,27 +0,0 @@
package de.dennisguse.opentracks.stats;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.data.models.Speed;
public class SpeedRingBuffer extends RingBuffer<Speed> {
SpeedRingBuffer(int size) {
super(size);
}
SpeedRingBuffer(RingBuffer<Speed> toCopy) {
super(toCopy);
}
@Nullable
@Override
protected Number from(Speed object) {
return object.toMPS();
}
@Override
protected Speed to(double object) {
return Speed.of(object);
}
}
@@ -19,16 +19,15 @@ package de.dennisguse.opentracks.stats;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import java.util.List;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.models.UnitConversions;
/**
* Updater for {@link TrackStatistics}.
@@ -41,23 +40,15 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
*/
public class TrackStatisticsUpdater {
/**
* The number of speed reading to smooth to get a somewhat accurate signal.
*/
@VisibleForTesting
private static final int SPEED_SMOOTHING_FACTOR = 25;
private static final String TAG = TrackStatisticsUpdater.class.getSimpleName();
/**
* Ignore any acceleration faster than this.
* Will ignore any speeds that imply acceleration greater than 2g's
* 2g = 19.6 m/s^2 = 0.0002 m/ms^2 = 0.02 m/(m*ms)
*/
private static final double MAX_ACCELERATION = 0.02;
private static final double SPEED_MAX_ACCELERATION = 2 * 9.81;
private final TrackStatistics trackStatistics;
private final SpeedRingBuffer speedBuffer;
private float averageHeartRateBPM;
private Duration totalHeartRateDuration = Duration.ZERO;
@@ -79,7 +70,6 @@ public class TrackStatisticsUpdater {
this.trackStatistics = trackStatistics;
this.currentSegment = new TrackStatistics();
speedBuffer = new SpeedRingBuffer(SPEED_SMOOTHING_FACTOR);
resetAverageHeartRate();
}
@@ -87,8 +77,6 @@ public class TrackStatisticsUpdater {
this.currentSegment = new TrackStatistics(toCopy.currentSegment);
this.trackStatistics = new TrackStatistics(toCopy.trackStatistics);
this.speedBuffer = new SpeedRingBuffer(toCopy.speedBuffer);
this.lastTrackPoint = toCopy.lastTrackPoint;
resetAverageHeartRate();
}
@@ -165,11 +153,8 @@ public class TrackStatisticsUpdater {
// Update max speed
updateSpeed(trackPoint, lastTrackPoint);
} else {
speedBuffer.reset();
}
if (trackPoint.isSegmentEnd()) {
reset(trackPoint);
return;
@@ -185,7 +170,6 @@ public class TrackStatisticsUpdater {
currentSegment.reset(trackPoint.getTime());
lastTrackPoint = null;
speedBuffer.reset();
resetAverageHeartRate();
}
@@ -194,22 +178,14 @@ public class TrackStatisticsUpdater {
totalHeartRateDuration = Duration.ZERO;
}
public Speed getSmoothedSpeed() {
return speedBuffer.getAverage();
}
/**
* Updates a speed reading while assuming the user is moving.
*/
@VisibleForTesting
private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
if (!trackPoint.isMoving()) {
speedBuffer.reset();
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
speedBuffer.setNext(trackPoint.getSpeed());
Speed average = speedBuffer.getAverage();
if (average.greaterThan(currentSegment.getMaxSpeed())) {
currentSegment.setMaxSpeed(average);
if (isValidSpeed(trackPoint, lastTrackPoint)) {
Speed currentSpeed = trackPoint.getSpeed();
if (currentSpeed.greaterThan(currentSegment.getMaxSpeed())) {
currentSegment.setMaxSpeed(currentSpeed);
}
} else {
Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed());
@@ -217,30 +193,13 @@ public class TrackStatisticsUpdater {
}
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
if (trackPoint.getSpeed().isZero()) {
return false;
}
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Speed maxAcceleration = Speed.of(MAX_ACCELERATION * timeDifference.toMillis());
{
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed());
if (speedDifference.greaterThan(maxAcceleration)) {
return false;
}
}
Speed maxSpeedDifference = Speed.of(Distance.of(SPEED_MAX_ACCELERATION), Duration.ofMillis(1000))
.mul(timeDifference.toMillis() / UnitConversions.S_TO_MS);
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
if (speedBuffer.isFull()) {
Speed average = speedBuffer.getAverage();
Speed speedDifference = Speed.absDiff(average, trackPoint.getSpeed());
return trackPoint.getSpeed().lessThan(average.mul(10)) && speedDifference.lessThan(maxAcceleration);
}
return true;
Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed());
return speedDifference.lessThan(maxSpeedDifference);
}
@NonNull
@@ -49,8 +49,7 @@ public class IntervalStatistics {
interval.add(trackStatisticsUpdater.getTrackStatistics(), trackPoint);
double adjustFactor = distanceInterval.dividedBy(interval.distance);
Interval adjustedInterval = new Interval(interval);
adjustedInterval.adjust(adjustFactor);
Interval adjustedInterval = new Interval(interval, adjustFactor);
intervalList.set(intervalList.size() - 1, adjustedInterval);
@@ -116,7 +115,6 @@ public class IntervalStatistics {
public Interval(Interval i, double adjustFactor) {
distance = i.distance.multipliedBy(adjustFactor);
time = Duration.ofMillis((long) (i.time.toMillis() * adjustFactor));
time = i.time;
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
@@ -130,11 +128,6 @@ public class IntervalStatistics {
avgHeartRate = i.avgHeartRate;
}
private void adjust(double adjustFactor) {
distance = distance.multipliedBy(adjustFactor);
time = Duration.ofMillis((long) (time.toMillis() * adjustFactor));
}
public Distance getDistance() {
return distance;
}
@@ -21,8 +21,10 @@ import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
@@ -55,6 +57,8 @@ public class MarkerEditActivity extends AbstractActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String CAMERA_PHOTO_URI_KEY = "camera_photo_uri_key";
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private Track.Id trackId;
private Marker marker;
@@ -86,6 +90,10 @@ public class MarkerEditActivity extends AbstractActivity {
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
if (savedInstanceState != null) {
cameraPhotoUri = Uri.parse(savedInstanceState.getString(CAMERA_PHOTO_URI_KEY, ""));
}
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
// Setup UI elements
@@ -165,6 +173,14 @@ public class MarkerEditActivity extends AbstractActivity {
takePictureFromCamera = null;
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (cameraPhotoUri != null) {
outState.putString(CAMERA_PHOTO_URI_KEY, cameraPhotoUri.toString());
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.marker_edit, menu);
@@ -232,7 +248,13 @@ public class MarkerEditActivity extends AbstractActivity {
}
private void createMarkerWithGalleryImage() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 1);
} else {
intent = new Intent(Intent.ACTION_GET_CONTENT);
}
intent.setType("image/*");
takePictureFromGallery.launch(intent);
}
@@ -1,12 +1,15 @@
package de.dennisguse.opentracks.util;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import java.util.ArrayList;
@@ -136,7 +139,7 @@ public class IntentDashboardUtils {
* @param targetClass the target class
* @param trackIds the track ids
*/
public static void startDashboard(Context context, boolean isRecording, String targetPackage, String targetClass, Track.Id... trackIds) {
public static void startDashboard(Context context, boolean isRecording, @Nullable String targetPackage, @Nullable String targetClass, Track.Id... trackIds) {
if (trackIds.length == 0) {
return;
}
@@ -166,14 +169,19 @@ public class IntentDashboardUtils {
clipData.addItem(new ClipData.Item(uris.get(MARKERS_URI_INDEX)));
intent.setClipData(clipData);
if ((targetPackage != null) && (targetClass != null)) {
if (targetPackage != null && targetClass != null) {
Log.i(TAG, "Starting dashboard activity with explicit intent (package=" + targetPackage + ", class=" + targetClass + ")");
intent.setClassName(targetPackage, targetClass);
} else {
Log.i(TAG, "Starting dashboard activity with generic intent (package=" + targetPackage + ", class=" + targetClass + ")");
}
context.startActivity(intent);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Dashboard not installed; cannot start it.");
Toast.makeText(context, R.string.show_on_dashboard_not_installed, Toast.LENGTH_SHORT).show();
}
}
/**
+7 -6
View File
@@ -15,14 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
--><resources>
<string name="about_preference_title">O OpenTracks</string>
<string name="about_description"><i> OpenTracks </i> je sportovní sledovací aplikace, která zcela respektuje vaše soukromí.</string>
<string name="about_description"><i> OpenTracks </i> je aplikace pro záznam vašich sportů, která zcela respektuje vaše soukromí.</string>
<string name="about_url">Webová stránka projektu: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Název verze: %1$s</string>
<string name="about_version_code">Kód verze: %1$d</string>
<string name="about_history">OpenTracks je fork aplikace <a href="https://code.google.com/archive/p/mytracks/"> <i> MyTracks Google </i> </a> (v roce 2016 ukončena).</string>
<string name="about_history">OpenTracks je fork aplikace <a href="https://code.google.com/archive/p/mytracks/"> <i> MyTracks Google </i> </a> (ukončena v roce 2016).</string>
<string name="about_libraries">Knihovny</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks má následující funkce:</string>
<string name="help_feature_title">OpenTracks dokáže:</string>
<string name="help_feature_content">* vytvářet GPS stopy
\n* použít snímač srdeční frekvence (pouze Bluetooth LE)
\n* hlasová oznámení (podle času nebo vzdálenosti)
@@ -34,14 +34,14 @@ limitations under the License.
\n a) (živé aktualizace) Nainstalujte <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Poskytuje online nebo offline mapy prostřednictvím <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n Ve skutečnosti je OSMDashboard samostatná aplikace, která má přístup (dočasně) pouze k datům stop, které se budou zobrazovat.
\n b) Prostřednictvím „normální“ mapové aplikace (např. <a href="http://osmand.net/">OsmAnd</a> a <a href="https://maps.me/">MAPS.ME</a>). Tyto aplikace musí podporovat formát souboru KMZ (pouze umístění a časová razítka). Ve skutečnosti musí aplikace podporovat <i>ACTION_VIEW</i> pomocí MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_storage_pictures_title">Jak ukládá OpenTracks obrázky pořízené jako značky?</string>
<string name="help_storage_pictures_title">Jak ukládá OpenTracks obrázky pořízené jako značky na cestě\?</string>
<string name="help_storage_pictures_content">Jsou uloženy v interním úložišti aplikace (tj. přístupné pouze v rámci OpenTracks).</string>
<string name="help_track_export_title">Jak mohu exportovat/sdílet zaznamenané stopy\?</string>
<string name="help_track_export_content"> Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. KMZ a KML obsahuje všechna data, GPX pouze umístění. Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. </string>
<string name="help_track_export_content"> Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. KMZ a KML obsahuje všechna data, GPX pouze polohu. Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. </string>
<string name="help_data_google_play_services_title">Vyžaduje/používá OpenTracks službu Google Play\?</string>
<string name="help_recording_title">Jak OpenTracks zaznamenává data?</string>
<string name="help_recording_content">OpenTracks používá GPS senzor zařízení k nepřetržitému zaznamenávání polohy. Pokud zařízení GPS hlásí nepřesná data (např. polohu, rychlost, nadmořskou výšku), OpenTracks s tím nemůže nic udělat.</string>
<string name="help_data_storage_title">Jak OpenTracks ukládá data senzorů?</string>
<string name="help_data_storage_title">Jak OpenTracks ukládá data ze senzorů\?</string>
<string name="help_data_storage_content"> Data senzoru se ukládají na zaznamenané místo. Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i> jedno </i> měření srdeční frekvence. Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). </string>
<string name="help_gps_troubleshooting_title">OpenTracks nezaznamenává místa?</string>
<string name="help_gps_troubleshooting_content"> Co můžete udělat:
@@ -574,4 +574,5 @@ limitations under the License.
<string name="settings_public_api_dashboard_enabled_summary_on">Aplikace, která zahájila nahrávání, může také přistupovat k nahraným datům.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Zaznamenaná data nebudou automaticky sdílena s jinými aplikacemi.</string>
<string name="pace">Tempo</string>
<string name="track_discarding">Mazání trasy</string>
</resources>
+3
View File
@@ -575,4 +575,7 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="report_bug">Fehler melden</string>
<string name="settings_ui_summary">Erscheinungsbild, Aufzeichnungslayout, Theme</string>
<string name="settings_gps_summary">Zeitintervall, Entfernungsintervall, Max Entfernung, Genauigkeit</string>
<string name="description_speed_nautical">Geschwindigkeit (Knoten)</string>
<string name="settings_open_tracks_title">Open Tracks</string>
<string name="gps_recording_status">%1$s: %2$s</string>
</resources>
+19
View File
@@ -587,4 +587,23 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="settings_announcements_lap_heart_rate">Fréquence cardiaque au tour</string>
<string name="image_discard">Ignorer</string>
<string name="track_discarding">Ignorer la trace</string>
<string name="description_pace_nautical">Allure (min/MN)</string>
<string name="show_on_dashboard_not_installed">Il n\'y a pas d\'application installé supportant l\'API OpenTracks Dashboard.</string>
<string name="description_speed_nautical">Vitesse (nœuds)</string>
<string name="settings_stats_units_nautical">Nautique (MN, pieds)</string>
<string name="unit_nautical_mile">MN</string>
<string name="unit_knots">kn</string>
<string name="unit_minute_per_nautical_mile">min/MN</string>
<string name="value_integer_knots">%1$d nœuds</string>
<string name="value_float_knots">%1$.1f nœuds</string>
<string name="value_float_knots_recommended">%1$.1f nœuds (recommandé)</string>
<string name="voice_per_nautical_mile">par mile nautique</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">%1$.1f nœud</item>
<item quantity="other">%1$.1f nœuds</item>
</plurals>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">%1$.2f mile nautique</item>
<item quantity="other">%1$.2f milles nautique</item>
</plurals>
</resources>
+32
View File
@@ -580,4 +580,36 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">OpenTracks está gravando sen datos GPS.</string>
<string name="settings_export_filename_title">Formato do nome do ficheiro</string>
<string name="settings_announcements_lap_heart_rate">Ritmo cardíaco na volta</string>
<string name="settings_announcements_average_heart_rate">Ritmo cardíaco medio</string>
<string name="image_discard">Descartar</string>
<string name="settings_announcements_average_speed_pace">Velocidade/ritmo medio</string>
<string name="settings_announcements_lap_speed_pace">Velocidade/ritmo da volta</string>
<string name="track_discarding">Desbotando Ruta</string>
<string name="share_crash_report">Comparte o informe do fallo</string>
<string name="description_pace_nautical">Ritmo (min/NM)</string>
<string name="description_speed_nautical">Velocidade (nós)</string>
<string name="settings_stats_units_nautical">Náutica (NM, ft)</string>
<string name="unit_nautical_mile">NM</string>
<string name="unit_knots">kn</string>
<string name="unit_minute_per_nautical_mile">min/NM</string>
<string name="value_integer_knots">%1$d nós</string>
<string name="value_float_knots">%1$.1f nós</string>
<string name="value_float_knots_recommended">%1$.1f nós (recomendado)</string>
<string name="voice_per_nautical_mile">por milla náutica</string>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">1 milla náutica</item>
<item quantity="other">%1$.2f millas náuticas</item>
</plurals>
<string name="show_on_dashboard">Taboleiro da API OpenTracks</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">1 nó</item>
<item quantity="other">%1$.1f nós</item>
</plurals>
<string name="show_on_dashboard_not_installed">Non hai ningunha aplicación instalada con soporte para a API do taboleiro OpenTracks.</string>
<string name="select_show_on_map_behavior">Elixe \"ver no mapa\"</string>
<string name="show_on_map_format_ask">Preguntar</string>
<string name="always">Sempre</string>
<string name="error_crash_title">%1$s fallou</string>
<string name="report_bug">Informar do problema</string>
</resources>
+32 -7
View File
@@ -206,10 +206,10 @@ limitations under the License.
\n%1$s</string>
<string name="share_track_subject">Jeg vil dele et spor med deg</string>
<!-- Stats -->
<string name="stats_average_moving_pace">Gjennomsnittlig forflytningstempo</string>
<string name="stats_average_moving_speed">Gjennomsnittlig forflytningshastighet</string>
<string name="stats_average_pace">Gjennomsnittlig tempo</string>
<string name="stats_average_speed">Gjennomsnittlig hastighet</string>
<string name="stats_average_moving_pace">Gj. forflytningstempo</string>
<string name="stats_average_moving_speed">Gj. forflytningshastighet</string>
<string name="stats_average_pace">Gj. tempo</string>
<string name="stats_average_speed">Gj. hastighet</string>
<string name="stats_altitude">Høyde</string>
<string name="stats_fastest_pace">Raskeste tempo</string>
<string name="stats_gain">Total stigning</string>
@@ -449,11 +449,11 @@ limitations under the License.
<string name="generic_open_track">Åpne spor</string>
<string name="import_progress_summary_exists_msg">Finnes allerede</string>
<string name="import_progress_summary_errors_msg">Feil</string>
<string name="sensor_state_cadence_avg">Gjennomsnittlig kadens</string>
<string name="sensor_state_cadence_avg">Gj. kadens</string>
<string name="sensor_state_cadence_max">Maks kadens</string>
<string name="sensor_state_heart_rate_avg">Gjennomsnittlig puls</string>
<string name="sensor_state_heart_rate_avg">Gj. puls</string>
<string name="sensor_state_heart_rate_max">Maks puls</string>
<string name="sensor_state_power_avg">Gjennomsnittlig kraft</string>
<string name="sensor_state_power_avg">Gj. kraft</string>
<string name="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string>
<string name="track_distance_notification">Distanse: %1$s</string>
<string name="track_speed_notification">Hastighet: %1$s</string>
@@ -575,4 +575,29 @@ limitations under the License.
<string name="show_on_map_format_ask">Spør</string>
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
<string name="always">Alltid</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_average_speed_pace">Gjennomsnittlig hastighet/tempo</string>
<string name="image_discard">Forkast</string>
<string name="settings_announcements_lap_speed_pace">Runde-hastighet/tempo</string>
<string name="description_pace_nautical">Tempo (min/NM)</string>
<string name="description_speed_nautical">Hastighet (knop)</string>
<string name="settings_stats_units_nautical">Nautisk (NM, ft)</string>
<string name="track_discarding">Forkaster spor</string>
<string name="unit_nautical_mile">NM</string>
<string name="unit_knots">kn</string>
<string name="unit_minute_per_nautical_mile">min/NM</string>
<string name="value_integer_knots">%1$d knop</string>
<string name="value_float_knots">%1$.1f knop</string>
<string name="value_float_knots_recommended">%1$.1f knop (anbefales)</string>
<string name="voice_per_nautical_mile">per nautisk mil</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">1 knop</item>
<item quantity="other">%1$.1f knop</item>
</plurals>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">1 nautisk mil</item>
<item quantity="other">%1$.2f nautiske mil</item>
</plurals>
<string name="show_on_dashboard_not_installed">Ingen app som støtter OpenTracks Dashboard API er installert.</string>
</resources>
+24
View File
@@ -336,4 +336,28 @@ limitations under the License.
<string name="waypoint_type_waterfall">водопад</string>
<string name="waypoint_type_water_fountain">фонтана</string>
<string name="waypoint_type_waypoint">тачка пута</string>
<string name="about_url">Web stranica projekta: <a href="%1$s">%1$s</a></string>
<string name="help_feature_title">OtvorenaPraćenja ima sledeće funkcije:</string>
<string name="help_storage_pictures_title">Kako OtvorenaPraćenja čuva fotografije tamo gde su postavljeni markeri\?</string>
<string name="about_history">OtvorenaPraćenja je kopija od <a href="https://code.google.com/archive/p/mytracks/"><i>Guglove MojaPraćenja aplikacije</i></a> (zaustavljene u 2016).</string>
<string name="help_track_map_title">Kako da se pokaže praćenja na mapi\?</string>
<string name="help_track_map_content"> Koristi ikonicu male mape.
\nA) (apdejti uživo) Instalirajte
\n<a href="https://github.com/OpenTracksApp/OSMDashboard">OSMPočetna</a>. Izvodi onlajn mape ili oflajn preko <a href="https://www.openstreetmap.org">OtvorenihMapaZaUlice</a>.
\nZapravo, OSMPočtna je pojedina aplikacija koja može pristupiti (privremeno) podacima o praćenjima koji se prikazuju.
\nB) Sa normalnim aplikacijama za mape (npr. <a href="http://osmand.net/">OSMAnd</a> i <a href="https://maps.me/">MOJE.MAPE</a>). Te aplikacije imaju podršku za KMZ fajl format (lokacije i tajmstampovi). U temi, aplikacija mora da podrži <i>ACTION_VIEW</i> sa MIME-om <i>application/vnd.google-earth.kmz</i>. </string>
<string name="about_version_code">Kod verzije: %1$d</string>
<string name="about_libraries">Biblioteke</string>
<string name="about_version_name">Ime verzije: %1$s</string>
<string name="help_feature_content">* pravi praćenja o lokaciji
\n* koristi senzor za merenje srca (samo Blutut LE)
\n* predstavljanje glasa (od vremena ili distance)
\n* pravi markere (uključujući slike)
\n* radi potpuno bez interneta
\n* pokazuje praćenja na mapi (vidi dole)</string>
<string name="about_description"><i>OtvorenaPraćenja</i> je aplikacija za praćenje sporta koja potpuno pazi o vašoj privatnosti.</string>
<string name="about_third_party_data">Informacije od trećih strana</string>
<string name="help_track_export_title">Kako ja mogu da izvezem/podelim sačuvand podatke\?</string>
<string name="about_preference_title">O OtvorenimPraćenjima</string>
<string name="help_storage_pictures_content">Oni su sačuvani u aplikaciji u internoj memoriji (samo pristupnoj bez OtvorenihPraćenja).</string>
</resources>
+24 -4
View File
@@ -462,7 +462,7 @@ limitations under the License.
<string name="permission_gps_failed">OpenTracks GPS kullanmak için izin gerektirir.</string>
<string name="import_progress_summary_ok_msg">Dosyalar içe aktarıldı</string>
<string name="settings_defaults_title">Varsayılan Birimler ve Etkinlik</string>
<string name="settings_ui_title">Kullanıcı arayüzü</string>
<string name="settings_ui_title">Kullanıcı Arayüzü</string>
<string name="settings_default_trackfileformat">Dosya biçimini dışa aktarma/paylaşma</string>
<string name="settings_export_filename_title">Dosya adının biçimi</string>
<string name="settings_import_export_title">İçe/Dışa Aktar</string>
@@ -477,7 +477,7 @@ limitations under the License.
<string name="settings_recording_keep_screen_on_while_recording_summary">Kayıt esnasında ekranı açık tutar.</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Kayıt esnasında tam ekranda sunar.</string>
<string name="settings_recording_idle_speed_title">Boşta hız eşiği</string>
<string name="settings_night_mode_title">Kullanıcı Arayüzü Teması</string>
<string name="settings_night_mode_title">Kullanıcı arayüzü teması</string>
<string name="settings_night_mode_option_system">Sistem</string>
<string name="settings_night_mode_option_night">Gece</string>
<string name="custom_layout_select_layout">Dizilim seçin</string>
@@ -487,8 +487,8 @@ limitations under the License.
<string name="settings_layout_reset_done">Tüm dizilim ayarları varsayılan değerlere döndürüldü</string>
<string name="settings_public_api_enabled_title">Genel API</string>
<string name="settings_public_api_enabled_summary_on">Diğer yüklü uygulamalar kayıtları başlatabilir veya durdurabilir.</string>
<string name="settings_import_section">İçe aktar</string>
<string name="settings_export_section">Dışa aktar</string>
<string name="settings_import_section">İçe Aktarma</string>
<string name="settings_export_section">Dışa Aktarma</string>
<string name="settings_prevent_reimport_tracks_title">Parçaların yeniden içe aktarılmasını önleme</string>
<string name="stats_coordinates">Enlem/Boylam</string>
<string name="stats_sensors_power">Güç</string>
@@ -571,4 +571,24 @@ limitations under the License.
<string name="report_bug">Hata bildir</string>
<string name="error_crash_title">%1$s çöktü</string>
<string name="select_show_on_map_behavior">\"Haritada göster\" eylemini seçin</string>
<string name="settings_announcements_average_heart_rate">Ortalama kalp atış hızı</string>
<string name="description_pace_nautical">Tempo (dk/NM)</string>
<string name="description_speed_nautical">Hız (düğüm)</string>
<string name="settings_announcements_average_speed_pace">Ortalama hız/tempo</string>
<string name="settings_announcements_lap_speed_pace">Tur hızı/temposu</string>
<string name="settings_stats_units_nautical">Deniz (NM, ft)</string>
<string name="unit_minute_per_nautical_mile">dk/NM</string>
<string name="value_integer_knots">%1$d düğüm</string>
<string name="value_float_knots">%1$.1f düğüm</string>
<string name="value_float_knots_recommended">%1$.1f düğüm (önerilen)</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">1 düğüm</item>
<item quantity="other">%1$.1f düğüm</item>
</plurals>
<string name="voice_per_nautical_mile">deniz mili başına</string>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">1 deniz mili</item>
<item quantity="other">%1$.2f deniz mili</item>
</plurals>
<string name="image_discard"></string>
</resources>
+1
View File
@@ -314,6 +314,7 @@
<bool name="import_prevent_reimport_default">true</bool>
<string name="settings_api_key" translatable="false">settingsAPI</string>
<string name="publicapi_package_key" translatable="false">apiPackageKey</string>
<string name="publicapi_enabled_key" translatable="false">apiEnabledKey</string>
<bool name="publicapi_enabled_default">false</bool>
<string name="publicapi_dashboard_enabled_key" translatable="false">apiDashboardEnabledKey</string>
+2
View File
@@ -442,6 +442,7 @@ limitations under the License.
<!-- Settings public api -->
<string name="settings_api_title">Public API</string>
<string name="settings_public_api_package_title">Package name for Public API</string>
<string name="settings_public_api_enabled_title">Public API</string>
<string name="settings_public_api_enabled_summary_on">Other installed applications can start or stop recordings.</string>
<string name="settings_public_api_enabled_summary_off">Only OpenTracks can start and stop recordings.</string>
@@ -666,5 +667,6 @@ limitations under the License.
<string name="select_show_on_map_behavior">Select \"show on map\" behavior</string>
<string name="show_on_map_format_ask">Ask</string>
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
<string name="show_on_dashboard_not_installed">No application supporting OpenTracks Dashboard API installed.</string>
<string name="always">Always</string>
</resources>
+3 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/settings_gps_title">
<ListPreference
@@ -31,6 +32,7 @@
android:defaultValue="@string/idle_speed_default"
android:entryValues="@array/idle_speed_values"
android:key="@string/idle_speed_key"
android:title="@string/settings_recording_idle_speed_title" />
android:title="@string/settings_recording_idle_speed_title"
app:useSimpleSummaryProvider="true" />
</PreferenceScreen>
+6
View File
@@ -3,6 +3,12 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/settings_api_title">
<Preference
android:key="@string/publicapi_package_key"
android:selectable="false"
android:persistent="false"
android:title="@string/settings_public_api_package_title" />
<SwitchPreferenceCompat
android:title="@string/settings_public_api_enabled_title"
android:summaryOn="@string/settings_public_api_enabled_summary_on"