Compare commits

...

38 Commits

Author SHA1 Message Date
Román Martínez 2178ff97d3 Release: v3.20.2 2021-09-22 22:30:16 +02:00
Timo Schwarzer f070495fb4 Ask for permission before using Bluetooth 2021-09-22 22:14:53 +02:00
Román Martínez d99fb5ea47 Cadence set to 0 when connected but user is not pedaling. Fixes #931.
Speed set to 0 when connected but last value is outdated. Fixes #930.
Bug: overflow when reset sensors (cadence and speed). Fixes #833.
Moving time stops when is not moving (the last track point is not moving) and restarts when last track point is moving.
2021-09-21 18:07:23 +02:00
Román Martínez d69a726e0e Renaming variables. 2021-09-18 16:17:29 +02:00
Román Martínez e0111c2ffd Gain and loss are integers. 2021-09-18 16:17:29 +02:00
Timo Schwarzer 487bd6c3a0 Set FLAG_IMMUTABLE for PendingIntents
This fixes a crash on Android 12, since it explicitly requires setting either FLAG_MUTABLE or FLAG_IMMUTABLE
2021-09-07 23:18:14 +02:00
Román Martínez 40aab9444a Time interval setting is in minutes. 2021-09-06 22:06:48 +02:00
Dennis Guse 82b460b57b Release: v3.20.1 2021-09-03 19:46:51 +02:00
Mario 68baad16e2 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2021-09-02 21:55:28 +02:00
Dennis Guse 26ed9b24d3 CI: build nightly on push.
Fixes #894.
2021-09-02 21:55:25 +02:00
dependabot[bot] 9f6089e7f1 Bump gradle from 7.0.1 to 7.0.2
Bumps gradle from 7.0.1 to 7.0.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>
2021-09-02 21:55:15 +02:00
FTno d0eef44bf8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-08-31 08:45:48 +02:00
Mario eead3b5721 Translated using Weblate (Spanish)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-08-31 06:47:45 +02:00
Mario b8f0c59198 Translated using Weblate (Spanish (Latin America))
Currently translated at 99.5% (425 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2021-08-31 06:47:45 +02:00
vsevjednom-cz 8f408dceba Translated using Weblate (Slovak)
Currently translated at 68.7% (292 of 425 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sk/
2021-08-31 06:47:45 +02:00
vsevjednom-cz d1dfaa5443 Translated using Weblate (Czech)
Currently translated at 96.7% (411 of 425 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-08-31 06:47:45 +02:00
vsevjednom-cz b2da849e25 Translated using Weblate (Czech)
Currently translated at 95.7% (407 of 425 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-08-31 06:47:45 +02:00
Xosé M 88579be0a2 Translated using Weblate (Galician)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-08-31 06:47:45 +02:00
FTno 70d971d8fd Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-08-31 06:47:45 +02:00
mondstern 70b0dac4b6 Translated using Weblate (Tagalog)
Currently translated at 72.7% (297 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/tl/
2021-08-31 06:47:45 +02:00
Carl Dea 038e8d3e06 Using Zulu builds of OpenJDK in GH actions
The AdoptOpenJDK has been discontinued since July 2021. When using Zulu you get all the latest updated (TCK Tested) builds for all versions of OpenJDK.
2021-08-31 06:46:34 +02:00
Dennis Guse 932bb5ed5a KMLTrackExporter: uses NumberFormat to export sensor data.
Fixes #832.
2021-08-30 17:48:01 +02:00
Dennis Guse 8f2d1722ad Announcements: can be triggered by time and distance (instead of either).
Fixes #921.
2021-08-30 17:24:51 +02:00
Dennis Guse 9cb3d8c1bb Finished renaming: TrackPointCreator was still referenced as HandlerServer. 2021-08-30 17:24:51 +02:00
Dennis Guse fa02ba9c69 Voice announcements: if TTS is not available play a sound file.
Fixes #862.
2021-08-30 17:24:51 +02:00
Dennis Guse 4f31607e3e Voice announcements are now triggered by the TrackRecordingService's timer that is also used for UI updates. 2021-08-30 17:24:51 +02:00
Dennis Guse b5165de7f0 PeriodicTaskExecutor: use a Handler instead of Timer. 2021-08-30 17:24:51 +02:00
Dennis Guse b32e87e10b PeriodTaskFactory: refactor interfaces. 2021-08-30 17:24:51 +02:00
dependabot[bot] 29a1daa177 Bump reactivecircus/android-emulator-runner from 2.19.1 to 2.20.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.19.1 to 2.20.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.19.1...v2.20.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>
2021-08-30 17:23:53 +02:00
Dennis Guse d5cfd118f6 Cleanup. 2021-08-28 19:00:06 +02:00
Dennis Guse 5981340bb9 TrackRecordingManager: only keeps reference to TrackId. 2021-08-28 19:00:06 +02:00
dependabot[bot] f562d51d9a Bump mockito-android from 3.12.3 to 3.12.4
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.12.3 to 3.12.4.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.12.3...v3.12.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-26 12:15:22 +02:00
Dennis Guse d77b9243af RecordingStatus provides methods for status changes. 2021-08-25 22:58:45 +02:00
dependabot[bot] 2c7e665206 Bump mockito-android from 3.12.1 to 3.12.3
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.12.1 to 3.12.3.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.12.1...v3.12.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-25 07:00:23 +02:00
Dennis Guse 1aa6b94f19 Revert "Translated using Weblate (Slovenian)"
This reverts commit 8f39bf13
2021-08-24 18:38:12 +02:00
Dennis Guse 68c447f77e Removed AdaptiveLocationListenerPolicy.
Fixes #886.
2021-08-24 18:26:09 +02:00
Román Martínez 6b3a2eae5e Added Garmin HRM-Dual heart rate sensor 2021-08-23 13:13:11 +02:00
dependabot[bot] 3869dba72b Bump mockito-android from 3.12.0 to 3.12.1
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.12.0 to 3.12.1.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.12.0...v3.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-23 12:02:42 +02:00
114 changed files with 963 additions and 1390 deletions
+34
View File
@@ -0,0 +1,34 @@
#https://github.com/marketplace/actions/android-emulator-runner
name: Build nightly
on: [ push ]
jobs:
nightly-build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2.3.4
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Assemble nighlty APK
shell: bash
run: |
echo ${NIGHTLY_STORE_FILE} | base64 -d > KEY_NIGHTLY.jks
./gradlew assembleNightly -Dnightly_store_file=KEY_NIGHTLY.jks -Dnightly_store_password="${NIGHTLY_STORE_PASSWORD}" -Dnightly_key_alias="${NIGHTLY_KEY_ALIAS}" -Dnightly_key_password="${NIGHTLY_STORE_PASSWORD}"
env:
NIGHTLY_STORE_FILE: ${{ secrets.NIGHTLY_STORE_FILE }}
NIGHTLY_STORE_PASSWORD: ${{ secrets.NIGHTLY_STORE_PASSWORD }}
NIGHTLY_KEY_ALIAS: ${{ secrets.NIGHTLY_KEY_ALIAS }}
- name: Archive
if: always()
uses: actions/upload-artifact@v2
with:
name: OpenTracks-nightly.apk
path: ./build/outputs/apk/nightly/OpenTracks-nightly.apk
retention-days: 7
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
distribution: 'zulu'
java-version: '11'
- run: ./gradlew lint
- run: ./gradlew lint
+2 -2
View File
@@ -15,11 +15,11 @@ jobs:
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
distribution: 'zulu'
java-version: '11'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.19.1
uses: reactivecircus/android-emulator-runner@v2.20.0
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
+2 -1
View File
@@ -11,6 +11,7 @@
* Wahoo Tickr (Model: WFBTHR02)
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
* Garmin HRM-Dual (Reference: 010-12883-00)
## CSCP: Cycling Cadence and Speed Service
@@ -54,4 +55,4 @@ Also the distance is not computed.
We do not support type of movement (i.e., walking vs running).
* Polar Stride
* Polar Stride
+20 -4
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.1'
classpath 'com.android.tools.build:gradle:7.0.2'
}
}
@@ -50,8 +50,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4410
versionName "v3.20.0"
versionCode 4448
versionName "v3.20.2"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -62,6 +62,16 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
signingConfigs {
nightly {
if (System.getProperty("nightly_store_file") != null) {
storeFile file(System.getProperty("nightly_store_file"))
storePassword System.getProperty("nightly_store_password")
keyAlias System.getProperty("nightly_key_alias")
keyPassword System.getProperty("nightly_key_password")
}
}
}
buildTypes {
debug {
@@ -69,6 +79,12 @@ android {
versionNameSuffix "-debug"
}
nightly {
signingConfig signingConfigs.nightly
applicationIdSuffix ".nightly"
versionNameSuffix "-nightly"
}
release {
minifyEnabled false
}
@@ -98,7 +114,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.mockito:mockito-android:3.12.0'
androidTestImplementation 'org.mockito:mockito-android:3.12.4'
androidTestUtil 'androidx.test:orchestrator:1.4.0'
}
@@ -0,0 +1,7 @@
v3.20.1: OpenTracks
Changes:
- Announcements can now be used by both distance and time
- Announcements beep if no Text-to-speech software is installed
- Removed adaptive GPS location frequency
@@ -0,0 +1,11 @@
v3.20.2: OpenTracks
Changes:
- Elevation gain and loss are integers instead of floats
- Ask for permission before using Bluetooth by a Toast message
Bugfix:
- Set FLAG_IMMUTABLE for PendingIntents to avoid crash on Android 12
- Cadence sensor returns 0 instead of None when user is not pedaling
- Speed sensor returns 0 instead of the last speed when it's stopped
- With sensors, moving time stops when is not moving
@@ -9,10 +9,10 @@ import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewAssertion;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.runner.AndroidJUnit4;
import android.content.Context;
import android.content.SharedPreferences;
@@ -83,7 +83,7 @@ public class EspressoCustomLayoutTest {
}
RecyclerView recyclerView = (RecyclerView) view;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
assertThat(adapter.getItemCount(), is(expectedCount));
}
}
@@ -143,7 +143,7 @@ public class EspressoDeleteTrackTest {
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
return new TypeSafeMatcher<>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
@@ -160,13 +160,15 @@ public class EspressoDeleteTrackTest {
}
public static Matcher<View> withListSize (final int size) {
return new TypeSafeMatcher<View> () {
@Override public boolean matchesSafely (final View view) {
return ((ListView) view).getCount () == size;
return new TypeSafeMatcher<>() {
@Override
public boolean matchesSafely(final View view) {
return ((ListView) view).getCount() == size;
}
@Override public void describeTo (final Description description) {
description.appendText ("ListView should have " + size + " items");
@Override
public void describeTo(final Description description) {
description.appendText("ListView should have " + size + " items");
}
};
}
@@ -144,7 +144,7 @@ public class EspressoEditTrackRecordingTest {
// check track's name is "New Name"
ViewInteraction textView = onView(
allOf(withId(R.id.stats_name_value),
withParent(withParent(IsInstanceOf.<View>instanceOf(android.widget.ScrollView.class))),
withParent(withParent(IsInstanceOf.instanceOf(android.widget.ScrollView.class))),
isDisplayed()));
textView.check(matches(withText("New Name")));
}
@@ -153,7 +153,7 @@ public class EspressoEditTrackRecordingTest {
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
return new TypeSafeMatcher<>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.content.sensor;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -11,6 +12,7 @@ import de.dennisguse.opentracks.util.UintUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
@RunWith(AndroidJUnit4.class)
public class SensorDataCyclingTest {
@@ -81,7 +83,9 @@ public class SensorDataCyclingTest {
assertEquals(60, current.getValue(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_cadence_rollOverCount() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
@@ -94,6 +98,19 @@ public class SensorDataCyclingTest {
assertEquals(60, current.getValue(), 0.01);
}
@Test
public void compute_cadence_overflow() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous);
// then
assertNull(current.getValue());
}
@Test
public void compute_speed() {
// given
@@ -108,7 +125,9 @@ public class SensorDataCyclingTest {
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_speed_rollOverCount() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
@@ -122,6 +141,19 @@ public class SensorDataCyclingTest {
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
}
@Test
public void compute_speed_overflow() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, Distance.ofMM(2000));
// then
assertNull(current.getValue());
}
@Test
public void equals_speed_with_no_data() {
// given
@@ -112,41 +112,41 @@ public class ExportImportTest {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
service.getHandlerServer().setClock(Clock.systemUTC());
service.getTrackPointCreator().setClock(Clock.systemUTC());
}
public void setUp(boolean hasSensorDistance) throws TimeoutException {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
TrackPointCreator handlerServer = service.getHandlerServer();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackId = service.startNewTrack();
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:04Z"), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:04Z"), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET")));
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET")));
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
service.pauseCurrentTrack();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET")));
service.resumeCurrentTrack();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:21Z"), ZoneId.of("CET")));
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:21Z"), ZoneId.of("CET")));
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET")));
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET")));
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
service.endCurrentTrack();
Track track = contentProviderUtils.getTrack(trackId);
@@ -380,7 +380,7 @@ public class ExportImportTest {
}
}
private void sendLocation(TrackPointCreator handlerServer, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
private void sendLocation(TrackPointCreator trackPointCreator, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
Location location = new Location("mock");
location.setLatitude(latitude);
location.setLongitude(longitude);
@@ -388,7 +388,7 @@ public class ExportImportTest {
location.setSpeed(speed);
location.setAltitude(altitude);
handlerServer.setAltitudeSumManager(new AltitudeSumManager() {
trackPointCreator.setAltitudeSumManager(new AltitudeSumManager() {
@Override
public void fill(@NonNull TrackPoint trackPoint) {
trackPoint.setAltitudeGain(altitudeGain);
@@ -396,7 +396,7 @@ public class ExportImportTest {
}
});
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
@Override
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
SensorDataSet sensorDataSet = new SensorDataSet();
@@ -422,7 +422,7 @@ public class ExportImportTest {
}
});
handlerServer.setClock(Clock.fixed(time, ZoneId.of("CET")));
handlerServer.getLocationHandler().onLocationChanged(location);
trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET")));
trackPointCreator.getLocationHandler().onLocationChanged(location);
}
}
@@ -1,83 +0,0 @@
/*
* Copyright 2012 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.services;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.time.Duration;
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
import static org.junit.Assert.assertEquals;
/**
* Tests the {@link AdaptiveLocationListenerPolicy}.
*
* @author youtaol
*/
@RunWith(JUnit4.class)
public class AdaptiveLocationListenerPolicyTest {
private static final Duration MIN = Duration.ofMillis(1000);
private static final Duration MAX = Duration.ofMillis(3000);
private static final int MIN_DISTANCE = 10;
private static final Duration NEW_IDLE_TIME_BIG = Duration.ofMillis(10000);
private static final Duration NEW_IDLE_TIME_NORMAL = Duration.ofMillis(5000);
private static final Duration NEW_IDLE_TIME_SMALL = Duration.ofMillis(2000);
private static final Duration NEW_IDLE_TIME_LESS_THAN_MIN = Duration.ofMillis(500);
private AdaptiveLocationListenerPolicy adaptiveLocationListenerPolicy;
@Before
public void setUp() {
adaptiveLocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE);
}
/**
* Tests the {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four situations.
* <ul>
* <li>The newIdleTime is bigger than max interval.</li>
* <li>The newIdleTime is between min and max interval.</li>
* <li>The newIdleTime is smaller than max interval.</li>
* <li>The newIdleTime is smaller than the smallest interval unit.</li>
* </ul>
*/
@Test
public void testGetDesiredPollingInterval() {
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG);
assertEquals(MAX, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
assertEquals(NEW_IDLE_TIME_NORMAL.dividedBy(2).getSeconds(), adaptiveLocationListenerPolicy.getDesiredPollingInterval().getSeconds());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN);
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
}
/**
* Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance_m()}.
*/
@Test
public void testGetMinDistance() {
assertEquals(MIN_DISTANCE, adaptiveLocationListenerPolicy.getMinDistance_m());
}
}
@@ -138,7 +138,7 @@ public class TrackRecordingServiceTest {
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
service.getHandlerServer().setClock(Clock.systemUTC());
service.getTrackPointCreator().setClock(Clock.systemUTC());
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
@@ -212,15 +212,15 @@ public class TrackRecordingServiceTest {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator handlerServer = service.getHandlerServer();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
handlerServer.stopGPS();
handlerServer.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.endCurrentTrack();
// then
@@ -242,22 +242,22 @@ public class TrackRecordingServiceTest {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator handlerServer = service.getHandlerServer();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
handlerServer.stopGPS();
handlerServer.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.pauseCurrentTrack();
// then
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
//when
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
service.resumeTrack(trackId);
// then
@@ -281,23 +281,23 @@ public class TrackRecordingServiceTest {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator handlerServer = service.getHandlerServer();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
handlerServer.stopGPS();
handlerServer.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.endCurrentTrack();
// when
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
service.resumeTrack(trackId);
handlerServer.stopGPS();
handlerServer.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
handlerServer.onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z")), Distance.of(50));
trackPointCreator.onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z")), Distance.of(50));
// then
assertTrue(service.isRecording());
@@ -323,18 +323,18 @@ public class TrackRecordingServiceTest {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator handlerServer = service.getHandlerServer();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
handlerServer.stopGPS();
handlerServer.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.pauseCurrentTrack();
// when
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
service.endCurrentTrack();
// then
@@ -405,14 +405,14 @@ public class TrackRecordingServiceTest {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator handlerServer = service.getHandlerServer();
handlerServer.stopGPS();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.stopGPS();
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
handlerServer.onNewTrackPoint(
trackPointCreator.onNewTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
.setLatitude(10)
.setLongitude(10)
@@ -467,6 +467,6 @@ public class TrackRecordingServiceTest {
.setSpeed(Speed.of(speed))
.setBearing(3.0f);
trackRecordingService.getHandlerServer().onNewTrackPoint(trackPoint, Distance.of(50));
trackRecordingService.getTrackPointCreator().onNewTrackPoint(trackPoint, Distance.of(50));
}
}
@@ -96,7 +96,7 @@ public class TrackRecordingServiceTestLocation {
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
service.getHandlerServer().stopGPS();
service.getTrackPointCreator().stopGPS();
}
@After
@@ -107,10 +107,10 @@ public class TrackRecordingServiceTestLocation {
@MediumTest
@Test
public void testOnLocationChangedAsync_movingAccurate() throws Exception {
public void testOnLocationChangedAsync_movingAccurate() {
// given
Track.Id trackId = service.startNewTrack();
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
@@ -180,10 +180,10 @@ public class TrackRecordingServiceTestLocation {
@MediumTest
@Test
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
public void testOnLocationChangedAsync_slowMovingAccurate() {
// given
Track.Id trackId = service.startNewTrack();
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
@@ -225,10 +225,10 @@ public class TrackRecordingServiceTestLocation {
@MediumTest
@Test
public void testOnLocationChangedAsync_idle() throws Exception {
public void testOnLocationChangedAsync_idle() {
// given
Track.Id trackId = service.startNewTrack();
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -277,11 +277,11 @@ public class TrackRecordingServiceTestLocation {
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
public void testOnLocationChangedAsync_idle_withMovement() {
// given
Track.Id trackId = service.startNewTrack();
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
service.getHandlerServer().stopGPS();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().stopGPS();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
@@ -337,11 +337,11 @@ public class TrackRecordingServiceTestLocation {
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
public void testOnLocationChangedAsync_idle_withSensorData() {
// given
Track.Id trackId = service.startNewTrack();
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
service.getHandlerServer().setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
@Override
public boolean isEnabled() {
@@ -432,10 +432,10 @@ public class TrackRecordingServiceTestLocation {
@MediumTest
@Test
public void testOnLocationChangedAsync_segment() throws Exception {
public void testOnLocationChangedAsync_segment() {
// given
Track.Id trackId = service.startNewTrack();
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -107,34 +107,6 @@ public class TrackRecordingServiceTestLooper {
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.voice_frequency_key), Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.voice_frequency_key), -1);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.voice_frequency_key), 1);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
@@ -235,8 +207,8 @@ public class TrackRecordingServiceTestLooper {
// Insert a few points, markers and statistics.
for (int i = 0; i < 30; i++) {
//TODO Should send locations to LocationHandler instead of TrackPoints to HandlerServer?
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, service.getHandlerServer().createNow())
//TODO Should send locations to LocationHandler instead of TrackPoints to TrackPointCreator?
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, service.getTrackPointCreator().createNow())
.setLongitude(35.0f + i / 10.0f)
.setLatitude(45.0f - i / 5.0f)
.setHorizontalAccuracy(Distance.of(5))
@@ -244,7 +216,7 @@ public class TrackRecordingServiceTestLooper {
.setBearing(3.0f);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
service.getHandlerServer().onNewTrackPoint(trackPoint, prefAccuracy);
service.getTrackPointCreator().onNewTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) {
service.insertMarker(null, null, null, null);
@@ -33,7 +33,7 @@ public class LocationHandlerTest {
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@Mock
private TrackPointCreator handlerServer;
private TrackPointCreator trackPointCreator;
@InjectMocks
private LocationHandler locationHandler;
@@ -49,7 +49,7 @@ public class LocationHandlerTest {
// Let's use default values.
sharedPreferences.edit().clear().commit();
Mockito.when(handlerServer.createNow())
Mockito.when(trackPointCreator.createNow())
.thenReturn(Instant.now());
//TODO REMOVE
@@ -67,7 +67,7 @@ public class LocationHandlerTest {
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
// then
verify(handlerServer, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
}
/**
@@ -83,7 +83,7 @@ public class LocationHandlerTest {
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
// then
verify(handlerServer, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
}
/**
@@ -99,7 +99,7 @@ public class LocationHandlerTest {
// then
// no newTrackPoint called
verify(handlerServer, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
}
@Test
@@ -112,7 +112,7 @@ public class LocationHandlerTest {
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
// then
verify(handlerServer, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
}
/**
@@ -15,6 +15,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -25,7 +26,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class PeriodicTaskExecutorTest {
public class VoiceAnnouncementManagerTest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@@ -53,17 +54,38 @@ public class PeriodicTaskExecutorTest {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
PeriodicTaskExecutor periodicTaskExecutor = new PeriodicTaskExecutor(service, new AnnouncementPeriodicTask.Factory());
periodicTaskExecutor.setMetricUnits(true);
periodicTaskExecutor.setTaskFrequency(-5);
VoiceAnnouncementManager voiceAnnouncementManager = new VoiceAnnouncementManager(service);
voiceAnnouncementManager.setFrequency(Distance.ofKilometer(5));
// when
TrackStatistics statistics = new TrackStatistics();
statistics.setTotalDistance(Distance.of(13000));
assertEquals(Distance.of(15000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
statistics.setTotalDistance(Distance.ofKilometer(13));
voiceAnnouncementManager.restore(statistics);
assertEquals(Distance.of(15000), voiceAnnouncementManager.getNextTotalDistance());
statistics.setTotalDistance(Distance.of(15100));
assertEquals(Distance.of(20000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
voiceAnnouncementManager.restore(statistics);
assertEquals(Distance.of(20000), voiceAnnouncementManager.getNextTotalDistance());
}
@Test
public void calculateNextTotalTime() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
VoiceAnnouncementManager voiceAnnouncementManager = new VoiceAnnouncementManager(service);
voiceAnnouncementManager.setFrequency(Duration.ofSeconds(5));
// when
TrackStatistics statistics = new TrackStatistics();
statistics.setTotalTime(Duration.ofSeconds(91));
voiceAnnouncementManager.restore(statistics);
assertEquals(Duration.ofSeconds(95), voiceAnnouncementManager.getNextTotalTime());
statistics.setTotalTime(Duration.ofSeconds(95));
voiceAnnouncementManager.restore(statistics);
assertEquals(Duration.ofSeconds(100), voiceAnnouncementManager.getNextTotalTime());
}
}
+5 -1
View File
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="de.dennisguse.opentracks"
android:installLocation="auto">
@@ -52,7 +53,10 @@ limitations under the License.
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application
@@ -22,8 +22,7 @@ import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import de.dennisguse.opentracks.util.TTSUtils;
import de.dennisguse.opentracks.services.tasks.VoiceAnnouncement;
/**
* @author Jimmy Shih
@@ -35,7 +34,7 @@ public abstract class AbstractActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
// Set volume control stream for text to speech
setVolumeControlStream(TTSUtils.getTTSStream());
setVolumeControlStream(VoiceAnnouncement.AUDIO_STREAM);
setContentView(getRootView());
@@ -77,7 +77,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
private static final String TAG = TrackListActivity.class.getSimpleName();
// The following are set in onCreate
// The following are setFrequency in onCreate
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private ResourceCursorAdapter resourceCursorAdapter;
@@ -66,7 +66,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
// The following are set in onCreate.
// The following are setFrequency in onCreate.
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
@@ -51,7 +51,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
// The following are set in onCreate
// The following are setFrequency in onCreate
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -59,8 +59,8 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed(), metricUnits, isReportSpeed));
viewHolder.gain.setText(StringUtils.formatAltitude(context, interval.getGain_m(), metricUnits));
viewHolder.loss.setText(StringUtils.formatAltitude(context, interval.getLoss_m(), metricUnits));
viewHolder.gain.setText(StringUtils.formatAltitudeChange(context, interval.getGain_m(), metricUnits));
viewHolder.loss.setText(StringUtils.formatAltitudeChange(context, interval.getLoss_m(), metricUnits));
}
@@ -77,6 +77,7 @@ public class ChartPoint {
return power;
}
@NonNull
@Override
public String toString() {
return "ChartPoint{" + "timeOrDistance=" + timeOrDistance + '}';
@@ -123,6 +123,7 @@ public class Distance {
return Objects.hash(distance_m);
}
@NonNull
@Override
public String toString() {
return "Distance{" +
@@ -1,5 +1,7 @@
package de.dennisguse.opentracks.content.data;
import androidx.annotation.NonNull;
import java.time.Duration;
import java.util.Objects;
@@ -132,6 +134,7 @@ public class Speed {
return Objects.hash(speed_mps);
}
@NonNull
@Override
public String toString() {
return "Speed{" +
@@ -50,8 +50,14 @@ public abstract class SensorData<T> {
return value != null;
}
@NonNull
protected abstract T getNoneValue();
public T getValue() {
return value;
if (isRecent()) {
return value;
}
return getNoneValue();
}
/**
@@ -63,7 +69,7 @@ public abstract class SensorData<T> {
/**
* Is the data recent considering the current time.
*/
public boolean isRecent() {
private boolean isRecent() {
return Instant.now()
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
}
@@ -59,17 +59,30 @@ public final class SensorDataCycling {
return crankRevolutionsTime;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
public void compute(Cadence previous) {
if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
if (timeDiff_ms <= 0) {
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
value = null;
} else {
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
float cadence_ms = crankDiff / timeDiff_ms;
value = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
return;
}
// TODO We have to treat with overflow according to the documentation: read https://github.com/OpenTracksApp/OpenTracks/pull/953#discussion_r711625268
if (crankRevolutionsCount < previous.crankRevolutionsCount) {
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
return;
}
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
float cadence_ms = crankDiff / timeDiff_ms;
value = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
}
}
@@ -121,6 +134,16 @@ public final class SensorDataCycling {
return wheelRevolutionsTime;
}
@NonNull
@Override
protected Data getNoneValue() {
if (value != null) {
return new Data(value.distance, value.distanceOverall, Speed.zero());
} else {
return new Data(Distance.of(0), Distance.of(0), Speed.zero());
}
}
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
@@ -131,6 +154,10 @@ public final class SensorDataCycling {
return;
}
if (wheelRevolutionsCount < previous.wheelRevolutionsCount) {
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
return;
}
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
@@ -191,6 +218,7 @@ public final class SensorDataCycling {
return speed;
}
@NonNull
@Override
public String toString() {
return "Data{" +
@@ -216,6 +244,12 @@ public final class SensorDataCycling {
public DistanceSpeed getDistanceSpeed() {
return this.value != null ? this.value.second : null;
}
@NonNull
@Override
protected Pair<Cadence, DistanceSpeed> getNoneValue() {
return new Pair<>(null, null);
}
}
}
@@ -18,4 +18,10 @@ public class SensorDataCyclingPower extends SensorData<Float> {
public String toString() {
return super.toString() + " power=" + value;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
}
@@ -18,4 +18,10 @@ public class SensorDataHeartRate extends SensorData<Float> {
public String toString() {
return super.toString() + " heart=" + value;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
}
@@ -1,5 +1,6 @@
package de.dennisguse.opentracks.content.sensor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -52,6 +53,16 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
return totalDistance;
}
@NonNull
@Override
protected Data getNoneValue() {
if (value != null) {
return new Data(Speed.zero(), 0f, ((Data) value).distance);
} else {
return new Data(Speed.zero(), 0f, Distance.of(0));
}
}
public void compute(SensorDataRunning previous) {
Distance overallDistance = null;
if (hasTotalDistance() && previous != null && previous.hasTotalDistance()) {
@@ -92,10 +103,12 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
return cadence;
}
@Nullable
public Distance getDistance() {
return distance;
}
@NonNull
@Override
public String toString() {
return "Data{" +
@@ -108,7 +108,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
};
/**
* A runnable that will set the orange pointer as appropriate and redraw.
* A runnable that will setFrequency the orange pointer as appropriate and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
@@ -243,11 +243,11 @@ public class StatisticsRecordedFragment extends Fragment {
Pair<String, String> parts;
parts = StringUtils.getAltitudeParts(getContext(), altitudeGain_m, preferenceMetricUnits);
parts = StringUtils.getAltitudeChangeParts(getContext(), altitudeGain_m, preferenceMetricUnits);
viewBinding.statsAltitudeGainValue.setText(parts.first);
viewBinding.statsAltitudeGainUnit.setText(parts.second);
parts = StringUtils.getAltitudeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
parts = StringUtils.getAltitudeChangeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
viewBinding.statsAltitudeLossValue.setText(parts.first);
viewBinding.statsAltitudeLossUnit.setText(parts.second);
@@ -26,10 +26,12 @@ import androidx.annotation.VisibleForTesting;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import de.dennisguse.opentracks.R;
@@ -67,6 +69,13 @@ public class KMLTrackExporter implements TrackExporter {
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private static final NumberFormat SENSOR_DATA_FORMAT = NumberFormat.getInstance(Locale.US);
static {
SENSOR_DATA_FORMAT.setMaximumFractionDigits(1);
SENSOR_DATA_FORMAT.setGroupingUsed(false);
}
private final Context context;
private final boolean exportPhotos;
private final ContentProviderUtils contentProviderUtils;
@@ -394,7 +403,7 @@ public class KMLTrackExporter implements TrackExporter {
if (value == null) {
printWriter.println("<gx:value />");
} else {
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
printWriter.println("<gx:value>" + SENSOR_DATA_FORMAT.format(value) + "</gx:value>");
}
}
printWriter.println("</gx:SimpleArrayData>");
@@ -66,7 +66,7 @@ public class SharePictureTrackExporter implements TrackExporter {
viewBinding.sharePictureTotalTime.setText(StringUtils.formatElapsedTimeWithHour(track.getTrackStatistics().getTotalTime()));
viewBinding.sharePictureTotalDistance.setText(StringUtils.formatDistance(context, track.getTrackStatistics().getTotalDistance(), metricUnits));
viewBinding.sharePictureGain.setText(StringUtils.formatAltitude(context, trackStatistics.getTotalAltitudeGain(), metricUnits));
viewBinding.sharePictureGain.setText(StringUtils.formatAltitudeChange(context, trackStatistics.getTotalAltitudeGain(), metricUnits));
// prepare rendering
View view = viewBinding.getRoot();
@@ -34,7 +34,7 @@ class TrackRecordingManager {
private Distance recordingDistanceInterval;
private Distance maxRecordingDistance;
private Track track;
private Track.Id trackId;
private TrackStatisticsUpdater trackStatisticsUpdater;
private TrackPoint lastTrackPoint;
@@ -47,66 +47,68 @@ class TrackRecordingManager {
}
Track.Id start(TrackPoint segmentStartTrackPoint) {
// Insert a track
track = new Track();
track.setId(contentProviderUtils.insertTrack(track));
// Create new track
Track track = new Track();
trackId = contentProviderUtils.insertTrack(track);
track.setId(trackId);
trackStatisticsUpdater = new TrackStatisticsUpdater();
insertTrackPoint(track, segmentStartTrackPoint);
insertTrackPoint(trackId, segmentStartTrackPoint);
//TODO Pass TrackPoint
track.setName(TrackNameUtils.getTrackName(context, track.getId(), segmentStartTrackPoint.getTime()));
track.setName(TrackNameUtils.getTrackName(context, trackId, segmentStartTrackPoint.getTime()));
String category = PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), context); //TODO Re-use sharedpreferences
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(track); //TODO Could we do this in the insert?
contentProviderUtils.updateTrack(track);
return track.getId();
return trackId;
}
//TODO Handle non-existing trackId? Start a new track or exception?
void resume(@NonNull Track.Id trackId, @NonNull TrackPoint segmentStartTrackPoint) {
track = contentProviderUtils.getTrack(trackId);
void resume(@NonNull Track.Id resumeTrackId, @NonNull TrackPoint segmentStartTrackPoint) {
trackId = resumeTrackId;
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
return;
}
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
insertTrackPoint(track, segmentStartTrackPoint);
insertTrackPoint(trackId, segmentStartTrackPoint);
}
void pause(TrackPointCreator handlerServer) {
void pause(TrackPointCreator trackPointCreator) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPointIfNewer(trackId, lastTrackPoint);
}
insertTrackPoint(track, handlerServer.createSegmentEnd());
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
}
void end(TrackPointCreator handlerServer) {
void end(TrackPointCreator trackPointCreator) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPointIfNewer(trackId, lastTrackPoint);
}
TrackPoint segmentEnd = handlerServer.createSegmentEnd();
insertTrackPoint(track, segmentEnd);
TrackPoint segmentEnd = trackPointCreator.createSegmentEnd();
insertTrackPoint(trackId, segmentEnd);
track = null;
trackId = null;
trackStatisticsUpdater = null;
lastTrackPoint = null;
lastValidTrackPoint = null;
isIdle = false;
}
Pair<Track, Pair<TrackPoint, SensorDataSet>> get(TrackPointCreator handlerServer) {
if (handlerServer == null) {
Pair<Track, Pair<TrackPoint, SensorDataSet>> get(TrackPointCreator trackPointCreator) {
if (trackPointCreator == null) {
return null;
}
TrackStatisticsUpdater tmpTrackStatisticsUpdater = getTrackStatisticsUpdater();
Pair<TrackPoint, SensorDataSet> current = handlerServer.createCurrentTrackPoint(lastTrackPoint);
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPoint);
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
@@ -123,14 +125,14 @@ class TrackRecordingManager {
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
if (name == null) {
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(track.getId());
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(trackId);
if (nextMarkerNumber == null) {
nextMarkerNumber = 1;
}
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
}
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(trackId);
if (trackPoint == null) {
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
return null;
@@ -143,20 +145,20 @@ class TrackRecordingManager {
// Insert marker
Marker marker = new Marker(name, description, category, icon, track.getId(), getTrackStatistics(), trackPoint, photoUrl);
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), trackPoint, photoUrl);
Uri uri = contentProviderUtils.insertMarker(marker);
return new Marker.Id(ContentUris.parseId(uri));
}
void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(trackId);
//Storing trackPoint
// Always insert the first segment location
if (!currentSegmentHasTrackPoint()) {
insertTrackPoint(track, trackPoint);
insertTrackPoint(trackId, trackPoint);
lastTrackPoint = trackPoint;
return;
}
@@ -164,20 +166,21 @@ class TrackRecordingManager {
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
if (distanceToLastTrackLocation != null) {
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPointIfNewer(trackId, lastTrackPoint);
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(track, trackPoint);
insertTrackPoint(trackId, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
}
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPointIfNewer(trackId, lastTrackPoint);
insertTrackPoint(track, trackPoint);
insertTrackPoint(trackId, trackPoint);
isIdle = false;
@@ -187,9 +190,9 @@ class TrackRecordingManager {
}
if (!isIdle && !trackPoint.isMoving()) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPointIfNewer(trackId, lastTrackPoint);
insertTrackPoint(track, trackPoint);
insertTrackPoint(trackId, trackPoint);
isIdle = true;
@@ -198,9 +201,9 @@ class TrackRecordingManager {
}
if (isIdle && trackPoint.isMoving()) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPointIfNewer(trackId, lastTrackPoint);
insertTrackPoint(track, trackPoint);
insertTrackPoint(trackId, trackPoint);
isIdle = false;
@@ -213,7 +216,7 @@ class TrackRecordingManager {
}
Track getTrack() {
return contentProviderUtils.getTrack(track.getId()); //Copy in memory; not via DB
return contentProviderUtils.getTrack(trackId);
}
//Functionality that uses this method should happen here.
@@ -247,34 +250,24 @@ class TrackRecordingManager {
/**
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
*
* @param track the track
* @param trackPoint the trackPoint
*/
private void insertTrackPointIfNewer(@NonNull Track track, @NonNull TrackPoint trackPoint) {
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
private void insertTrackPointIfNewer(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(trackId);
if (lastValidTrackPoint != null && trackPoint.getTime().equals(lastValidTrackPoint.getTime())) {
// Do not insert if inserted already
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid track point time.");
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
return;
}
insertTrackPoint(track, trackPoint);
insertTrackPoint(trackId, trackPoint);
}
/**
* Inserts a trackPoint.
*
* @param track the track
* @param trackPoint the trackPoint
*/
private void insertTrackPoint(@NonNull Track track, @NonNull TrackPoint trackPoint) {
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
try {
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrackStatistics(track.getId(), track.getTrackStatistics());
contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics());
} catch (SQLiteException e) {
/*
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
@@ -49,9 +49,7 @@ import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTask;
import de.dennisguse.opentracks.services.tasks.PeriodicTask;
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
import de.dennisguse.opentracks.services.tasks.VoiceAnnouncementManager;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.ExportUtils;
@@ -71,12 +69,12 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
public static final RecordingStatus STATUS_DEFAULT = new RecordingStatus(null, false);
public static final RecordingStatus STATUS_DEFAULT = RecordingStatus.notRecording();
public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null);
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
// The following variables are set in onCreate:
private PeriodicTaskExecutor voiceExecutor;
// The following variables are setFrequency in onCreate:
private VoiceAnnouncementManager voiceAnnouncementManager;
private TrackRecordingServiceNotificationManager notificationManager;
private TrackRecordingManager trackRecordingManager;
@@ -106,25 +104,26 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
voiceExecutor.setMetricUnits(metricUnits);
notificationManager.setMetricUnits(metricUnits);
}
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(sharedPreferences, context));
if (PreferencesUtils.isKey(context, R.string.voice_announcement_frequency_key, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency(sharedPreferences, context));
}
if (PreferencesUtils.isKey(context, new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementDistance(sharedPreferences, context));
}
handlerServer.onSharedPreferenceChanged(sharedPreferences, key);
trackPointCreator.onSharedPreferenceChanged(sharedPreferences, key);
trackRecordingManager.onSharedPreferenceChanged(sharedPreferences, key);
}
};
// The following variables are set when recording:
// The following variables are setFrequency when recording:
private WakeLock wakeLock;
private final Binder binder = new Binder();
private TrackPointCreator handlerServer; //TODO Move to TrackRecordingManager?
private TrackPointCreator trackPointCreator; //TODO Move to TrackRecordingManager?
private RecordingStatus recordingStatus;
private MutableLiveData<RecordingStatus> recordingStatusObservable;
@@ -143,9 +142,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
trackRecordingManager = new TrackRecordingManager(this);
handlerServer = new TrackPointCreator(this);
trackPointCreator = new TrackPointCreator(this);
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTask.Factory());
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
notificationManager = new TrackRecordingServiceNotificationManager(this);
@@ -168,8 +167,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
public void onDestroy() {
handler = null;
handlerServer.stop();
handlerServer = null;
trackPointCreator.stop();
trackPointCreator = null;
// Reverse order from onCreate
showNotification(false); //TODO Why?
@@ -178,9 +177,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
sharedPreferences = null;
try {
voiceExecutor.shutdown();
voiceAnnouncementManager.shutdown();
} finally {
voiceExecutor = null;
voiceAnnouncementManager = null;
}
// This should be the next to last operation
@@ -209,10 +208,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
return trackRecordingManager.insertMarker(name, category, description, photoUrl);
}
public void run(@NonNull PeriodicTask periodicTask) {
periodicTask.run(recordingStatus.getTrackId(), trackRecordingManager.getTrackStatistics());
}
/**
* Starts a new track.
*
@@ -225,8 +220,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
// Set recording status
Track.Id trackId = trackRecordingManager.start(handlerServer.createSegmentStartManual());
updateRecordingStatus(new RecordingStatus(trackId, false));
Track.Id trackId = trackRecordingManager.start(trackPointCreator.createSegmentStartManual());
updateRecordingStatus(RecordingStatus.record(trackId));
startRecording();
return trackId;
@@ -239,11 +234,11 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
* @param trackId the id of the track to be resumed.
*/
public void resumeTrack(Track.Id trackId) {
handlerServer.resetSensorData();
trackRecordingManager.resume(trackId, handlerServer.createSegmentStartManual());
trackPointCreator.resetSensorData();
trackRecordingManager.resume(trackId, trackPointCreator.createSegmentStartManual());
// Set recording status
updateRecordingStatus(new RecordingStatus(trackId, false));
updateRecordingStatus(RecordingStatus.record(trackId));
startRecording();
}
@@ -267,8 +262,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
startGps();
// Restore periodic tasks
voiceExecutor.restore();
voiceAnnouncementManager.restore(trackRecordingManager.getTrackStatistics());
}
public void tryStartGps() {
@@ -279,7 +273,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private void startGps() {
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
handlerServer.start(this);
trackPointCreator.start(this);
showNotification(true);
}
@@ -298,7 +292,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
updateRecordingStatus(STATUS_DEFAULT);
if (!wasPause) {
trackRecordingManager.end(handlerServer);
trackRecordingManager.end(trackPointCreator);
}
ExportUtils.postWorkoutExport(this, trackId, new ExportServiceResultReceiver(new Handler(), this));
@@ -314,9 +308,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
// Set recording status
updateRecordingStatus(new RecordingStatus(recordingStatus.getTrackId(), true));
updateRecordingStatus(recordingStatus.pause());
trackRecordingManager.pause(handlerServer);
trackRecordingManager.pause(trackPointCreator);
endRecording(false);
@@ -337,10 +331,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
// Shutdown periodic tasks
voiceExecutor.shutdown();
voiceAnnouncementManager.shutdown();
// Update instance variables
handlerServer.stop();
trackPointCreator.stop();
stopGps(trackStopped);
}
@@ -357,7 +351,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
void stopGps(boolean shutdown) {
if (!isRecording()) return;
handlerServer.stop();
trackPointCreator.stop();
showNotification(false);
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
if (shutdown) {
@@ -374,7 +368,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
trackRecordingManager.onNewTrackPoint(trackPoint, thresholdHorizontalAccuracy);
notificationManager.updateTrackPoint(this, trackRecordingManager.getTrackStatistics(), trackPoint, thresholdHorizontalAccuracy);
voiceExecutor.update(recordingStatus.getTrackId(), trackRecordingManager.getTrackStatistics());
}
@Override
@@ -391,10 +384,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
if (isRecording()) {
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingStatus.getTrackId());
int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackRecordingActivity.class)
.addNextIntent(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
.getPendingIntent(0, pendingIntentFlags);
notificationManager.updatePendingIntent(pendingIntent);
notificationManager.updateContent(getString(R.string.gps_starting));
@@ -402,9 +400,14 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
if (!isRecording() && isGpsStarted) {
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
int pendingIntentFlags = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntentFlags = PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addNextIntent(intent)
.getPendingIntent(0, 0);
.getPendingIntent(0, pendingIntentFlags);
notificationManager.updatePendingIntent(pendingIntent);
notificationManager.updateContent(getString(R.string.gps_starting));
@@ -416,21 +419,17 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
}
public Duration getTotalTime() {
return trackRecordingManager.getTrackStatistics().getTotalTime();
}
@Deprecated
@VisibleForTesting
public TrackPointCreator getHandlerServer() {
return handlerServer;
public TrackPointCreator getTrackPointCreator() {
return trackPointCreator;
}
public LiveData<GpsStatusValue> getGpsStatusObservable() {
return gpsStatusObservable;
}
public MutableLiveData<RecordingData> getRecordingDataObservable() {
public LiveData<RecordingData> getRecordingDataObservable() {
return recordingDataObservable;
}
@@ -442,14 +441,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
// Compute temporary track statistics using sensorData and update time.
TrackPointCreator localHandlerServer = this.handlerServer;
if (localHandlerServer == null) {
TrackPointCreator localTrackPointCreator = this.trackPointCreator;
VoiceAnnouncementManager localVoiceAnnouncementManager = this.voiceAnnouncementManager;
if (localTrackPointCreator == null || localVoiceAnnouncementManager == null) {
// when this happens, no recording is running and we should not send any notifications.
//TODO This implementation is not a good idea; rather solve the issue for this properly
return;
}
Pair<Track, Pair<TrackPoint, SensorDataSet>> data = trackRecordingManager.get(handlerServer);
Pair<Track, Pair<TrackPoint, SensorDataSet>> data = trackRecordingManager.get(trackPointCreator);
if (data == null) {
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
return;
@@ -457,6 +457,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
TrackPoint trackPoint = data.second.first;
egm2008CorrectionManager.correctAltitude(this, trackPoint);
localVoiceAnnouncementManager.update(data.first);
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
}
@@ -497,7 +499,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private final Track.Id trackId;
private final boolean paused;
public RecordingStatus(Track.Id trackId, boolean paused) {
private RecordingStatus(Track.Id trackId, boolean paused) {
this.trackId = trackId;
this.paused = paused;
}
@@ -518,20 +520,23 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
return isRecording() && !isPaused();
}
// TODO Use
// public RecordingStatus pause() {
// return new RecordingStatus(getTrackId(), true);
// }
//
// public RecordingStatus record(@NonNull Track.Id trackId) {
// return new RecordingStatus(trackId, false);
// }
private static RecordingStatus notRecording() {
return new RecordingStatus(null, false);
}
private static RecordingStatus record(@NonNull Track.Id trackId) {
return new RecordingStatus(trackId, false);
}
private RecordingStatus pause() {
return new RecordingStatus(getTrackId(), true);
}
public RecordingStatus stop() {
return STATUS_DEFAULT;
}
@NonNull
@Override
public String toString() {
return "RecordingStatus{" +
@@ -1,53 +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.services.handlers;
import java.time.Duration;
/**
* This is a simple location listener policy that will always dictate the same polling interval.
*
* @author Sandor Dornbush
*/
public class AbsoluteLocationListenerPolicy implements LocationListenerPolicy {
private final Duration interval;
/**
* Constructor.
*
* @param interval the interval to request for gps signal
*/
public AbsoluteLocationListenerPolicy(Duration interval) {
this.interval = interval;
}
@Override
public Duration getDesiredPollingInterval() {
return interval;
}
@Override
public int getMinDistance_m() {
return 0;
}
@Override
public void updateIdleTime(Duration idleTime) {
// Ignore
}
}
@@ -1,76 +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.services.handlers;
import java.time.Duration;
/**
* A {@link LocationListenerPolicy} that will change based on how long the user has been stationary.
* This policy will dictate a policy based on a min, max and idle time.
* The policy will dictate an interval bounded by min and max, and is half of the idle time.
*
* @author Sandor Dornbush
*/
public class AdaptiveLocationListenerPolicy implements LocationListenerPolicy {
private final Duration minInterval;
private final Duration maxInterval;
private final int minDistance_m;
// The time the user has been idle at the current location, in milliseconds.
private Duration idleTime;
/**
* Creates a policy that will be bounded by the given minInterval_ms and maxInterval_ms.
*
* @param minInterval the smallest interval this policy will dictate
* @param maxInterval the largest interval this policy will dictate
* @param minDistance_m the minimum distance
*/
public AdaptiveLocationListenerPolicy(Duration minInterval, Duration maxInterval, int minDistance_m) {
this.minInterval = minInterval;
this.maxInterval = maxInterval;
this.minDistance_m = minDistance_m;
}
/*
* Returns an interval half of the idle time, but bounded by minInterval and maxInterval.
*/
public Duration getDesiredPollingInterval() {
Duration desiredInterval = idleTime.dividedBy(2);
// Round to second to avoid setting the interval too often
desiredInterval = Duration.ofSeconds(desiredInterval.getSeconds());
if (minInterval.compareTo(desiredInterval) > 0) {
return minInterval;
} else if (maxInterval.compareTo(desiredInterval) < 0) {
return maxInterval;
}
return desiredInterval;
}
@Override
public int getMinDistance_m() {
return minDistance_m;
}
@Override
public void updateIdleTime(Duration newIdleTime) {
idleTime = newIdleTime;
}
}
@@ -71,8 +71,8 @@ class GpsStatus {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
thresholdHorizontalAccuracy = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
Duration minRecordingInterval = Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context));
signalLostThreshold = !minRecordingInterval.isNegative() ? SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval) : SIGNAL_LOST_THRESHOLD;
Duration minRecordingInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
signalLostThreshold = SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval);
gpsStatusHandler = new Handler();
}
@@ -102,13 +102,8 @@ class GpsStatus {
thresholdHorizontalAccuracy = value;
}
/**
* Method to change the lost threshold from outside.
*
* @param value Minimal recording interval preference value in seconds or an special value: -1, -2, 0.
*/
public void onMinRecordingIntervalChanged(int value) {
signalLostThreshold = value > 0 ? SIGNAL_LOST_THRESHOLD.plus(Duration.ofSeconds(value)) : SIGNAL_LOST_THRESHOLD;
public void onMinRecordingIntervalChanged(Duration value) {
signalLostThreshold = SIGNAL_LOST_THRESHOLD.plus(value);
}
/**
@@ -19,21 +19,20 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@VisibleForTesting(otherwise = 3)
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class LocationHandler implements LocationListener, GpsStatus.GpsStatusListener {
private final String TAG = LocationHandler.class.getSimpleName();
private LocationManager locationManager;
private final TrackPointCreator handlerServer;
private final TrackPointCreator trackPointCreator;
private GpsStatus gpsStatus;
private LocationListenerPolicy locationListenerPolicy;
private Duration currentRecordingInterval;
private Duration gpsInterval;
private Distance thresholdHorizontalAccuracy;
private TrackPoint lastTrackPoint;
public LocationHandler(TrackPointCreator handlerServer) {
this.handlerServer = handlerServer;
public LocationHandler(TrackPointCreator trackPointCreator) {
this.trackPointCreator = trackPointCreator;
}
public void onStart(@NonNull Context context, SharedPreferences sharedPreferences) {
@@ -64,37 +63,36 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
}
public void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
// Choose battery life over moving time accuracy.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(Duration.ofSeconds(30), Duration.ofSeconds(5), 5);
} else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
// Get all the updates.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(Duration.ofSeconds(1), Duration.ofSeconds(30), 0);
} else {
locationListenerPolicy = new AbsoluteLocationListenerPolicy(Duration.ofSeconds(minRecordingInterval));
}
boolean registerListener = false;
registerLocationListener();
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
registerListener = true;
gpsInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
if (gpsStatus != null) {
gpsStatus.onMinRecordingIntervalChanged(gpsInterval);
}
}
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
}
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
registerListener = true;
if (gpsStatus != null) {
gpsStatus.onMinRecordingIntervalChanged(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context));
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
gpsStatus.onRecordingDistanceChanged(gpsMinDistance);
}
}
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
if (gpsStatus != null) {
gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context));
}
if (registerListener) {
registerLocationListener();
}
}
/**
* Checks if location is valid and builds a track point that will be send through HandlerServer.
* Checks if location is valid and builds a track point that will be send through TrackPointCreator.
*
* @param location {@link Location} object.
*/
@@ -105,7 +103,7 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
return;
}
TrackPoint trackPoint = new TrackPoint(location, handlerServer.createNow());
TrackPoint trackPoint = new TrackPoint(location, trackPointCreator.createNow());
boolean isAccurate = trackPoint.fulfillsAccuracy(thresholdHorizontalAccuracy);
boolean isValid = LocationUtils.isValidLocation(location);
@@ -123,18 +121,8 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
return;
}
Duration idleTime = Duration.ofSeconds(0);
if (lastTrackPoint != null && trackPoint.getTime().isAfter(lastTrackPoint.getTime())) {
idleTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
}
locationListenerPolicy.updateIdleTime(idleTime);
if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) {
registerLocationListener();
}
lastTrackPoint = trackPoint;
handlerServer.onNewTrackPoint(trackPoint, thresholdHorizontalAccuracy);
trackPointCreator.onNewTrackPoint(trackPoint, thresholdHorizontalAccuracy);
}
@Override
@@ -161,9 +149,7 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
return;
}
try {
Duration interval = locationListenerPolicy.getDesiredPollingInterval();
currentRecordingInterval = interval;
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval.toMillis(), locationListenerPolicy.getMinDistance_m(), this);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsInterval.toMillis(), 0, this);
} catch (SecurityException e) {
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
}
@@ -181,6 +167,6 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
*/
@Override
public void onGpsStatusChanged(GpsStatusValue prevStatus, GpsStatusValue currentStatus) {
handlerServer.sendGpsStatus(currentStatus);
trackPointCreator.sendGpsStatus(currentStatus);
}
}
@@ -1,46 +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.services.handlers;
import java.time.Duration;
/**
* This is an interface for classes that will manage the location listener policy.
*
* @author Sandor Dornbush
*/
interface LocationListenerPolicy {
/**
* Returns the polling interval this policy would like at this moment.
*
* @return the polling interval
*/
Duration getDesiredPollingInterval();
/**
* Returns the minimum distance between updates.
*/
int getMinDistance_m();
/**
* Notifies the amount of time the user has been idle at his current location.
*
* @param idleTime the time that the user has been idle at his current location
*/
void updateIdleTime(Duration idleTime);
}
@@ -29,8 +29,6 @@ public class TrackPointCreator {
private Context context;
private final Callback service;
// Disabled to simplify testing and implementation of #822
// private ExecutorService serviceExecutor;
@NonNull
private Clock clock = Clock.systemUTC();
@@ -52,7 +50,6 @@ public class TrackPointCreator {
public void start(@NonNull Context context) {
this.context = context;
// serviceExecutor = Executors.newSingleThreadExecutor();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
locationHandler.onStart(context, sharedPreferences);
@@ -80,7 +77,7 @@ public class TrackPointCreator {
altitudeSumManager.reset();
}
//TODO TrackPoint should be created by HandlerServer; instead of in the TrackRecordingService.
//TODO TrackPoint should be created by TrackPointCreator; instead of in the TrackRecordingService.
@Deprecated
public SensorDataSet fill(TrackPoint trackPoint) {
SensorDataSet sensorDataSet = remoteSensorManager.fill(trackPoint);
@@ -100,11 +97,6 @@ public class TrackPointCreator {
public void stop() {
locationHandler.onStop();
// if (serviceExecutor != null) {
// serviceExecutor.shutdownNow();
// }
// serviceExecutor = null;
if (remoteSensorManager != null) {
remoteSensorManager.stop();
remoteSensorManager = null;
@@ -128,13 +120,8 @@ public class TrackPointCreator {
}
public void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
// if (serviceExecutor == null || serviceExecutor.isTerminated() || serviceExecutor.isShutdown()) {
// return;
// }
fillAndReset(trackPoint);
// serviceExecutor.execute(() -> service.newTrackPoint(trackPoint, thresholdHorizontalAccuracy));
service.newTrackPoint(trackPoint, thresholdHorizontalAccuracy);
}
@@ -1,46 +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.services.tasks;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* This is interface for a task that will be executed on some schedule.
*
* @author Sandor Dornbush
*/
public interface PeriodicTask {
/**
* Sets up this task for subsequent calls to the run method.
*/
void start();
/**
* This method will be called periodically.
*/
@Deprecated
void run(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics);
/**
* Shuts down this task and clean up resources.
*/
void shutdown();
}
@@ -1,181 +0,0 @@
/*
* Copyright 2010 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.services.tasks;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Execute a periodic task on a time or distance schedule.
*
* @author Sandor Dornbush
*/
public class PeriodicTaskExecutor {
private static final String TAG = PeriodicTaskExecutor.class.getSimpleName();
private final TrackRecordingService trackRecordingService;
private final PeriodicTaskFactory periodicTaskFactory;
private final int TASK_FREQUENCY_OFF;
/**
* The task frequency.
* A positive value is a time frequency (minutes).
* A negative value is a distance frequency (km or mi).
* A zero value is to turn off periodic task.
*/
private int taskFrequency;
private PeriodicTask periodicTask;
// Time periodic task executor
private TimerTaskExecutor timerTaskExecutor = null;
private boolean metricUnits;
private TrackStatistics trackStatistics;
private Distance nextTaskDistance = Distance.of(Double.MAX_VALUE);
public PeriodicTaskExecutor(@NonNull TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
this.trackRecordingService = trackRecordingService;
this.periodicTaskFactory = periodicTaskFactory;
TASK_FREQUENCY_OFF = Integer.parseInt(trackRecordingService.getString(R.string.frequency_off));
taskFrequency = TASK_FREQUENCY_OFF;
}
/**
* Restores the executor.
*/
public void restore() {
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) {
Log.d(TAG, "Not recording or paused.");
return;
}
if (!isTimeFrequency() && timerTaskExecutor != null) {
timerTaskExecutor.shutdown();
timerTaskExecutor = null;
}
if (taskFrequency == TASK_FREQUENCY_OFF) {
Log.d(TAG, "Task frequency is off.");
return;
}
periodicTask = periodicTaskFactory.create(trackRecordingService);
periodicTask.start();
if (isTimeFrequency()) {
if (timerTaskExecutor == null) {
timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService);
}
timerTaskExecutor.scheduleTask(Duration.ofMinutes(taskFrequency));
} else {
// For distance periodic task
updateNextTaskDistance();
}
}
/**
* Shuts down the executor.
*/
public void shutdown() {
if (periodicTask != null) {
periodicTask.shutdown();
periodicTask = null;
}
if (timerTaskExecutor != null) {
timerTaskExecutor.shutdown();
timerTaskExecutor = null;
}
}
/**
* Updates the executor.
*/
public void update(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) {
if (!isDistanceFrequency() || periodicTask == null) {
return;
}
if (trackStatistics.getTotalDistance().greaterThan(nextTaskDistance)) {
periodicTask.run(trackId, trackStatistics);
this.trackStatistics = trackStatistics;
updateNextTaskDistance();
}
}
public void setTaskFrequency(int taskFrequency) {
this.taskFrequency = taskFrequency;
restore();
}
public void setMetricUnits(boolean metricUnits) {
this.metricUnits = metricUnits;
updateNextTaskDistance();
}
private void updateNextTaskDistance() {
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused() || periodicTask == null) {
return;
}
if (!isDistanceFrequency()) {
nextTaskDistance = Distance.of(Double.MAX_VALUE);
Log.d(TAG, "SplitManager: Distance splits disabled.");
return;
}
if (trackStatistics == null) {
return;
}
nextTaskDistance = calculateNextTaskDistance(trackStatistics);
}
@VisibleForTesting
public Distance calculateNextTaskDistance(TrackStatistics trackStatistics) {
Distance distance = trackStatistics.getTotalDistance();
Distance announcementInterval = Distance.one(metricUnits).multipliedBy(Math.abs(taskFrequency));
int index = (int) (distance.dividedBy(announcementInterval));
return announcementInterval.multipliedBy(index + 1);
}
/**
* True if time frequency.
*/
private boolean isTimeFrequency() {
return taskFrequency > 0;
}
/**
* True if distance frequency.
*/
private boolean isDistanceFrequency() {
return taskFrequency < 0;
}
}
@@ -1,37 +0,0 @@
/*
* Copyright 2010 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.services.tasks;
import android.content.Context;
import androidx.annotation.NonNull;
/**
* An interface for classes that can create {@link PeriodicTask}.
*
* @author Sandor Dornbush
*/
interface PeriodicTaskFactory {
/**
* Creates a {@link PeriodicTask}.
*
* @return the task, or null if the task is not supported
*/
@NonNull
PeriodicTask create(Context context);
}
@@ -1,79 +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.services.tasks;
import java.time.Duration;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import de.dennisguse.opentracks.services.TrackRecordingService;
/**
* This class will periodically perform a task.
*
* @author Sandor Dornbush
*/
class TimerTaskExecutor {
private final PeriodicTask periodicTask;
private final TrackRecordingService trackRecordingService;
private TimerTask timerTask;
private Timer timer;
public TimerTaskExecutor(PeriodicTask periodicTask, TrackRecordingService trackRecordingService) {
this.periodicTask = periodicTask;
this.trackRecordingService = trackRecordingService;
}
/**
* Schedules the periodic task.
*/
void scheduleTask(Duration interval) {
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) {
return;
}
shutdown();
periodicTask.start();
timerTask = new TimerTask() {
@Override
public void run() {
trackRecordingService.run(periodicTask);
}
};
timer = new Timer(TimerTaskExecutor.class.getSimpleName());
//TODO Simplify: far too complicated for it's purpose
long next = System.currentTimeMillis() + interval.toMillis() - (trackRecordingService.getTotalTime().toMillis() % interval.toMillis());
timer.scheduleAtFixedRate(timerTask, new Date(next), interval.toMillis());
}
void shutdown() {
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
}
periodicTask.shutdown();
}
}
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
@@ -28,6 +29,7 @@ import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -38,13 +40,15 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
/**
* This class will periodically announce the user's {@link TrackStatistics}.
* This class will announce the user's {@link TrackStatistics}.
*
* @author Sandor Dornbush
*/
public class AnnouncementPeriodicTask implements PeriodicTask {
public class VoiceAnnouncement {
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
public final static int AUDIO_STREAM = TextToSpeech.Engine.DEFAULT_STREAM;
private static final String TAG = VoiceAnnouncement.class.getSimpleName();
private final Context context;
@@ -79,7 +83,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
int result = audioManager.requestAudioFocus(audioFocusChangeListener, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
int result = audioManager.requestAudioFocus(audioFocusChangeListener, AUDIO_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "Failed to request audio focus.");
}
@@ -107,14 +111,14 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
private boolean ttsReady = false;
AnnouncementPeriodicTask(Context context) {
private MediaPlayer ttsFallback;
VoiceAnnouncement(Context context) {
this.context = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
contentProviderUtils = new ContentProviderUtils(context);
}
@Override
public void start() {
Log.d(TAG, "Start");
@@ -126,10 +130,14 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
ttsInitStatus = status;
});
}
if (ttsFallback == null) {
ttsFallback = MediaPlayer.create(context, R.raw.tts_fallback);
ttsFallback.setAudioStreamType(AUDIO_STREAM);
ttsFallback.setLooping(false);
}
}
@Override
public void run(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) {
public void announce(@NonNull Track track) {
synchronized (this) {
if (!ttsReady) {
ttsReady = ttsInitStatus == TextToSpeech.SUCCESS;
@@ -137,45 +145,49 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
onTtsReady();
}
}
if (!ttsReady) {
Log.i(TAG, "TTS not ready.");
return;
}
}
if (Arrays.asList(AudioManager.MODE_IN_CALL, AudioManager.MODE_IN_COMMUNICATION)
.contains(audioManager.getMode())) {
Log.i(TAG, "Speech is not allowed at this time.");
Log.i(TAG, "Announcement is not allowed at this time.");
return;
}
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
Log.i(TAG, "It doesn't exists a track with trackid = " + track);
if (!ttsReady) {
Log.i(TAG, "TTS not ready/available, just generating a tone.");
ttsFallback.seekTo(0);
ttsFallback.start();
return;
}
String category = track.getCategory();
boolean isMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, track.getCategory());
Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
//TODO Do not load all trackpoints for every announcement
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.one(isMetricUnits), minGPSDistance);
intervalStatistics.addTrackPoints(trackPointIterator);
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, isMetricUnits, isReportSpeed, lastInterval);
speakAnnouncement(announcement);
String announcement = AnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), isMetricUnits, isReportSpeed, lastInterval);
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
}
@Override
public void shutdown() {
if (tts != null) {
tts.shutdown();
tts = null;
}
if (ttsFallback != null) {
ttsFallback.release();
ttsFallback = null;
}
sharedPreferences = null;
}
@@ -194,23 +206,4 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate(PreferencesUtils.getSharedPreferences(context), context));
tts.setOnUtteranceProgressListener(utteranceListener);
}
private void speakAnnouncement(String announcement) {
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
}
/**
* A {@link PeriodicTaskFactory} for text-to-speech announcement periodic task.
*
* @author Rodrigo Damazio
*/
public static class Factory implements PeriodicTaskFactory {
@Override
@NonNull
public PeriodicTask create(Context context) {
return new AnnouncementPeriodicTask(context);
}
}
}
@@ -0,0 +1,148 @@
/*
* Copyright 2010 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.services.tasks;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Execute a periodic task on a time or distance schedule.
*
* @author Sandor Dornbush
*/
public class VoiceAnnouncementManager {
private static final String TAG = VoiceAnnouncementManager.class.getSimpleName();
private final TrackRecordingService trackRecordingService;
private VoiceAnnouncement voiceAnnouncement;
private TrackStatistics trackStatistics;
private static final Distance DISTANCE_OFF = Distance.of(Double.MAX_VALUE);
private Distance distanceFrequency = DISTANCE_OFF;
@NonNull
private Distance nextTotalDistance = DISTANCE_OFF;
private static final Duration TOTALTIME_OFF = Duration.ofMillis(Long.MAX_VALUE);
private Duration totalTimeFrequency = TOTALTIME_OFF;
@NonNull
private Duration nextTotalTime = TOTALTIME_OFF;
public VoiceAnnouncementManager(@NonNull TrackRecordingService trackRecordingService) {
this.trackRecordingService = trackRecordingService;
}
public void restore(@Nullable TrackStatistics trackStatistics) {
voiceAnnouncement = new VoiceAnnouncement(trackRecordingService);
voiceAnnouncement.start();
this.trackStatistics = trackStatistics;
if (!totalTimeFrequency.isZero()) {
nextTotalTime = calculateNextDuration();
}
if (!distanceFrequency.isZero()) {
nextTotalDistance = calculateNextTaskDistance();
}
}
public void update(@NonNull Track track) {
if (voiceAnnouncement == null) {
Log.e(TAG, "Cannot update when in status shutdown.");
return;
}
boolean announce = false;
this.trackStatistics = track.getTrackStatistics();
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
nextTotalDistance = calculateNextTaskDistance();
announce = true;
}
if (!trackStatistics.getTotalTime().minus(nextTotalTime).isNegative()) {
nextTotalTime = calculateNextDuration();
announce = true;
}
if (announce) {
voiceAnnouncement.announce(track);
}
}
public void shutdown() {
if (voiceAnnouncement != null) {
voiceAnnouncement.shutdown();
voiceAnnouncement = null;
}
}
public void setFrequency(Duration frequency) {
this.totalTimeFrequency = frequency;
restore(this.trackStatistics);
}
public void setFrequency(Distance frequency) {
this.distanceFrequency = frequency;
restore(this.trackStatistics);
}
@VisibleForTesting
public Distance calculateNextTaskDistance() {
if (trackStatistics == null) {
return DISTANCE_OFF;
}
Distance distance = trackStatistics.getTotalDistance();
int index = (int) (distance.dividedBy(distanceFrequency));
return distanceFrequency.multipliedBy(index + 1);
}
private Duration calculateNextDuration() {
if (trackStatistics == null) {
return TOTALTIME_OFF;
}
Duration totalTime = trackStatistics.getTotalTime();
Duration intervalMod = Duration.ofMillis(trackStatistics.getTotalTime().toMillis() % totalTimeFrequency.toMillis());
return totalTime.plus(totalTimeFrequency.minus(intervalMod));
}
@VisibleForTesting
@NonNull
public Duration getNextTotalTime() {
return nextTotalTime;
}
@VisibleForTesting
@NonNull
public Distance getNextTotalDistance() {
return nextTotalDistance;
}
}
@@ -14,11 +14,7 @@ final class PreferenceHelper {
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
if (value == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
entries[i] = context.getString(R.string.value_adapt_accuracy);
} else if (value == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
entries[i] = context.getString(R.string.value_adapt_battery_life);
} else if (value == PreferencesUtils.getMinRecordingIntervalDefault(context)) {
if (value == PreferencesUtils.getMinRecordingIntervalDefault(context).getSeconds()) {
entries[i] = context.getString(R.string.value_smallest_recommended);
} else {
entries[i] = value < 60 ? context.getString(R.string.value_integer_second, value) : context.getString(R.string.value_integer_minute, value / 60);
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.settings;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
import androidx.fragment.app.FragmentActivity;
@@ -3,12 +3,10 @@ package de.dennisguse.opentracks.settings;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.documentfile.provider.DocumentFile;
@@ -98,9 +96,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
public static class PrefsFragment extends PreferenceFragmentCompat {
private TextToSpeech tts;
private int ttsInitStatus;
private SharedPreferences sharedPreferences;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
@@ -133,23 +128,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
// Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData.
private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog;
private Preference announcementsFrequency;
private Preference announcementsSpeed;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ttsInitStatus = TextToSpeech.ERROR;
tts = new TextToSpeech(getContext(), status -> {
ttsInitStatus = status;
Log.i(TAG, "TextToSpeech initialized with status " + status);
if (announcementsFrequency != null && announcementsSpeed != null) {
updateVoiceAnnouncements();
}
});
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
@@ -203,7 +181,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
findPreference(getString(R.string.min_recording_interval_key))
.setSummaryProvider(
preference -> {
Duration interval = Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(sharedPreferences, getContext()));
Duration interval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, getContext());
return getString(R.string.settings_recording_location_frequency_summary, getString(R.string.value_integer_second, interval.getSeconds()));
}
);
@@ -214,10 +192,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
startActivity(intent);
return true;
});
announcementsFrequency = findPreference(getString(R.string.voice_frequency_key));
announcementsSpeed = findPreference(getString(R.string.voice_speed_rate_key));
updateVoiceAnnouncements();
}
@Override
@@ -278,9 +252,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
super.onDestroy();
trackRecordingServiceConnection.unbind(getContext());
sharedPreferences = null;
announcementsFrequency = null;
announcementsSpeed = null;
tts = null;
}
public void setDefaultActivity(String iconValue) {
@@ -313,8 +284,11 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
ListPreference voiceFrequency = findPreference(getString(R.string.voice_announcement_frequency_key));
voiceFrequency.setEntries(StringUtils.getAnnouncementFrequency(getActivity()));
ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key));
voiceDistance.setEntries(StringUtils.getAnnouncementDistance(getActivity(), metricUnits));
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
@@ -374,12 +348,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
});
}
private void updateVoiceAnnouncements() {
boolean enabled = ttsInitStatus == TextToSpeech.SUCCESS;
announcementsFrequency.setEnabled(enabled);
announcementsSpeed.setEnabled(enabled);
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
this.recordingStatus = status;
if (!status.isRecording() && isAdded()) {
@@ -1,5 +1,6 @@
package de.dennisguse.opentracks.settings.bluetooth;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
@@ -8,6 +9,8 @@ import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelUuid;
import android.text.TextUtils;
@@ -15,7 +18,10 @@ import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
@@ -143,13 +149,39 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
List<ParcelUuid> serviceUUIDs = getArguments().getParcelableArrayList(ARG_BLE_SERVICE_UUIDS);
// Don't know why: need to load the drawable _twice_, so that animation is actually started.
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
bluetoothIcon.start();
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && (
ContextCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
)
) {
ActivityResultLauncher<String[]> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), granted -> {
if (!granted.containsValue(false)) {
startBluetoothScan();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_SCAN) || shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show();
dismiss();
} else {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show();
dismiss();
}
});
requestPermissionLauncher.launch(new String[]{Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN});
} else {
startBluetoothScan();
}
}
private void startBluetoothScan() {
List<ParcelUuid> serviceUUIDs = getArguments().getParcelableArrayList(ARG_BLE_SERVICE_UUIDS);
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Log.w(TAG, "Bluetooth adapter is present or not enabled.");
@@ -178,7 +178,9 @@ public class TrackStatisticsUpdater {
}
// Update moving time
currentSegment.addMovingTime(movingTime);
if (lastTrackPoint.isMoving()) {
currentSegment.addMovingTime(movingTime);
}
// Update max speed
if (trackPoint.hasSpeed() && lastTrackPoint.hasSpeed()) {
@@ -271,6 +273,7 @@ public class TrackStatisticsUpdater {
return true;
}
@NonNull
@Override
public String toString() {
return "TrackStatisticsUpdater{" +
@@ -28,6 +28,8 @@ import androidx.annotation.VisibleForTesting;
import androidx.documentfile.provider.DocumentFile;
import androidx.preference.PreferenceManager;
import java.time.Duration;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
@@ -88,6 +90,15 @@ public class PreferencesUtils {
return key == null || key.equals(getKey(context, keyId));
}
public static boolean isKey(Context context, int[] keyIds, String key) {
for(int keyId : keyIds) {
if (isKey(context, keyId, key)) {
return true;
}
}
return false;
}
private static boolean getBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean defaultValue) {
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
}
@@ -214,13 +225,23 @@ public class PreferencesUtils {
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static int getVoiceFrequency(SharedPreferences sharedPreferences, Context context) {
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
public static Duration getVoiceAnnouncementFrequency(SharedPreferences sharedPreferences, Context context) {
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_announcement_frequency_default));
int value = getInt(sharedPreferences, context, R.string.voice_announcement_frequency_key, DEFAULT);
return Duration.ofSeconds(value);
}
/**
* @return Result depends on isMetricUnits
*/
public static Distance getVoiceAnnouncementDistance(SharedPreferences sharedPreferences, Context context) {
final float DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_announcement_distance_default));
float value = getFloat(sharedPreferences, context, R.string.voice_announcement_distance_key, DEFAULT);
return Distance.one(isMetricUnits(sharedPreferences, context)).multipliedBy(value);
}
public static float getVoiceSpeedRate(SharedPreferences sharedPreferences, Context context) {
final float DEFAULT = Float.parseFloat(context.getResources().getString(R.string.voice_frequency_default));
final float DEFAULT = Float.parseFloat(context.getResources().getString(R.string.voice_speed_rate_default));
return getFloat(sharedPreferences, context, R.string.voice_speed_rate_key, DEFAULT);
}
@@ -241,22 +262,19 @@ public class PreferencesUtils {
return Distance.of(getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE));
}
//TODO Duration
public static int getMinRecordingInterval(SharedPreferences sharedPreferences, Context context) {
final int MIN_RECORDING_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
return getInt(sharedPreferences, context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
public static Duration getMinRecordingInterval(SharedPreferences sharedPreferences, Context context) {
final Duration MIN_RECORDING_INTERVAL = getMinRecordingIntervalDefault(context);
Duration interval = Duration.ofSeconds(getInt(sharedPreferences, context, R.string.min_recording_interval_key, (int) MIN_RECORDING_INTERVAL.getSeconds()));
if (interval.isNegative()) {
// Due to removal of adaptive listener policy; used -1, and -2
interval = getMinRecordingIntervalDefault(context);
}
return interval;
}
public static int getMinRecordingIntervalAdaptAccuracy(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_adapt_accuracy));
}
public static int getMinRecordingIntervalAdaptBatteryLife(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_adapt_battery_life));
}
public static int getMinRecordingIntervalDefault(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
public static Duration getMinRecordingIntervalDefault(Context context) {
return Duration.ofSeconds(Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
}
public static Distance getThresholdHorizontalAccuracy(SharedPreferences sharedPreferences, Context context) {
@@ -292,23 +292,29 @@ public class StringUtils {
}
}
/**
* Gets the frequency display options.
*
* @param context the context
* @param metricUnits true to display in metric units
*/
public static String[] getFrequencyOptions(Context context, boolean metricUnits) {
String[] values = context.getResources().getStringArray(R.array.frequency_values);
public static String[] getAnnouncementFrequency(Context context) {
String[] values = context.getResources().getStringArray(R.array.voice_announcement_frequency_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
if (context.getString(R.string.frequency_off).equals(values[i])) {
if (context.getString(R.string.announcement_off).equals(values[i])) {
options[i] = context.getString(R.string.value_off);
} else if (value < 0) {
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, Math.abs(value));
} else {
options[i] = context.getString(R.string.value_integer_minute, value);
int value = Integer.parseInt(values[i]);
options[i] = context.getString(R.string.value_integer_minute, Duration.ofSeconds(value).toMinutes());
}
}
return options;
}
public static String[] getAnnouncementDistance(Context context, boolean metricUnits) {
String[] values = context.getResources().getStringArray(R.array.voice_announcement_distance_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (context.getString(R.string.announcement_off).equals(values[i])) {
options[i] = context.getString(R.string.value_off);
} else {
int value = Integer.parseInt(values[i]);
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, value);
}
}
return options;
@@ -330,9 +336,24 @@ public class StringUtils {
}
public static String formatAltitude(Context context, Float altitude_m, boolean metricUnits) {
Pair<String, String> distanceParts = getAltitudeParts(context, altitude_m, metricUnits);
Pair<String, String> altitudeParts = getAltitudeParts(context, altitude_m, metricUnits);
return context.getString(R.string.altitude_with_unit, distanceParts.first, distanceParts.second);
return context.getString(R.string.altitude_with_unit, altitudeParts.first, altitudeParts.second);
}
/**
* @return the formatted altitudeDifference_m (or null) and it's unit as {@link Pair}
*/
public static Pair<String, String> getAltitudeChangeParts(Context context, Float altitudeDifference_m, boolean metricUnits) {
String formattedValue = altitudeDifference_m != null ? String.valueOf((int) Distance.of(altitudeDifference_m).toM_FT(metricUnits)) : context.getString(R.string.value_unknown);
String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
return new Pair<>(formattedValue, unit);
}
public static String formatAltitudeChange(Context context, Float altitude_m, boolean metricUnits) {
Pair<String, String> altitudeParts = getAltitudeChangeParts(context, altitude_m, metricUnits);
return context.getString(R.string.altitude_with_unit, altitudeParts.first, altitudeParts.second);
}
public static String valueInParentheses(String text) {
@@ -1,13 +0,0 @@
package de.dennisguse.opentracks.util;
import android.speech.tts.TextToSpeech;
public class TTSUtils {
private TTSUtils() {
}
public static int getTTSStream() {
return TextToSpeech.Engine.DEFAULT_STREAM;
}
}
@@ -53,7 +53,7 @@ public class StatisticDataBuilder {
boolean reportSpeed = fieldKey.equals("speed");
title = fieldKey.equals("speed") ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null && sensorDataSet.getCyclingDistanceSpeed().hasValue() && sensorDataSet.getCyclingDistanceSpeed().isRecent()) {
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null && sensorDataSet.getCyclingDistanceSpeed().hasValue()) {
valueAndUnit = StringUtils.getSpeedParts(context, sensorDataSet.getCyclingDistanceSpeed().getValue().getSpeed(), metricUnits, reportSpeed);
description = context.getString(R.string.description_speed_source_sensor, sensorDataSet.getCyclingDistanceSpeed().getSensorNameOrAddress());
} else {
@@ -84,10 +84,10 @@ public class StatisticDataBuilder {
description = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? context.getString(latestTrackPoint.getAltitude().getLabelId()) : null;
valueAndUnit = StringUtils.getAltitudeParts(context, altitude, metricUnits);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeGain(), metricUnits);
valueAndUnit = StringUtils.getAltitudeChangeParts(context, recordingData.getTrackStatistics().getTotalAltitudeGain(), metricUnits);
title = context.getString(R.string.stats_gain);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeLoss(), metricUnits);
valueAndUnit = StringUtils.getAltitudeChangeParts(context, recordingData.getTrackStatistics().getTotalAltitudeLoss(), metricUnits);
title = context.getString(R.string.stats_loss);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
title = context.getString(R.string.stats_coordinates);
@@ -99,7 +99,7 @@ public class StatisticDataBuilder {
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
title = context.getString(R.string.stats_sensors_heart_rate);
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue()) {
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().getValue());
description = sensorDataSet.getHeartRate().getSensorNameOrAddress();
} else {
@@ -108,7 +108,7 @@ public class StatisticDataBuilder {
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
title = context.getString(R.string.stats_sensors_cadence);
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue()) {
valueAndUnit = StringUtils.getCadenceParts(context, sensorDataSet.getCyclingCadence().getValue());
description = sensorDataSet.getCyclingCadence().getSensorNameOrAddress();
} else {
@@ -117,7 +117,7 @@ public class StatisticDataBuilder {
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_power_key))) {
title = context.getString(R.string.stats_sensors_power);
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
@@ -142,13 +142,13 @@ public class StatisticDataBuilder {
if (sensorDataSet == null) {
return sensorDataList;
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue()) {
sensorDataList.add(build(context, recordingData, "heart_rate", true, metricUnits));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue()) {
sensorDataList.add(build(context, recordingData, "cadence", true, metricUnits));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
sensorDataList.add(build(context, recordingData, "power", true, metricUnits));
}
Binary file not shown.
-3
View File
@@ -177,7 +177,6 @@ limitations under the License.
<string name="menu_show_on_map">إظهار على الخريطة</string>
<string name="menu_start_gps">بدء GPS</string>
<string name="menu_stop_gps">إيقاف GPS</string>
<string name="menu_voice_frequency">معدل الصوت</string>
<!-- OpenTracks -->
<string name="recording_service">خدمة التسجيل</string>
<!-- Search -->
@@ -268,8 +267,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">د/كم</string>
<string name="unit_minute_per_mile">دقيقة/ميل</string>
<!-- Value -->
<string name="value_adapt_accuracy">تكييف من أجل الدقة</string>
<string name="value_adapt_battery_life">التكيف بحسب عمر البطارية</string>
<string name="value_integer_feet">%1$d قدم</string>
<string name="value_integer_feet_excellent_gps">%1$d قدم (إشارة نظام تحديد المواقع ممتازة)</string>
<string name="value_integer_feet_recommended">%1$d قدم (موصى به)</string>
-3
View File
@@ -144,7 +144,6 @@
<string name="menu_show_on_map">Show on map</string>
<string name="menu_start_gps">Start GPS</string>
<string name="menu_stop_gps">Stop GPS</string>
<string name="menu_voice_frequency">Voice frequency</string>
<string name="recording_service">Recording service</string>
<string name="sensor_state_battery">Battery level</string>
<string name="sensor_state_cadence">Cadence</string>
@@ -216,8 +215,6 @@
<string name="unit_mile_per_hour">mi/h</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<string name="value_adapt_accuracy">Adapt for accuracy</string>
<string name="value_adapt_battery_life">Adapt for battery life</string>
<string name="value_integer_feet">%1$d ft</string>
<string name="value_integer_feet_excellent_gps">%1$d ft (excellent GPS)</string>
<string name="value_integer_feet_recommended">%1$d ft (recommended)</string>
+28 -3
View File
@@ -145,7 +145,6 @@
<string name="menu_show_on_map">Mostrar en el mapa</string>
<string name="menu_start_gps">Iniciar GPS</string>
<string name="menu_stop_gps">Detener GPS</string>
<string name="menu_voice_frequency">Frecuencia de voz</string>
<string name="menu_resume_track">Reanudar ruta</string>
<string name="recording_service">Servicio de grabación</string>
<string name="sensor_state_battery">Nivel de la batería</string>
@@ -219,8 +218,6 @@
<string name="unit_mile_per_hour">millas/hora</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/milla</string>
<string name="value_adapt_accuracy">Adaptar para precisión</string>
<string name="value_adapt_battery_life">Adaptar a la duración de la batería</string>
<string name="value_integer_feet">%1$d pies</string>
<string name="value_integer_feet_excellent_gps">%1$d pies (señal de GPS excelente)</string>
<string name="value_integer_feet_recommended">%1$d pies (recomendado)</string>
@@ -453,4 +450,32 @@
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="track_detail_intervals_tab">Intervalos</string>
<string name="interval_list_empty_message">Aún no hay ningún intervalo</string>
<string name="settings_recording_customize_layout_title">Personalice su pantalla de grabación</string>
<string name="settings_recording_customize_layout_select_columns">Datos por columna</string>
<string name="settings_announcements_totaltime_frequency">Intervalo de tiempo</string>
<string name="settings_announcements_distance_frequency">Intervalo de distancia</string>
<string name="settings_layout_reset">Restablecer disposición personalizada</string>
<string name="settings_announcements">Anuncios</string>
<string name="settings_layout_reset_confirm_title">¿Restablecer todas las disposiciones\?</string>
<string name="settings_layout_reset_done">Todas las disposiciones se han revertido a los valores por defecto</string>
<string name="settings_all_reset">Restablecer todos los ajustes</string>
<string name="stats_sensors_heart_rate">Ritmo cardíaco</string>
<string name="stats_sensors_cadence">Cadencia</string>
<string name="stats_sensors_power">Poder</string>
<string name="field_set_primary">Primario</string>
<string name="field_remove_from_layout">Remover</string>
<string name="field_set_secondary">Secundario</string>
<string name="description_speed_source_gps">GPS</string>
<string name="menu_voice_rate">Velocidad de la voz</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="settings_layout_reset_confirm_message">Todos los ajustes de disposición se revertirán a los valores por defecto. Esto no eliminará ningún recorrido en el dispositivo.</string>
<string name="settings_sensor_running_speed_and_cadence">Correr: Distancia, velocidad y cadencia</string>
<string name="stats_coordinates">Latitud/Longitud</string>
<string name="location_coordinate">%1$s°</string>
<string name="description_speed_source_sensor">Sensor: %1$s</string>
<string name="title_activity_settings_custom_layout">Personalizar distribución de grabación</string>
<string name="generic_choose_an_option">Selecciona una opción</string>
<string name="settings_sensor_cycling_distance_speed">Ciclismo: Distancia y velocidad</string>
<string name="settings_sensor_cycling_cadence">Ciclismo: Cadencia</string>
<string name="track_delete_not_recording">Este recorrido no puede ser eliminado porque se está grabando actualmente.</string>
</resources>
-3
View File
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">Показване на картата</string>
<string name="menu_start_gps">Стартиране на GPS</string>
<string name="menu_stop_gps">Спиране на GPS</string>
<string name="menu_voice_frequency">Честота на гласа</string>
<!-- OpenTracks -->
<string name="recording_service">Услуга за запис</string>
<!-- Search -->
@@ -265,8 +264,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">мин/км</string>
<string name="unit_minute_per_mile">мин/мил</string>
<!-- Value -->
<string name="value_adapt_accuracy">Адаптиране за точност</string>
<string name="value_adapt_battery_life">Адаптиране спрямо батерията</string>
<string name="value_integer_feet">%1$d фт</string>
<string name="value_integer_feet_excellent_gps">%1$d фт (отличен GPS)</string>
<string name="value_integer_feet_recommended">%1$d фт (препоръчително)</string>
-3
View File
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">Mostra-ho al mapa</string>
<string name="menu_start_gps">Activa el GPS</string>
<string name="menu_stop_gps">Desactiva el GPS</string>
<string name="menu_voice_frequency">Freqüència de veu</string>
<!-- OpenTracks -->
<string name="recording_service">Servei d\'enregistrament</string>
<!-- Search -->
@@ -263,8 +262,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/milles</string>
<!-- Value -->
<string name="value_adapt_accuracy">Adapta\'n la precisió</string>
<string name="value_adapt_battery_life">Adapta a la durada de la bateria</string>
<string name="value_integer_feet">%1$d peus</string>
<string name="value_integer_feet_excellent_gps">%1$d peus (senyal de GPS excel·lent)</string>
<string name="value_integer_feet_recommended">%1$d peus (recomanat)</string>
+6 -3
View File
@@ -227,7 +227,6 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
<string name="menu_show_on_map">Zobrazit na mapě</string>
<string name="menu_start_gps">Spustit GPS</string>
<string name="menu_stop_gps">Ukončit GPS</string>
<string name="menu_voice_frequency">Frekvence hlasových oznámení</string>
<string name="menu_resume_track">Obnovit měření</string>
<!-- OpenTracks -->
<string name="recording_service">Služba pro záznam</string>
@@ -333,8 +332,6 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/míle</string>
<!-- Value -->
<string name="value_adapt_accuracy">Upřednostnit přesnost</string>
<string name="value_adapt_battery_life">Upřednostnit výdrži baterie</string>
<string name="value_integer_feet">%1$d stop</string>
<string name="value_integer_feet_excellent_gps">%1$d stop (vynikající signál GPS)</string>
<string name="value_integer_feet_recommended">%1$d stop (doporučeno)</string>
@@ -521,4 +518,10 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
<string name="settings_sensor_cycling_distance_speed">Jízda na kole: vzdálenost a rychlost</string>
<string name="settings_sensor_cycling_cadence">Cyklistika: kadence</string>
<string name="settings_sensor_running_speed_and_cadence">Běh: vzdálenost, rychlost a kadence</string>
<string name="settings_recording_customize_layout_title">Přizpůsobit vzhled měření</string>
<string name="settings_recording_customize_layout_select_columns">Počet polí na řádek</string>
<string name="title_activity_settings_custom_layout">Přizpůsobit vzhled měření</string>
<string name="field_set_primary">První</string>
<string name="field_set_secondary">Druhý</string>
<string name="field_remove_from_layout">Odstranit</string>
</resources>
-3
View File
@@ -229,7 +229,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
<string name="menu_show_on_map">Vis på kort</string>
<string name="menu_start_gps">Start GPS</string>
<string name="menu_stop_gps">Stop GPS</string>
<string name="menu_voice_frequency">Stemmefrekvens</string>
<string name="menu_resume_track">Genoptag sporet</string>
<!-- OpenTracks -->
<string name="recording_service">Registreringstjeneste</string>
@@ -335,8 +334,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
<string name="unit_minute_per_kilometer">min./km</string>
<string name="unit_minute_per_mile">min./mil</string>
<!-- Value -->
<string name="value_adapt_accuracy">Juster nøjagtighed</string>
<string name="value_adapt_battery_life">Tilpas til batterilevetid</string>
<string name="value_integer_feet">%1$d fod</string>
<string name="value_integer_feet_excellent_gps">%1$d fod (fremragende GPS)</string>
<string name="value_integer_feet_recommended">%1$d fod (anbefales)</string>
+5 -6
View File
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +12,8 @@ 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.
--><resources>
-->
<resources>
<string name="about_preference_title">Über OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> ist eine Sport-Tracking-Anwendung, die Ihre Privatsphäre vollständig respektiert.</string>
<string name="about_url">Projekt-Website: <a href="%1$s">%1$s</a></string>
@@ -230,12 +230,13 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="menu_show_on_map">Auf Karte anzeigen</string>
<string name="menu_start_gps">GPS aktivieren</string>
<string name="menu_stop_gps">GPS deaktivieren</string>
<string name="menu_voice_frequency">Ansagehäufigkeit</string>
<string name="menu_resume_track">Track fortsetzen</string>
<!-- OpenTracks -->
<string name="recording_service">Dienst für Aufzeichnung</string>
<!-- Permission -->
<string name="permission_gps_failed">OpenTracks benötigt eine Berechtigung zur Nutzung von GPS.</string>
<string name="permission_bluetooth_failed">OpenTracks benötigt eine Berechtigung zur Nutzung von Bluetooth.</string>
<string name="permission_bluetooth_failed_rejected">Du musst OpenTracks die Berechtigung zum Zugriff auf Geräte in der Nähe manuell in den Systemeinstellungen geben.</string>
<!-- Search -->
<!-- Sensor State -->
<string name="sensor_state_battery">Akkustatus</string>
@@ -340,8 +341,6 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">Min./Meile</string>
<!-- Value -->
<string name="value_adapt_accuracy">Genauigkeit anpassen</string>
<string name="value_adapt_battery_life">An Akkulaufzeit anpassen</string>
<string name="value_integer_feet">%1$d Fuß</string>
<string name="value_integer_feet_excellent_gps">%1$d Fuß (ausgezeichnetes GPS-Signal)</string>
<string name="value_integer_feet_recommended">%1$d Fuß (empfohlen)</string>
-3
View File
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">Εμφάνιση στον χάρτη</string>
<string name="menu_start_gps">Έναρξη GPS</string>
<string name="menu_stop_gps">Διακοπή GPS</string>
<string name="menu_voice_frequency">Συχνότητα ανακοινώσεων</string>
<!-- OpenTracks -->
<string name="recording_service">Υπηρεσία καταγραφής</string>
<!-- Search -->
@@ -264,8 +263,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">λεπτά / χιλ.</string>
<string name="unit_minute_per_mile">λεπτά/μίλι</string>
<!-- Value -->
<string name="value_adapt_accuracy">Προσαρμογή για ακρίβεια</string>
<string name="value_adapt_battery_life">Καλύτερο για μπατ.</string>
<string name="value_integer_feet">%1$d πόδια</string>
<string name="value_integer_feet_excellent_gps">%1$d πόδια (τέλειο σήμα GPS)</string>
<string name="value_integer_feet_recommended">%1$d ft (συνιστάται)</string>
+23 -3
View File
@@ -226,7 +226,6 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
<string name="menu_show_on_map">Mostrar en el mapa</string>
<string name="menu_start_gps">Iniciar GPS</string>
<string name="menu_stop_gps">Detener GPS</string>
<string name="menu_voice_frequency">Frecuencia de voz</string>
<string name="menu_resume_track">Reanudar ruta</string>
<!-- OpenTracks -->
<string name="recording_service">Servicio de grabación</string>
@@ -332,8 +331,6 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/milla</string>
<!-- Value -->
<string name="value_adapt_accuracy">Adaptar para obtener valores exactos</string>
<string name="value_adapt_battery_life">Adaptar a la duración de la batería</string>
<string name="value_integer_feet">%1$d pies</string>
<string name="value_integer_feet_excellent_gps">%1$d pies (señal de GPS excelente)</string>
<string name="value_integer_feet_recommended">%1$d pies (recomendado)</string>
@@ -512,4 +509,27 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
<string name="settings_sensor_running_speed_and_cadence">Correr: distancia, velocidad y cadencia</string>
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distancia y velocidad</string>
<string name="settings_sensor_cycling_cadence">Ciclismo: cadencia</string>
<string name="settings_announcements">Avisos</string>
<string name="settings_announcements_totaltime_frequency">Intervalo de tiempo</string>
<string name="settings_announcements_distance_frequency">Intervalo de distancia</string>
<string name="stats_sensors_heart_rate">Ritmo cardíaco</string>
<string name="stats_sensors_cadence">Cadencia</string>
<string name="stats_sensors_power">Poder</string>
<string name="field_set_primary">Primario</string>
<string name="description_speed_source_gps">GPS</string>
<string name="settings_recording_customize_layout_title">Personalizar la distribución de grabación</string>
<string name="settings_recording_customize_layout_select_columns">Datos por columna</string>
<string name="settings_layout_reset_confirm_message">Todas los ajustes de distribución se revertirán a los valores por defecto. Esto no eliminará ningún recorrido en el dispositivo.</string>
<string name="settings_layout_reset_confirm_title">¿Restablecer todas las disposiciones\?</string>
<string name="settings_layout_reset_done">Todos los ajustes de disposición se han revertido a los valores por defecto</string>
<string name="settings_all_reset">Restablecer todos los ajustes</string>
<string name="location_coordinate">%1$s°</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="field_remove_from_layout">Remover</string>
<string name="description_speed_source_sensor">Sensor: %1$s</string>
<string name="generic_choose_an_option">Seleccione una opción</string>
<string name="settings_layout_reset">Restabecer distribución personalizada</string>
<string name="stats_coordinates">Latitud/Longitud</string>
<string name="title_activity_settings_custom_layout">Personalizar la pantalla de grabación</string>
<string name="field_set_secondary">Secundario</string>
</resources>
-3
View File
@@ -226,7 +226,6 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
<string name="menu_show_on_map">Kuva kaardil</string>
<string name="menu_start_gps">Käivita GPS</string>
<string name="menu_stop_gps">Peata GPS</string>
<string name="menu_voice_frequency">Häälteadete sagedus</string>
<string name="menu_resume_track">Jätka pala</string>
<!-- OpenTracks -->
<string name="recording_service">Salvestamisteenus</string>
@@ -332,8 +331,6 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/miilis</string>
<!-- Value -->
<string name="value_adapt_accuracy">Täpsuse kohandamine</string>
<string name="value_adapt_battery_life">Kohandage aku elueale</string>
<string name="value_integer_feet">%1$d jalga</string>
<string name="value_integer_feet_excellent_gps">%1$d jalga (tugev GPS-signaal)</string>
<string name="value_integer_feet_recommended">%1$d jalga (soovitatav)</string>
-3
View File
@@ -227,7 +227,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
<string name="menu_show_on_map">Erakutsi mapan</string>
<string name="menu_start_gps">Hasi GPSa</string>
<string name="menu_stop_gps">Gelditu GPSa</string>
<string name="menu_voice_frequency">Ahotsaren frekuentzia</string>
<string name="menu_resume_track">Jarraitu pista</string>
<!-- OpenTracks -->
<string name="recording_service">Grabaketa-zerbitzua</string>
@@ -333,8 +332,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Doitu zehaztasuna</string>
<string name="value_adapt_battery_life">Doitu bateriaren iraupenera</string>
<string name="value_integer_feet">%1$d ft</string>
<string name="value_integer_feet_excellent_gps">%1$d ft (GPS bikaina)</string>
<string name="value_integer_feet_recommended">%1$d oin (gomendatua)</string>
-3
View File
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">نمایش روی نقشه</string>
<string name="menu_start_gps">شروع GPS</string>
<string name="menu_stop_gps">توقف GPS</string>
<string name="menu_voice_frequency">بسامد اعلان صوتی</string>
<!-- OpenTracks -->
<string name="recording_service">خدمات ضبط</string>
<!-- Search -->
@@ -264,8 +263,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">دقیقه در کیلومتر</string>
<string name="unit_minute_per_mile">دقیقه در مایل</string>
<!-- Value -->
<string name="value_adapt_accuracy">تعدیل به خاطر دقت</string>
<string name="value_adapt_battery_life">تعدیل به خاطر عمر باتری</string>
<string name="value_integer_feet">%1$d فوت</string>
<string name="value_integer_feet_excellent_gps">%1$d فوت (GPS عالی)</string>
<string name="value_integer_feet_recommended">%1$d فوت (توصیه می‌شود)</string>
-3
View File
@@ -230,7 +230,6 @@ limitations under the License.
<string name="menu_show_on_map">Näytä kartalta</string>
<string name="menu_start_gps">Käynnistä GPS</string>
<string name="menu_stop_gps">Pysäytä GPS</string>
<string name="menu_voice_frequency">Ilmoitustiheys</string>
<string name="menu_resume_track">Jatka reittiä</string>
<!-- OpenTracks -->
<string name="recording_service">Tallennuspalvelu</string>
@@ -340,8 +339,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/maili</string>
<!-- Value -->
<string name="value_adapt_accuracy">Säädä tarkkuutta</string>
<string name="value_adapt_battery_life">Sovita akun kestoon</string>
<string name="value_integer_feet">%1$d jalkaa</string>
<string name="value_integer_feet_excellent_gps">%1$d jalkaa (erinomainen GPS)</string>
<string name="value_integer_feet_recommended">%1$d jalkaa (suositus)</string>
-3
View File
@@ -144,7 +144,6 @@
<string name="menu_show_on_map">Afficher sur une carte</string>
<string name="menu_start_gps">Démarrer le GPS</string>
<string name="menu_stop_gps">Arrêter le GPS</string>
<string name="menu_voice_frequency">Fréquence vocale</string>
<string name="recording_service">Service d\'enregistrement</string>
<string name="sensor_state_battery">Niveau de charge de la pile</string>
<string name="sensor_state_cadence">Cadence</string>
@@ -215,8 +214,6 @@
<string name="unit_mile_per_hour">mi/h</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<string name="value_adapt_accuracy">Adapter pour la précision</string>
<string name="value_adapt_battery_life">Optimiser pour économiser la pile</string>
<string name="value_integer_feet">%1$d pi</string>
<string name="value_integer_feet_excellent_gps">%1$d pi(s) (réception GPS excellente)</string>
<string name="value_integer_feet_recommended">%1$d pi (recommandé)</string>
-3
View File
@@ -144,7 +144,6 @@
<string name="menu_show_on_map">Afficher sur la carte</string>
<string name="menu_start_gps">Démarrer le GPS</string>
<string name="menu_stop_gps">Arrêter le GPS</string>
<string name="menu_voice_frequency">Fréquence vocale</string>
<string name="recording_service">Service enregistrement</string>
<string name="sensor_state_battery">Niveau de la batterie</string>
<string name="sensor_state_cadence">Cadence</string>
@@ -215,8 +214,6 @@
<string name="unit_mile_per_hour">miles/h</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<string name="value_adapt_accuracy">Adapter pour la précision</string>
<string name="value_adapt_battery_life">Économiser la batterie</string>
<string name="value_integer_feet">%1$d pied(s)</string>
<string name="value_integer_feet_excellent_gps">%1$d pied(s) (réception GPS excellente)</string>
<string name="value_integer_feet_recommended">%1$d pied(s) (recommandé)</string>
-3
View File
@@ -226,7 +226,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="menu_show_on_map">Afficher sur la carte</string>
<string name="menu_start_gps">Démarrer le GPS</string>
<string name="menu_stop_gps">Arrêter le GPS</string>
<string name="menu_voice_frequency">Fréquence vocale</string>
<string name="menu_resume_track">Reprendre la trace</string>
<!-- OpenTracks -->
<string name="recording_service">Service enregistrement</string>
@@ -332,8 +331,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Adapter pour la précision</string>
<string name="value_adapt_battery_life">Économiser la batterie</string>
<string name="value_integer_feet">%1$d pied(s)</string>
<string name="value_integer_feet_excellent_gps">%1$d pied(s) (réception GPS excellente)</string>
<string name="value_integer_feet_recommended">%1$d pied(s) (recommandé)</string>
+20 -3
View File
@@ -237,7 +237,6 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="menu_show_on_map">Amosar no mapa</string>
<string name="menu_start_gps">Iniciar GPS</string>
<string name="menu_stop_gps">Desactivar GPS</string>
<string name="menu_voice_frequency">Frecuencia da voz</string>
<string name="menu_resume_track">Retomar gravación</string>
<!-- OpenTracks -->
<string name="recording_service">Servizo de gravación</string>
@@ -349,8 +348,6 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/km</string>
<!-- Value -->
<string name="value_adapt_accuracy">Adaptar para obter precisión</string>
<string name="value_adapt_battery_life">Adaptar á duración restante da batería</string>
<string name="value_integer_feet">%1$d m</string>
<string name="value_integer_feet_excellent_gps">%1$d m (GPS excelente)</string>
<string name="value_integer_feet_recommended">%1$d m (recomendado)</string>
@@ -518,4 +515,24 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distancia e velocidade</string>
<string name="settings_sensor_cycling_cadence">Ciclismo: cadencia</string>
<string name="settings_sensor_running_speed_and_cadence">Execución: distancia, velocidade, e cadencia</string>
<string name="description_speed_source_sensor">Sensor: %1$s</string>
<string name="settings_recording_customize_layout_title">Personaliza a disposición da gravación</string>
<string name="settings_layout_reset_confirm_message">Os axustes da disposición volverán aos valores por defecto. Esto non eliminará as rutas no dispositivo.</string>
<string name="stats_coordinates">Latitude/Lonxitude</string>
<string name="stats_sensors_power">Potencia</string>
<string name="description_speed_source_gps">GPS</string>
<string name="generic_choose_an_option">Elixe unha opción</string>
<string name="settings_recording_customize_layout_select_columns">Campos de datos por fila</string>
<string name="settings_layout_reset">Restablecer disposición</string>
<string name="settings_layout_reset_confirm_title">Restablecer disposición\?</string>
<string name="settings_layout_reset_done">Os axustes da disposición volveron aos seus valores por defecto</string>
<string name="settings_all_reset">Restablecer tódolos Axustes</string>
<string name="stats_sensors_heart_rate">Pulso cardíaco</string>
<string name="stats_sensors_cadence">Cadencia</string>
<string name="location_coordinate">%1$sº</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="title_activity_settings_custom_layout">Personaliza a disposición ao gravar</string>
<string name="field_remove_from_layout">Eliminar</string>
<string name="field_set_primary">Primario</string>
<string name="field_set_secondary">Secundario</string>
</resources>
-3
View File
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">नक्शे पर दिखाएं</string>
<string name="menu_start_gps">GPS प्रारंभ करें</string>
<string name="menu_stop_gps">GPS बंद करें</string>
<string name="menu_voice_frequency">ध्वनि आवृत्ति</string>
<!-- OpenTracks -->
<string name="recording_service">रिकॉर्डिंग सेवा</string>
<!-- Search -->
@@ -264,8 +263,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">मि/किमी</string>
<string name="unit_minute_per_mile">मि/मील</string>
<!-- Value -->
<string name="value_adapt_accuracy">सटीकता के लिए ग्रहण करें</string>
<string name="value_adapt_battery_life">बैटरी जीवन के लिए ग्रहण करें</string>
<string name="value_integer_feet">%1$d फुट</string>
<string name="value_integer_feet_excellent_gps">%1$d फुट (उत्कृष्ट GPS)</string>
<string name="value_integer_feet_recommended">%1$d फ़ुट (अनुशंसित)</string>
-3
View File
@@ -174,7 +174,6 @@ limitations under the License.
<string name="menu_show_on_map">Prikaži na karti</string>
<string name="menu_start_gps">Pokreni GPS</string>
<string name="menu_stop_gps">Zaustavi GPS</string>
<string name="menu_voice_frequency">Učest. glas. objava</string>
<!-- OpenTracks -->
<string name="recording_service">Usluga snimanja</string>
<!-- Search -->
@@ -265,8 +264,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Prilagodba radi točnosti</string>
<string name="value_adapt_battery_life">Prilagodi vijeku trajanja baterije</string>
<string name="value_integer_feet">%1$d ft</string>
<string name="value_integer_feet_excellent_gps">%1$d ft (izvrstan GPS)</string>
<string name="value_integer_feet_recommended">%1$d ft (preporučeno)</string>
-3
View File
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">Megjelenítés a térképen</string>
<string name="menu_start_gps">GPS indítása</string>
<string name="menu_stop_gps">GPS leállítása</string>
<string name="menu_voice_frequency">Utasítások gyakorisága</string>
<!-- OpenTracks -->
<string name="recording_service">Rögzítőszolgáltatás</string>
<!-- Search -->
@@ -265,8 +264,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">perc/km</string>
<string name="unit_minute_per_mile">perc/mérföld</string>
<!-- Value -->
<string name="value_adapt_accuracy">Beállítás pontossághoz</string>
<string name="value_adapt_battery_life">Akkumulátor élettartama szerint</string>
<string name="value_integer_feet">%1$d láb</string>
<string name="value_integer_feet_excellent_gps">%1$d láb (kiváló GPS-jel)</string>
<string name="value_integer_feet_recommended">%1$d láb (ajánlott)</string>
-3
View File
@@ -143,7 +143,6 @@
<string name="menu_show_on_map">Tampilkan di peta</string>
<string name="menu_start_gps">Nyalakan GPS</string>
<string name="menu_stop_gps">Matikan GPS</string>
<string name="menu_voice_frequency">Kerapatan suara</string>
<string name="recording_service">Layanan perekaman</string>
<string name="sensor_state_battery">Level baterai</string>
<string name="sensor_state_cadence">Tempo</string>
@@ -216,8 +215,6 @@
<string name="unit_mile_per_hour">mph</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">mnt/mil</string>
<string name="value_adapt_accuracy">Sesuaikan dengan akurasi</string>
<string name="value_adapt_battery_life">Sesuaikan untuk masa pakai baterai</string>
<string name="value_integer_feet">%1$d kaki</string>
<string name="value_integer_feet_excellent_gps">%1$d kaki (GPS sangat baik)</string>
<string name="value_integer_feet_recommended">%1$d kaki (direkomendasikan)</string>
-3
View File
@@ -215,7 +215,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="menu_show_on_map">Visualizza su mappa</string>
<string name="menu_start_gps">Accendi GPS</string>
<string name="menu_stop_gps">Spegni GPS</string>
<string name="menu_voice_frequency">Frequenza voce</string>
<!-- OpenTracks -->
<string name="recording_service">Servizio di registrazione</string>
<!-- Search -->
@@ -307,8 +306,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Adatta per la precisione</string>
<string name="value_adapt_battery_life">Adatta per la massima durata della batteria</string>
<string name="value_integer_feet">%1$d pd</string>
<string name="value_integer_feet_excellent_gps">%1$d pd (GPS forte)</string>
<string name="value_integer_feet_recommended">%1$d pd (consigliato)</string>
-3
View File
@@ -146,7 +146,6 @@
<string name="menu_show_on_map">הצג במפה</string>
<string name="menu_start_gps">הפעל GPS</string>
<string name="menu_stop_gps">עצור GPS</string>
<string name="menu_voice_frequency">תדירות הודעות קוליות</string>
<string name="recording_service">שירות הקלטה</string>
<string name="sensor_state_battery">טעינת סוללה</string>
<string name="sensor_state_cadence">קצב</string>
@@ -217,8 +216,6 @@
<string name="unit_mile_per_hour">מייל/שעה</string>
<string name="unit_minute_per_kilometer">דקות/ק\"מ</string>
<string name="unit_minute_per_mile">דקות/מייל</string>
<string name="value_adapt_accuracy">התאם למען הדיוק</string>
<string name="value_adapt_battery_life">התאם לחיי הסוללה</string>
<string name="value_integer_feet">%1$d רגל</string>
<string name="value_integer_feet_excellent_gps">%1$d רגל (GPS מעולה)</string>
<string name="value_integer_feet_recommended">%1$d רגל (מומלץ)</string>
-3
View File
@@ -172,7 +172,6 @@ limitations under the License.
<string name="menu_show_on_map">地図で見る</string>
<string name="menu_start_gps">GPSを開始</string>
<string name="menu_stop_gps">GPSを停止</string>
<string name="menu_voice_frequency">音声アナウンスの頻度</string>
<!-- OpenTracks -->
<string name="recording_service">記録サービス</string>
<!-- Search -->
@@ -263,8 +262,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">分/km</string>
<string name="unit_minute_per_mile">分/マイル</string>
<!-- Value -->
<string name="value_adapt_accuracy">精度を重視</string>
<string name="value_adapt_battery_life">電池残量を重視</string>
<string name="value_integer_feet">%1$dft</string>
<string name="value_integer_feet_excellent_gps">%1$dft(GPS信号: 強)</string>
<string name="value_integer_feet_recommended">%1$dft(推奨)</string>
-3
View File
@@ -172,7 +172,6 @@ limitations under the License.
<string name="menu_show_on_map">지도에 표시</string>
<string name="menu_start_gps">GPS 시작</string>
<string name="menu_stop_gps">GPS 중지</string>
<string name="menu_voice_frequency">음성 안내 빈도</string>
<!-- OpenTracks -->
<string name="recording_service">기록 서비스</string>
<!-- Search -->
@@ -263,8 +262,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">정확도를 위해 조정</string>
<string name="value_adapt_battery_life">배터리 수명을 위해 조정</string>
<string name="value_integer_feet">%1$d피트</string>
<string name="value_integer_feet_excellent_gps">%1$d피트(GPS 수신 원활)</string>
<string name="value_integer_feet_recommended">%1$d피트(권장)</string>
-3
View File
@@ -174,7 +174,6 @@ limitations under the License.
<string name="menu_show_on_map">Rodyti žemėlapyje</string>
<string name="menu_start_gps">Įjungti GPS</string>
<string name="menu_stop_gps">Išjungti GPS</string>
<string name="menu_voice_frequency">Balso dažnumas</string>
<!-- OpenTracks -->
<string name="recording_service">Įrašymo paslauga</string>
<!-- Search -->
@@ -266,8 +265,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min./km</string>
<string name="unit_minute_per_mile">min./myl</string>
<!-- Value -->
<string name="value_adapt_accuracy">Pritaikyti siekiant tikslumo</string>
<string name="value_adapt_battery_life">Pritaik. taus. akum</string>
<string name="value_integer_feet">%1$d pėd</string>
<string name="value_integer_feet_excellent_gps">%1$d pėd. (puikus GPS)</string>
<string name="value_integer_feet_recommended">%1$d pėd. (rekomenduojama)</string>
-3
View File
@@ -174,7 +174,6 @@ limitations under the License.
<string name="menu_show_on_map">Rādīt kartē</string>
<string name="menu_start_gps">Palaist GPS</string>
<string name="menu_stop_gps">Apturēt GPS</string>
<string name="menu_voice_frequency">Balss ziņas</string>
<!-- OpenTracks -->
<string name="recording_service">Ierakstīšanas pakalpojums</string>
<!-- Search -->
@@ -266,8 +265,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/jūdzē</string>
<!-- Value -->
<string name="value_adapt_accuracy">Pielāgot precizitātei</string>
<string name="value_adapt_battery_life">Pielāgot akumulatora darbības ilgumam</string>
<string name="value_integer_feet">%1$d pēda(-as)</string>
<string name="value_integer_feet_excellent_gps">%1$d pēda(-as) (izcilas kvalitātes GPS)</string>
<string name="value_integer_feet_recommended">%1$d pēdas (ieteicams)</string>
-3
View File
@@ -145,7 +145,6 @@
<string name="menu_show_on_map">Afișați pe hartă</string>
<string name="menu_start_gps">Porniți GPS</string>
<string name="menu_stop_gps">Opriți GPS</string>
<string name="menu_voice_frequency">Frecvența vocii</string>
<string name="recording_service">Serviciu de înregistrare</string>
<string name="sensor_state_battery">Nivelul bateriei</string>
<string name="sensor_state_cadence">Cadenţă</string>
@@ -216,8 +215,6 @@
<string name="unit_mile_per_hour">mi/h</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<string name="value_adapt_accuracy">Adaptaţi pentru acurateţe</string>
<string name="value_adapt_battery_life">Economisiţi bateria</string>
<string name="value_integer_feet">%1$d ft</string>
<string name="value_integer_feet_excellent_gps">%1$d ft (semnal GPS excelent)</string>
<string name="value_integer_feet_recommended">%1$d ft (recomandat)</string>
-3
View File
@@ -172,7 +172,6 @@ limitations under the License.
<string name="menu_show_on_map">Tunjukkan pada peta</string>
<string name="menu_start_gps">Mulakan GPS</string>
<string name="menu_stop_gps">Hentikan GPS</string>
<string name="menu_voice_frequency">Kekerapan suara</string>
<!-- OpenTracks -->
<string name="recording_service">Khidmat rakaman</string>
<!-- Search -->
@@ -263,8 +262,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/bt</string>
<!-- Value -->
<string name="value_adapt_accuracy">Sesuaikan untuk ketepatan</string>
<string name="value_adapt_battery_life">Sesuaikan untuk hayat bateri</string>
<string name="value_integer_feet">%1$d kaki</string>
<string name="value_integer_feet_excellent_gps">%1$d kaki (GPS bagus)</string>
<string name="value_integer_feet_recommended">%1$d kaki (disyorkan)</string>
+24 -4
View File
@@ -101,7 +101,7 @@ limitations under the License.
<string name="description_speed_ms">Hastighet (m/s)</string>
<string name="description_sensor_cadence">Kadens (o/min)</string>
<string name="description_sensor_heart_rate">Puls (slag/min)</string>
<string name="description_sensor_power">Styrke (W)</string>
<string name="description_sensor_power">Effekt (W)</string>
<string name="description_speed_imperial">Hastighet (mi/hr)</string>
<string name="description_speed_metric">Hastighet (km/t)</string>
<string name="description_time">Tid</string>
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">Vis på kart</string>
<string name="menu_start_gps">Start GPS</string>
<string name="menu_stop_gps">Stopp GPS</string>
<string name="menu_voice_frequency">Talefrekvens</string>
<!-- OpenTracks -->
<string name="recording_service">Opptakstjeneste</string>
<!-- Search -->
@@ -265,8 +264,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Tilpass for nøyaktighet</string>
<string name="value_adapt_battery_life">Tilpass for batterilevetid</string>
<string name="value_integer_feet">%1$d fot</string>
<string name="value_integer_feet_excellent_gps">%1$d fot (utmerket GPS)</string>
<string name="value_integer_feet_recommended">%1$d fot (anbefales)</string>
@@ -506,4 +503,27 @@ limitations under the License.
<string name="settings_sensor_cycling_cadence">Sykling: kadens</string>
<string name="settings_sensor_cycling_distance_speed">Sykling: distanse og hastighet</string>
<string name="settings_sensor_running_speed_and_cadence">Løping: distanse, hastighet og kadens</string>
<string name="description_speed_source_sensor">Sensor: %1$s</string>
<string name="settings_recording_customize_layout_title">Tilpass opptakslayouten</string>
<string name="settings_layout_reset_confirm_message">Alle layoutinnstillinger blir tilbakestilt til standardverdiene. Dette vil ikke slette spor på enheten.</string>
<string name="stats_coordinates">Breddegrad/Lengdegrad</string>
<string name="stats_sensors_heart_rate">Puls</string>
<string name="description_speed_source_gps">GPS</string>
<string name="generic_choose_an_option">Velg et alternativ</string>
<string name="settings_recording_customize_layout_select_columns">Datafelter per rekke</string>
<string name="settings_layout_reset">Tilbakestill tilpasset layout</string>
<string name="settings_layout_reset_confirm_title">Tilbakestille layoutene dine\?</string>
<string name="settings_layout_reset_done">Alle layoutinnstillinger er tilbakestilt til standardverdiene</string>
<string name="settings_all_reset">Tilbakestill alle innstillinger</string>
<string name="stats_sensors_cadence">Kadens</string>
<string name="stats_sensors_power">Effekt</string>
<string name="location_coordinate">%1$s°</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="title_activity_settings_custom_layout">Tilpass opptakslayouten</string>
<string name="field_set_primary">Primær</string>
<string name="field_set_secondary">Sekundær</string>
<string name="field_remove_from_layout">Fjern</string>
<string name="settings_announcements">Meldinger</string>
<string name="settings_announcements_totaltime_frequency">Tidsintervall</string>
<string name="settings_announcements_distance_frequency">Distanseintervall</string>
</resources>
-3
View File
@@ -226,7 +226,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<string name="menu_show_on_map">Weergeven op kaart</string>
<string name="menu_start_gps">GPS starten</string>
<string name="menu_stop_gps">GPS stoppen</string>
<string name="menu_voice_frequency">Frequentie voor gesproken berichten</string>
<string name="menu_resume_track">Hervat spoor</string>
<!-- OpenTracks -->
<string name="recording_service">Opnameservice</string>
@@ -332,8 +331,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Aanpassen voor nauwkeurigheid</string>
<string name="value_adapt_battery_life">Aanpassen voor resterende accutijd</string>
<string name="value_integer_feet">%1$d ft</string>
<string name="value_integer_feet_excellent_gps">%1$d ft (uitstekend GPS-signaal)</string>
<string name="value_integer_feet_recommended">%1$d ft (aanbevolen)</string>
-3
View File
@@ -186,7 +186,6 @@ limitations under the License.
<string name="menu_show_on_map">Pokaż na mapie</string>
<string name="menu_start_gps">Uruchom GPS</string>
<string name="menu_stop_gps">Zatrzymaj GPS</string>
<string name="menu_voice_frequency">Częstość powiadomień</string>
<string name="menu_resume_track">Kontynuuj trasę</string>
<!-- OpenTracks -->
<string name="recording_service">Usługa rejestrowania</string>
@@ -303,8 +302,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Dostosuj pod kątem dokładności</string>
<string name="value_adapt_battery_life">Dostosuj pod kątem żywotności baterii</string>
<string name="value_integer_feet">%1$d ft</string>
<string name="value_integer_feet_excellent_gps">%1$d ft (doskonały sygnał GPS)</string>
<string name="value_integer_feet_recommended">%1$d ft (zalecane)</string>
-3
View File
@@ -144,7 +144,6 @@
<string name="menu_show_on_map">Mostrar no mapa</string>
<string name="menu_start_gps">Iniciar GPS</string>
<string name="menu_stop_gps">Parar GPS</string>
<string name="menu_voice_frequency">Frequência da voz</string>
<string name="recording_service">Serviço de gravação</string>
<string name="sensor_state_battery">Nível da bateria</string>
<string name="sensor_state_cadence">Cadência</string>
@@ -217,8 +216,6 @@
<string name="unit_mile_per_hour">mph</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/milhas</string>
<string name="value_adapt_accuracy">Adaptar para fins de precisão</string>
<string name="value_adapt_battery_life">Adaptar à vida da bateria</string>
<string name="value_integer_feet">%1$d pés</string>
<string name="value_integer_feet_excellent_gps">%1$d pés (GPS excelente)</string>
<string name="value_integer_feet_recommended">%1$d pés (recomendado)</string>
-3
View File
@@ -144,7 +144,6 @@
<string name="menu_show_on_map">Mostrar no mapa</string>
<string name="menu_start_gps">Ligar GPS</string>
<string name="menu_stop_gps">Desligar GPS</string>
<string name="menu_voice_frequency">Frequência de voz</string>
<string name="recording_service">Serviço de gravação</string>
<string name="sensor_state_battery">Nível da bateria</string>
<string name="sensor_state_cadence">Cadência</string>
@@ -215,8 +214,6 @@
<string name="unit_mile_per_hour">mi/h</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/milha</string>
<string name="value_adapt_accuracy">Adaptar para precisão</string>
<string name="value_adapt_battery_life">Adaptar para a vida da bateria</string>
<string name="value_integer_feet">%1$d pés</string>
<string name="value_integer_feet_excellent_gps">%1$d pés (GPS excelente)</string>
<string name="value_integer_feet_recommended">%1$d pés (recomendado)</string>
-3
View File
@@ -173,7 +173,6 @@ limitations under the License.
<string name="menu_show_on_map">Mostrar no mapa</string>
<string name="menu_start_gps">Iniciar GPS</string>
<string name="menu_stop_gps">Parar GPS</string>
<string name="menu_voice_frequency">Frequência da voz</string>
<!-- OpenTracks -->
<string name="recording_service">Serviço de gravação</string>
<!-- Search -->
@@ -265,8 +264,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/milhas</string>
<!-- Value -->
<string name="value_adapt_accuracy">Adaptar para fins de precisão</string>
<string name="value_adapt_battery_life">Adaptar à vida da bateria</string>
<string name="value_integer_feet">%1$d pés</string>
<string name="value_integer_feet_excellent_gps">%1$d pés (GPS excelente)</string>
<string name="value_integer_feet_recommended">%1$d pés (recomendado)</string>
-3
View File
@@ -174,7 +174,6 @@ limitations under the License.
<string name="menu_show_on_map">Afișați pe hartă</string>
<string name="menu_start_gps">Porniți GPS</string>
<string name="menu_stop_gps">Opriți GPS</string>
<string name="menu_voice_frequency">Frecvența vocii</string>
<!-- OpenTracks -->
<string name="recording_service">Serviciu de înregistrare</string>
<!-- Search -->
@@ -266,8 +265,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Adaptaţi pentru acurateţe</string>
<string name="value_adapt_battery_life">Economisiţi bateria</string>
<string name="value_integer_feet">%1$d ft</string>
<string name="value_integer_feet_excellent_gps">%1$d ft (semnal GPS excelent)</string>
<string name="value_integer_feet_recommended">%1$d ft (recomandat)</string>
-3
View File
@@ -175,7 +175,6 @@ limitations under the License.
<string name="menu_show_on_map">Показать на карте</string>
<string name="menu_start_gps">Включить GPS</string>
<string name="menu_stop_gps">Отключить GPS</string>
<string name="menu_voice_frequency">Голосовые оповещения</string>
<!-- OpenTracks -->
<string name="recording_service">Служба записи треков</string>
<!-- Search -->
@@ -267,8 +266,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">мин/км</string>
<string name="unit_minute_per_mile">мин/мил</string>
<!-- Value -->
<string name="value_adapt_accuracy">Макс. точность</string>
<string name="value_adapt_battery_life">Мин. расход батареи</string>
<string name="value_integer_feet">%1$d фт</string>
<string name="value_integer_feet_excellent_gps">%1$d фт (отличный сигнал)</string>
<string name="value_integer_feet_recommended">%1$d фт (рекомендуется)</string>
+7 -8
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,8 +13,7 @@ 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.
-->
<resources>
--><resources>
<!-- Activity Type -->
<string name="activity_type_airplane">lietadlo</string>
<string name="activity_type_atv">Všetky terénne vozidlá</string>
@@ -174,7 +174,6 @@ limitations under the License.
<string name="menu_show_on_map">Zobraziť na mape</string>
<string name="menu_start_gps">Aktivovať GPS</string>
<string name="menu_stop_gps">Deaktivovať GPS</string>
<string name="menu_voice_frequency">Hlasová frekvencia</string>
<!-- OpenTracks -->
<string name="recording_service">Služba na záznam</string>
<!-- Search -->
@@ -218,7 +217,6 @@ limitations under the License.
<string name="settings_stats_units_imperial">Imperiálne jednotky (mi, ft)</string>
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
<string name="settings_stats_units_title">Preferované jednotky</string>
<!-- Share Track -->
<string name="share_track_share_file_body">Táto trasa by ťa mohla zaujímať.\n\nPre zobrazenie tejto trasy použi aplikáciu Google zem alebo iný mapový softvér.\n\n%1$s</string>
<string name="share_track_subject">Chcem s vami zdieľať trasu</string>
@@ -265,8 +263,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min./km</string>
<string name="unit_minute_per_mile">min./mi.</string>
<!-- Value -->
<string name="value_adapt_accuracy">Prispôsobiť na presnosť</string>
<string name="value_adapt_battery_life">Prispôsobiť na výdrž batérie</string>
<string name="value_integer_feet">%1$d st.</string>
<string name="value_integer_feet_excellent_gps">%1$d st. (výborný signál GPS)</string>
<string name="value_integer_feet_recommended">%1$d st. (odporúčané)</string>
@@ -374,4 +370,7 @@ limitations under the License.
<string name="waypoint_type_waterfall">vodopád</string>
<string name="waypoint_type_water_fountain">fontána</string>
<string name="waypoint_type_waypoint">navigačný bod</string>
</resources>
<string name="settings_recording_customize_layout_select_columns">Počet polí na riadok</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Na celú obrazovku</string>
<string name="settings_recording_customize_layout_title">Prispôsobiť vzhľad meranie</string>
</resources>
+5 -118
View File
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +12,8 @@ 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.
--><resources>
-->
<resources>
<!-- Activity Type -->
<string name="activity_type_airplane">letalo</string>
<string name="activity_type_atv">štirikolesniki</string>
@@ -175,7 +175,6 @@ limitations under the License.
<string name="menu_show_on_map">Pokaži na zemljevidu</string>
<string name="menu_start_gps">Zaženi GPS</string>
<string name="menu_stop_gps">Ustavi GPS</string>
<string name="menu_voice_frequency">Pogostost glas. obv.</string>
<!-- OpenTracks -->
<string name="recording_service">Storitev snemanja</string>
<!-- Search -->
@@ -219,6 +218,7 @@ limitations under the License.
<string name="settings_stats_units_imperial">Imperialni sistem (mi, ft)</string>
<string name="settings_stats_units_metric">Metrični sistem (km, m)</string>
<string name="settings_stats_units_title">Prednostne enote</string>
<!-- Share Track -->
<string name="share_track_share_file_body">Zdi se mi, da bi te ta pot utegnila zanimati.
\n
@@ -267,8 +267,6 @@ limitations under the License.
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/mi</string>
<!-- Value -->
<string name="value_adapt_accuracy">Prilagodi za natančnost</string>
<string name="value_adapt_battery_life">Prilagodi za življenjsko dobo baterije</string>
<string name="value_integer_feet">%1$d čevljev</string>
<string name="value_integer_feet_excellent_gps">%1$d čevljev (odličen signal GPS)</string>
<string name="value_integer_feet_recommended">%1$d čevljev (priporočeno)</string>
@@ -382,115 +380,4 @@ limitations under the License.
<string name="waypoint_type_waterfall">slap</string>
<string name="waypoint_type_water_fountain">fontana</string>
<string name="waypoint_type_waypoint">smerna točka</string>
<string name="help_track_map_title"></string>
<string name="export_without_photos"></string>
<string name="settings_default_trackfileformat"></string>
<string name="sensor_state_heart_rate_avg"></string>
<string name="track_delete_not_recording"></string>
<string name="track_detail_intervals_tab"></string>
<string name="generic_separator_done_total"></string>
<string name="settings_night_mode_option_day"></string>
<string name="settings_sensor_cycling_distance_speed"></string>
<string name="settings_sensor_wheel_circumference"></string>
<string name="stats_loss"></string>
<string name="gps_disabled_msg"></string>
<string name="track_distance_notification"></string>
<string name="about_history"></string>
<string name="settings_sensor_cycling_cadence"></string>
<string name="voice_speed_lap"></string>
<string name="export_error_post_workout"></string>
<string name="menu_insert_gallery_img"></string>
<string name="export_progress_summary_overwrite_msg"></string>
<string name="export_with_photos"></string>
<string name="sensor_state_cadence_max"></string>
<string name="settings_sensor_connecting"></string>
<string name="track_speed_notification"></string>
<string name="description_default_speed_or_pace"></string>
<string name="import_parser_error"></string>
<string name="generic_show_errors"></string>
<string name="stats_split_interval"></string>
<string name="generic_completed"></string>
<string name="bluetooth_disabled"></string>
<string name="track_recording_notification_accuracy"></string>
<string name="export_progress_summary_new_msg"></string>
<string name="export_progress_summary_skip_msg"></string>
<string name="hold_to_pause"></string>
<string name="settings_recording_fullscreen_on_while_recording_summary"></string>
<string name="help_gps_troubleshooting_title"></string>
<string name="hold_to_stop"></string>
<string name="menu_voice_rate"></string>
<string name="import_progress_summary_errors_msg"></string>
<string name="settings_night_mode_option_night"></string>
<string name="help_data_google_play_services_title"></string>
<string name="help_recording_content"></string>
<string name="interval_list_empty_message"></string>
<string name="share_image_subject"></string>
<string name="help_data_storage_title"></string>
<string name="help_data_storage_content"></string>
<string name="instant_export_enabled_title"></string>
<string name="import_progress_summary_exists_msg"></string>
<string name="import_file_imported"></string>
<string name="settings_night_mode_title"></string>
<string name="gps_fixed_and_ready"></string>
<string name="permission_gps_failed"></string>
<string name="about_libraries"></string>
<string name="open_track_as_KML"></string>
<string name="voice_pace_per_mile"></string>
<string name="about_preference_title"></string>
<string name="import_unsupported_format"></string>
<string name="settings_sensor_disconnected"></string>
<string name="track_pace_notification"></string>
<string name="help_feature_title"></string>
<string name="sensor_state_power_avg"></string>
<string name="about_description"></string>
<string name="help_storage_pictures_title"></string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary"></string>
<string name="settings_recording_keep_screen_on_while_recording_summary"></string>
<string name="export_track_already_exists_msg"></string>
<string name="about_version_code"></string>
<string name="menu_resume_track"></string>
<string name="open_track_as_GPX"></string>
<string name="instant_export_enabled_summary"></string>
<string name="import_progress_summary_ok_msg"></string>
<string name="settings_night_mode_option_system"></string>
<string name="activity_type_escooter"></string>
<string name="help_recording_title"></string>
<string name="settings_default_export_uri_title"></string>
<string name="generic_skip"></string>
<string name="help_track_export_content"></string>
<string name="open_track_as_trackfileformat"></string>
<string name="track_delete_progress"></string>
<string name="generic_click_twice_cancel"></string>
<string name="share_image_body"></string>
<string name="help_storage_pictures_content"></string>
<string name="import_prevent_reimport"></string>
<string name="delete_service"></string>
<string name="aggregated_stats_empty_message"></string>
<string name="import_thread_interrupted"></string>
<string name="settings_recording_fullscreen_on_while_recording_title"></string>
<string name="import_no_kml_file_found"></string>
<string name="settings_recording_keep_screen_on_while_recording_title"></string>
<string name="import_error_info"></string>
<string name="settings_prevent_reimport_tracks_title"></string>
<string name="sensor_state_cadence_avg"></string>
<string name="marker_add_photo_canceled"></string>
<string name="voice_pace_lap"></string>
<string name="settings_recording_show_on_lockscreen_while_recording_title"></string>
<string name="export_progress_summary_errors_msg"></string>
<string name="sensor_could_not_scan"></string>
<string name="about_url"></string>
<string name="description_altitude_loss"></string>
<string name="generic_overwrite"></string>
<string name="about_version_name"></string>
<string name="help_feature_content"></string>
<string name="export_all_do_it_for_all"></string>
<string name="settings_error_initial_values_restored"></string>
<string name="sensor_state_heart_rate_max"></string>
<string name="help_track_export_title"></string>
<string name="import_error_list_dialog_title"></string>
<string name="export_track_errors"></string>
<string name="about_third_party_data"></string>
<string name="import_unable_to_import_file"></string>
<string name="generic_open_track"></string>
<string name="help_support_title"></string>
</resources>
</resources>

Some files were not shown because too many files have changed in this diff Show More