Compare commits

...

42 Commits

Author SHA1 Message Date
Dennis Guse 1ccd2e3c0a Release: v4.0.6 2022-08-07 19:47:54 +02:00
Dennis Guse cdbddabc1b Cleanup: use ContextCompat.getColor() 2022-08-04 22:11:44 +02:00
Dennis Guse 9f6ca83f27 Bug: PublicAPI needs to call startForegroundService (SDK API26+).
Fixes #1301.
2022-08-04 22:11:44 +02:00
dependabot[bot] f2ee99a477 Bump gradle from 7.2.1 to 7.2.2
Bumps gradle from 7.2.1 to 7.2.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-04 06:44:02 +02:00
Dennis Guse 7009ede095 EGM2008 offset lookup for negative longitudes was wrong. 2022-08-03 17:30:31 +02:00
Dennis Guse 2b530a1c53 Cleanup: unused imports. 2022-08-02 18:44:15 +02:00
Poussinou 33673f5921 Update FUNDING.yml 2022-07-31 14:59:56 +02:00
Dennis Guse a7398d43e3 Release: v4.0.5 2022-07-29 17:58:32 +02:00
Dennis Guse 59b016a4a3 PlayStore: use an inverted icon (app launcher only).
Part of #1329.
2022-07-29 17:56:28 +02:00
Dennis Guse 78aa5d51c6 Intervals: if gain/loss is not available show "-" instead of null.
Fixes #1338.
2022-07-26 22:30:08 +02:00
Quang Trung 95f584b554 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (501 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2022-07-21 15:04:36 +02:00
Dennis Guse cf759d526d README_TESTED_SENSORS.md: we now store sensor data without movement.
Part of #1088.
2022-07-18 07:35:46 +02:00
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
70 changed files with 563 additions and 177 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
liberapay: OpenTracks
github: [rgmf, pstorch, dennisguse]
custom: [ https://liberapay.com/OpenTracks, https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore ]
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
@@ -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).
-4
View File
@@ -38,10 +38,6 @@ However, often only one value is provided.
We only support _Instantaneous Power_.
_WARNING_: we only support physically movements with valid GPS coordinates.
Using a device indoors (no GPS available) will only show the current power values, but won't store anything.
Also the distance is not computed.
For cycling:
* Assioma Duo Powermeter
* Elite drivo II
+7 -6
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.android.tools.build:gradle:7.2.2'
}
}
@@ -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 4951
versionName "v4.0.3"
versionCode 4993
versionName "v4.0.6"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdkVersion 21
targetSdkVersion 32
minSdk 21
targetSdk 33
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
@@ -100,6 +100,7 @@ android {
releasePlayStore {
applicationIdSuffix ".playstore"
versionNameSuffix "-PlayStore"
signingConfig signingConfigs.debug
}
}
@@ -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,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
@@ -0,0 +1,8 @@
v4.0.5: OpenTracks
Changes:
- PlayStore relase get's another logo
Bugfix:
- Interval list shows "-" if gain/loss is not present
@@ -0,0 +1,6 @@
v4.0.6: OpenTracks
Bugfix:
- EGM2008 offset computed wrong for negative longitudes
- PublicAPI crashed if OpenTracks was in background (API26+)
@@ -3,48 +3,25 @@ package de.dennisguse.opentracks;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
import static de.dennisguse.opentracks.util.EspressoUtils.withListSize;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.view.View;
import android.widget.ListView;
import androidx.test.espresso.IdlingRegistry;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.assertion.ViewAssertions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.services.TrackDeleteService;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoDeleteTrackTest {
@@ -4,7 +4,6 @@ import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@@ -15,6 +15,10 @@
*/
package de.dennisguse.opentracks.chart;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -25,10 +29,6 @@ import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests {@link ChartValueSeries}.
*
@@ -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;
/**
@@ -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() {
@@ -142,7 +142,7 @@ public class EGM2008UtilsTest {
}
@Test
public void data_Berlin() throws IOException {
public void data_Berlin_Germany_() throws IOException {
// given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint.setLatitude(52.530644);
@@ -157,7 +157,7 @@ public class EGM2008UtilsTest {
}
@Test
public void data_Berlin_Caching() throws IOException {
public void data_Berlin_Germany_Caching() throws IOException {
// given
TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint1.setLatitude(52.530644);
@@ -176,6 +176,22 @@ public class EGM2008UtilsTest {
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()), altitude_egm2008.correctAltitude(trackPoint2.getLocation()), 0.0001);
}
@Test
public void data_Seattle_USA() throws IOException {
// given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint.setLatitude(47.63153);
trackPoint.setLongitude(-122.30938);
trackPoint.setAltitude(0);
// when
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
// then
assertEquals(22.99, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
}
@Test
public void data_MaxUndulation() throws IOException {
// given
@@ -191,6 +207,7 @@ public class EGM2008UtilsTest {
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
}
@Test
public void getIndices() {
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
@@ -208,7 +225,7 @@ public class EGM2008UtilsTest {
trackPoint.setLatitude(-90);
trackPoint.setLongitude(-180);
assertEquals(new EGM2008Utils.Indices(2160, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
assertEquals(new EGM2008Utils.Indices(2160, 2160), EGM2008Utils.getIndices(trackPoint.getLocation()));
}
@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>
@@ -3,19 +3,15 @@ package de.dennisguse.opentracks;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding;
import de.dennisguse.opentracks.databinding.TrackListBinding;
public class ShowErrorActivity extends AbstractActivity {
@@ -241,7 +241,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
startActivity(newIntent);
connection.unbind(this);
}).startAndBind(this);
}).startAndBind(this, true);
});
viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
if (!recordingStatus.isRecording()) {
@@ -323,8 +323,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0);
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text);
searchAutoComplete.setHintTextColor(getResources().getColor(android.R.color.white));
searchAutoComplete.setTextColor(getResources().getColor(android.R.color.white));
searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
searchView.setOnCloseListener(() -> {
searchView.clearFocus();
@@ -213,7 +213,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
connection.unbind(this);
finish();
}).startAndBind(this);
}).startAndBind(this, true);
return true;
}
@@ -11,8 +11,6 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.widget.Toast;
@@ -49,7 +47,6 @@ import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
@@ -138,7 +135,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24);
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setBackgroundColor(getResources().getColor(R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> {
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
@@ -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) {
@@ -138,7 +145,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
connection.unbind(this);
finish();
}).startAndBind(this);
}).startAndBind(this, true);
}
@Override
@@ -32,9 +32,7 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.TrackDataHub;
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;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.ChartBinding;
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -24,6 +24,7 @@ import android.graphics.Paint.Style;
import android.graphics.Path;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import java.text.NumberFormat;
@@ -68,12 +69,12 @@ abstract class ChartValueSeries {
fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(fillColor));
fillPaint.setColor(ContextCompat.getColor(context, fillColor));
fillPaint.setAntiAlias(true);
strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setColor(ContextCompat.getColor(context, strokeColor));
strokePaint.setAntiAlias(true);
// Make copies of the stroke paint with the default thickness
@@ -32,7 +32,6 @@ import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.data.models.Marker;
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.data.tables.MarkerColumns;
@@ -13,6 +13,14 @@ public class Distance {
return new Distance(distance_m);
}
public static Distance of(Double distance_m) {
if (distance_m == null) {
return Distance.of(Double.NaN);
} else {
return Distance.of((double) distance_m);
}
}
public static Distance of(String distance_m) {
return of(Float.parseFloat(distance_m));
}
@@ -42,12 +42,13 @@ public class DistanceFormatter {
*/
public Pair<String, String> getDistanceParts(Distance distance) {
if (distance.isInvalid()) {
String valueUnknown = resources.getString(R.string.value_unknown);
switch (unitSystem) {
case METRIC:
return new Pair<>(null, resources.getString(R.string.unit_meter));
return new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
case IMPERIAL:
case NAUTICAL_IMPERIAL:
return new Pair<>(null, resources.getString(R.string.unit_feet));
return new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
default:
throw new RuntimeException("Not implemented");
}
@@ -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
@@ -17,8 +17,8 @@ import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.IntentUtils;
public abstract class DirectoryChooserActivity extends AppCompatActivity {
@@ -34,7 +34,7 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
if (PreferencesUtils.isPublicAPIenabled()) {
Log.i(TAG, "Received and trying to execute requested action.");
new TrackRecordingServiceConnection(serviceConnectedCallback)
.startAndBind(this);
.startAndBind(this, true);
} else {
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
Log.w(TAG, "Public API is disabled; ignoring request.");
@@ -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;
@@ -129,7 +129,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
trackRecordingManager = null;
// Reverse order from onCreate
showNotification(false); //TODO Why?
stopForeground(true);
notificationManager.stop();
notificationManager = null;
@@ -205,7 +206,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private void startSensors() {
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
trackPointCreator.start(this, handler);
showNotification(true);
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
}
public void endCurrentTrack() {
@@ -247,7 +248,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
if (!isRecording()) return;
trackPointCreator.stop();
showNotification(false);
stopForeground(true);
notificationManager.cancelNotification();
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
}
@@ -273,19 +275,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
}
private void showNotification(boolean isGpsStarted) {
if (isRecording()) {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setRecording(this, recordingStatus.getTrackId()));
}
if (!isRecording() && isGpsStarted) {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
}
if (!isRecording() && !isGpsStarted) {
stopForeground(true);
notificationManager.cancelNotification();
}
}
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
if (!isRecording()) {
return null;
@@ -28,6 +28,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R;
@@ -66,15 +67,21 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/**
* Starts and binds the service.
*
* @param foreground is the service expected to call `startForeground()`?
*/
public void startAndBind(Context context) {
public void startAndBind(Context context, boolean foreground) {
if (trackRecordingService != null) {
// Service is already started and bound.
return;
}
Log.i(TAG, "Starting the service.");
context.startService(new Intent(context, TrackRecordingService.class));
if (foreground) {
ContextCompat.startForegroundService(context, new Intent(context, TrackRecordingService.class));
} else {
context.startService(new Intent(context, TrackRecordingService.class));
}
startConnection(context);
}
@@ -88,7 +95,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
@Deprecated
public void startAndBindWithCallback(Context context) {
if (trackRecordingService == null) {
startAndBind(context);
startAndBind(context, false);
return;
}
if (callback != null) {
@@ -68,6 +68,7 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
}
void stop() {
cancelNotification();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this);
}
@@ -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.
@@ -5,8 +5,8 @@ import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnno
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceMovingTime;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
import android.content.Context;
import android.text.Spannable;
@@ -58,7 +58,7 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
altitudeSumManager = new AltitudeSumManager();
remoteSensorManager.start();
altitudeSumManager.start(context);
altitudeSumManager.start(context, handler);
}
@@ -8,9 +8,6 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
public class MainSettingsFragment extends PreferenceFragmentCompat {
@@ -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);
}
}
@@ -11,7 +11,6 @@ import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.SettingsBinding;
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
@@ -5,7 +5,6 @@ import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.DatePicker;
@@ -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);
}
@@ -22,6 +22,8 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
@@ -169,7 +171,7 @@ public class ListItemUtils {
textView.setVisibility(View.VISIBLE);
textView.setText(value);
if (addShadow) {
textView.setShadowLayer(5, 0, 2, context.getResources().getColor(android.R.color.black));
textView.setShadowLayer(5, 0, 2, ContextCompat.getColor(context, android.R.color.black));
} else {
textView.setShadowLayer(0, 0, 0, 0);
}
@@ -27,11 +27,11 @@ abstract public class RecyclerViewSwipeDeleteCallback extends ItemTouchHelper.Ca
public RecyclerViewSwipeDeleteCallback(Context context) {
background = new ColorDrawable();
backgroundColor = context.getResources().getColor(R.color.colorBackgroundDelete);
backgroundColor = ContextCompat.getColor(context, R.color.colorBackgroundDelete);
clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
deleteDrawable = ContextCompat.getDrawable(context, R.drawable.ic_delete_forever_24dp);
deleteDrawable.setTint(context.getResources().getColor(R.color.colorForegroundDelete));
deleteDrawable.setTint(ContextCompat.getColor(context, R.color.colorForegroundDelete));
intrinsicWidth = deleteDrawable.getIntrinsicWidth();
intrinsicHeight = deleteDrawable.getIntrinsicHeight();
}
@@ -65,7 +65,7 @@ public class EGM2008Utils {
if (location.getLongitude() >= 0) {
longitude = location.getLongitude();
} else {
longitude = 180 + Math.abs(location.getLongitude());
longitude = 360 + location.getLongitude();
}
int longitudeIndex = (int) (longitude * RESOLUTION_IN_MINUTES);
@@ -172,5 +172,13 @@ public class EGM2008Utils {
public int hashCode() {
return Objects.hash(latitudeIndex, longitudeIndex);
}
@Override
public String toString() {
return "Indices{" +
"latitudeIndex=" + latitudeIndex +
", longitudeIndex=" + longitudeIndex +
'}';
}
}
}
@@ -17,8 +17,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Track;
@@ -9,6 +9,7 @@ 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;
@@ -138,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;
}
@@ -168,7 +169,7 @@ 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 {
@@ -254,13 +254,14 @@ public class StringUtils {
/**
* @return the formatted altitude_m (or null) and it's unit as {@link Pair}
*/
//TODO altitude_m should be double or a value object
public static Pair<String, String> getAltitudeParts(Context context, Float altitude_m, UnitSystem unitSystem) {
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1)
.setUnit(unitSystem)
.build(context);
Distance distance = altitude_m != null ? Distance.of(altitude_m) : Distance.of(Double.NaN); //TODO Refactor Double.NaN
Distance distance = altitude_m != null ? Distance.of(altitude_m) : Distance.of((Double) null);
return formatter.getDistanceParts(distance);
}
+1
View File
@@ -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>
+1
View File
@@ -599,4 +599,5 @@ limitations under the License.
<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>
+1
View File
@@ -590,4 +590,5 @@ limitations under the License.
<item quantity="one">1 deniz mili</item>
<item quantity="other">%1$.2f deniz mili</item>
</plurals>
<string name="image_discard"></string>
</resources>
+32
View File
@@ -559,4 +559,36 @@ limitations under the License.
<string name="settings_public_api_dashboard_enabled_summary_on">Một ứng dụng bắt đầu ghi hình cũng có thể truy cập vào dữ liệu ghi lại.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Ghi lại dữ liệu sẽ không được miễn phí tự động với các ứng dụng khác.</string>
<string name="settings_public_api_dashboard_enabled_title">Chuyển dữ liệu tự động</string>
<string name="settings_export_filename_title">Định dạng tên tệp</string>
<string name="show_on_dashboard_not_installed">Không có ứng dụng hỗ trợ API OpenTracks Dashboard nào được cài đặt.</string>
<string name="description_pace_nautical">Nhịp độ (phút/dặm hải lý)</string>
<string name="image_discard">Bỏ</string>
<string name="settings_announcements_average_heart_rate">Nhịp tim trung bình</string>
<string name="settings_announcements_lap_heart_rate">Nhịp tim vòng</string>
<string name="settings_announcements_average_speed_pace">Tốc độ trung bình/nhịp độ</string>
<string name="settings_stats_units_nautical">Hải lý (dặm hải lý, ft)</string>
<string name="settings_public_api_package_title">Tên gói cho API công khai</string>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="other">%1$.2f dặm hải lý</item>
</plurals>
<string name="select_show_on_map_behavior">Chọn hành vi \"hiện trên bản đồ\"</string>
<string name="settings_announcements_lap_speed_pace">Tốc độ vòng/nhịp độ</string>
<string name="track_discarding">Đang bỏ đoạn đường</string>
<plurals name="voiceSpeedMKnots">
<item quantity="other">%1$.1f nút</item>
</plurals>
<string name="voice_per_nautical_mile">trên dặm hải lý</string>
<string name="description_speed_nautical">Tốc độ (nút)</string>
<string name="unit_nautical_mile">Dặm hải lý</string>
<string name="unit_knots">nút</string>
<string name="unit_minute_per_nautical_mile">phút/dặm hải lý</string>
<string name="value_integer_knots">%1$d nút</string>
<string name="value_float_knots">%1$.1f nút</string>
<string name="value_float_knots_recommended">%1$.1f nút (khuyên dùng)</string>
<string name="error_crash_title">%1$s đã dừng đột ngột</string>
<string name="share_crash_report">Chia sẻ báo cáo dừng đột ngột</string>
<string name="report_bug">Báo cáo lỗi</string>
<string name="show_on_map_format_ask">Hỏi</string>
<string name="show_on_dashboard">API OpenTracks Dashboard</string>
<string name="always">Luôn luôn</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>
+1
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>
+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"
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground>
<inset
android:inset="20dp"
android:drawable="@drawable/ic_logo_24dp" />
</foreground>
<monochrome>
<inset
android:inset="20dp"
android:drawable="@drawable/ic_logo_24dp" />
</monochrome>
</adaptive-icon>
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_logo_24dp" />
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFF6D00</color>
</resources>