forked from upstream-mirrors/OpenTracks
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae3078304d | |||
| 48b6438c06 | |||
| e822172f58 | |||
| a3aee403a1 | |||
| aac2ad4165 | |||
| 76d5e5780c | |||
| 931fd7514a | |||
| d1570ad700 | |||
| 04fff89f68 | |||
| b57fd368ae | |||
| 58d6a3b5a9 | |||
| 289a5273a9 | |||
| 120e20a0ea | |||
| 2843d19450 | |||
| 50127e311a | |||
| f83a1b3a4c | |||
| 65ecbc4521 | |||
| fa50db08c3 | |||
| b395c19832 | |||
| 7e6ffbabd2 | |||
| 6b43d341d0 | |||
| 8af0fb3250 | |||
| 49219de46a | |||
| 1baf652b81 | |||
| 6723bfb979 | |||
| 8357b234d2 | |||
| 9de9fbd058 | |||
| 6da6fc6004 | |||
| 114fc31be0 | |||
| ae795a76fa | |||
| 92004b0ec7 | |||
| f277347529 | |||
| ad1aa06407 | |||
| 3b92303511 | |||
| e62faa1253 | |||
| d3900f4dad | |||
| 00b8834795 |
@@ -6,7 +6,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [29]
|
api-level: [ 23, 26, 29 ]
|
||||||
target: [default]
|
target: [default]
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ However, often only one value is provided.
|
|||||||
|
|
||||||
* Wahoo Speed (Model: WFRPMSPD)
|
* Wahoo Speed (Model: WFRPMSPD)
|
||||||
|
|
||||||
|
* CooSpo cadence and speed sensor (model: BK467)
|
||||||
|
|
||||||
### Cadence
|
### Cadence
|
||||||
|
|
||||||
* Garmin Cadence Sensor 2
|
* Garmin Cadence Sensor 2
|
||||||
|
|||||||
+11
-7
@@ -3,17 +3,17 @@ apply plugin: 'com.android.application'
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
classpath 'com.android.tools.build:gradle:4.2.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +49,14 @@ android {
|
|||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aaptOptions {
|
||||||
|
cruncherEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
versionCode 4009
|
versionCode 4046
|
||||||
versionName "v3.17.3"
|
versionName "v3.18.0"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -86,7 +90,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha05'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha06'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
@@ -99,5 +103,5 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||||
androidTestImplementation 'org.mockito:mockito-android:3.8.0'
|
androidTestImplementation 'org.mockito:mockito-android:3.9.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
v3.18.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Altitude is shown in EGM2008 (stored/exported as WGS84)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fixed memory leak (introduced in v3.17.0)
|
||||||
|
- Charts: altitude should be in M/FT (introduced in v3.17.1)
|
||||||
|
- Charts: while recording was not correctly updated (introduced in v3.17.3)
|
||||||
|
- TrackDataHub did not account for recordingDistanceInterval (introduced in v3.17.2)
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Disable PNG crunching (reproducible build)
|
||||||
|
- Refactored TrackImporter and optimized KMZ import
|
||||||
|
- Use LiveData for TrackRecordingService to UI communication (removing memory leak)
|
||||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -129,6 +130,7 @@ fi
|
|||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
|||||||
Vendored
+1
@@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%*
|
|||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class ChartPointTest {
|
|||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(0.05, point.getAltitude(), 0.01);
|
assertEquals(50, point.getAltitude(), 0.01);
|
||||||
assertEquals(36, point.getSpeed(), 0.01);
|
assertEquals(36, point.getSpeed(), 0.01);
|
||||||
assertEquals(1.66, point.getPace(), 0.01);
|
assertEquals(1.66, point.getPace(), 0.01);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.chart.ChartView;
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link ChartFragment}.
|
* Tests {@link ChartFragment}.
|
||||||
@@ -59,7 +58,6 @@ public class ChartFragmentTest {
|
|||||||
boolean chartByDistance = false;
|
boolean chartByDistance = false;
|
||||||
chartFragment = ChartFragment.newInstance(chartByDistance);
|
chartFragment = ChartFragment.newInstance(chartByDistance);
|
||||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||||
chartFragment.setRecordingDistanceInterval(Distance.of(50));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+83
-34
@@ -3,24 +3,26 @@ package de.dennisguse.opentracks.io.file.importer;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import androidx.test.rule.GrantPermissionRule;
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
import androidx.test.rule.ServiceTestRule;
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.File;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -30,6 +32,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
@@ -41,7 +44,6 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
|||||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -53,7 +55,7 @@ import static org.junit.Assert.assertTrue;
|
|||||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||||
* <p>
|
* <p>
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class ExportImportTest {
|
public class ExportImportTest {
|
||||||
|
|
||||||
private static final String TAG = ExportImportTest.class.getSimpleName();
|
private static final String TAG = ExportImportTest.class.getSimpleName();
|
||||||
@@ -71,7 +73,6 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
|
||||||
|
|
||||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
@@ -79,6 +80,9 @@ public class ExportImportTest {
|
|||||||
private static final String TRACK_CATEGORY = "the category";
|
private static final String TRACK_CATEGORY = "the category";
|
||||||
private static final String TRACK_DESCRIPTION = "the description";
|
private static final String TRACK_DESCRIPTION = "the description";
|
||||||
|
|
||||||
|
private File tmpFile;
|
||||||
|
private Uri tmpFileUri;
|
||||||
|
|
||||||
private Track track;
|
private Track track;
|
||||||
private List<Marker> markers = new ArrayList<>();
|
private List<Marker> markers = new ArrayList<>();
|
||||||
private List<TrackPoint> trackPoints = new ArrayList<>();
|
private List<TrackPoint> trackPoints = new ArrayList<>();
|
||||||
@@ -86,6 +90,18 @@ public class ExportImportTest {
|
|||||||
private Track.Id trackId;
|
private Track.Id trackId;
|
||||||
private Track.Id importTrackId;
|
private Track.Id importTrackId;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void fileSetup() throws IOException {
|
||||||
|
tmpFile = File.createTempFile("test", "test", context.getFilesDir());
|
||||||
|
tmpFileUri = Uri.fromFile(tmpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void FileTearDown() {
|
||||||
|
tmpFile.deleteOnExit();
|
||||||
|
tmpFileUri = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void setUp(boolean hasSensorDistance) throws TimeoutException {
|
public void setUp(boolean hasSensorDistance) throws TimeoutException {
|
||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||||
.getService();
|
.getService();
|
||||||
@@ -138,7 +154,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void kml_with_trackdetail() throws TimeoutException {
|
public void kml_with_trackdetail() throws TimeoutException, IOException {
|
||||||
setUp(false);
|
setUp(false);
|
||||||
|
|
||||||
// given
|
// given
|
||||||
@@ -148,13 +164,12 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||||
trackExporter.writeTrack(track, outputStream);
|
|
||||||
contentProviderUtils.deleteTrack(context, trackId);
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -179,7 +194,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void kml_with_trackdetail_and_sensordata() throws TimeoutException {
|
public void kml_with_trackdetail_and_sensordata() throws TimeoutException, IOException {
|
||||||
setUp(true);
|
setUp(true);
|
||||||
|
|
||||||
// given
|
// given
|
||||||
@@ -189,13 +204,12 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||||
trackExporter.writeTrack(track, outputStream);
|
|
||||||
contentProviderUtils.deleteTrack(context, trackId);
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -217,9 +231,48 @@ public class ExportImportTest {
|
|||||||
assertMarkers();
|
assertMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Does not test images
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException {
|
||||||
|
setUp(true);
|
||||||
|
|
||||||
|
// given
|
||||||
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
|
TrackExporter trackExporter = TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. export
|
||||||
|
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||||
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
|
|
||||||
|
// 2. import
|
||||||
|
KmzTrackImporter trackImporter = new KmzTrackImporter();
|
||||||
|
importTrackId = trackImporter.importFile(context, tmpFileUri).get(0);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 1. track
|
||||||
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
assertNotNull(importedTrack);
|
||||||
|
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||||
|
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||||
|
assertEquals(track.getName(), importedTrack.getName());
|
||||||
|
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||||
|
|
||||||
|
// 2. trackpoints
|
||||||
|
assertTrackpoints(trackPoints, true, true, true, true, true, true);
|
||||||
|
|
||||||
|
// 2. trackstatistics
|
||||||
|
assertTrackStatistics(false, true, true);
|
||||||
|
|
||||||
|
// 4. markers
|
||||||
|
assertMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test(expected = ImportAlreadyExistsException.class)
|
@Test(expected = ImportAlreadyExistsException.class)
|
||||||
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException {
|
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException, IOException {
|
||||||
setUp(false);
|
setUp(false);
|
||||||
|
|
||||||
// given
|
// given
|
||||||
@@ -231,13 +284,11 @@ public class ExportImportTest {
|
|||||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
||||||
trackExporter.writeTrack(track, outputStream);
|
|
||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -247,7 +298,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void gpx() throws TimeoutException {
|
public void gpx() throws TimeoutException, IOException {
|
||||||
setUp(true);
|
setUp(true);
|
||||||
|
|
||||||
// given
|
// given
|
||||||
@@ -257,13 +308,12 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||||
trackExporter.writeTrack(track, outputStream);
|
|
||||||
contentProviderUtils.deleteTrack(context, trackId);
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context, contentProviderUtils));
|
||||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -295,7 +345,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test(expected = ImportAlreadyExistsException.class)
|
@Test(expected = ImportAlreadyExistsException.class)
|
||||||
public void gpx_duplicate_trackUUID() throws TimeoutException {
|
public void gpx_duplicate_trackUUID() throws TimeoutException, IOException {
|
||||||
setUp(false);
|
setUp(false);
|
||||||
|
|
||||||
// given
|
// given
|
||||||
@@ -308,12 +358,11 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||||
trackExporter.writeTrack(track, outputStream);
|
|
||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context, contentProviderUtils));
|
||||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -368,7 +417,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
|
assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
|
||||||
if (trackPoint.hasAltitude()) {
|
if (trackPoint.hasAltitude()) {
|
||||||
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
|
assertEquals(trackPoint.getAltitude().toM(), importedTrackPoint.getAltitude().toM(), 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type.equals(TrackPoint.Type.SEGMENT_START_MANUAL) || type.equals(TrackPoint.Type.SEGMENT_END_MANUAL)) {
|
if (type.equals(TrackPoint.Type.SEGMENT_START_MANUAL) || type.equals(TrackPoint.Type.SEGMENT_END_MANUAL)) {
|
||||||
@@ -438,7 +487,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
|
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
|
||||||
TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time));
|
TrackPoint tp = new TrackPoint(latitude, longitude, Altitude.WGS84.of(altitude), Instant.ofEpochMilli(time));
|
||||||
tp.setAccuracy(accuracy);
|
tp.setAccuracy(accuracy);
|
||||||
tp.setSpeed(Speed.of(speed));
|
tp.setSpeed(Speed.of(speed));
|
||||||
tp.setHeartRate_bpm(heartRate);
|
tp.setHeartRate_bpm(heartRate);
|
||||||
|
|||||||
+8
-7
@@ -11,6 +11,7 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -48,9 +49,9 @@ public class LegacyImportTest {
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void kml_with_statistics_marker() {
|
public void kml_with_statistics_marker() throws IOException {
|
||||||
// given
|
// given
|
||||||
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_statistics_marker);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_statistics_marker);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -90,9 +91,9 @@ public class LegacyImportTest {
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test(expected = ImportParserException.class)
|
@Test(expected = ImportParserException.class)
|
||||||
public void kml_without_locations() {
|
public void kml_without_locations() throws IOException {
|
||||||
// given
|
// given
|
||||||
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -104,9 +105,9 @@ public class LegacyImportTest {
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void gpx_with_pause_resume() {
|
public void gpx_with_pause_resume() throws IOException {
|
||||||
// given
|
// given
|
||||||
GpxFileTrackImporter trackImporter = new GpxFileTrackImporter(context);
|
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_gpx_pause_resume);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_gpx_pause_resume);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -151,7 +152,7 @@ public class LegacyImportTest {
|
|||||||
if (altitude == null) {
|
if (altitude == null) {
|
||||||
assertFalse(trackPoint.hasAltitude());
|
assertFalse(trackPoint.hasAltitude());
|
||||||
} else {
|
} else {
|
||||||
assertEquals(altitude, (Double) trackPoint.getAltitude());
|
assertEquals(altitude, (Double) trackPoint.getAltitude().toM());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.test.rule.GrantPermissionRule;
|
|||||||
import androidx.test.rule.ServiceTestRule;
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@@ -83,6 +84,10 @@ public class TrackRecordingServiceTest {
|
|||||||
if (Looper.myLooper() == null) Looper.prepare();
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void finalTearDown() {
|
||||||
|
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||||
|
}
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
|
|||||||
+7
@@ -13,6 +13,7 @@ import androidx.test.rule.GrantPermissionRule;
|
|||||||
import androidx.test.rule.ServiceTestRule;
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@@ -44,6 +45,7 @@ import static org.junit.Assert.assertNotNull;
|
|||||||
* So, if it is executed on a real device, data might be lost.
|
* So, if it is executed on a real device, data might be lost.
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
//TODO Implement as mock test; no need to store data in database
|
||||||
public class TrackRecordingServiceTestLocation {
|
public class TrackRecordingServiceTestLocation {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@@ -63,6 +65,11 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
if (Looper.myLooper() == null) Looper.prepare();
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void finalTearDown() {
|
||||||
|
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws TimeoutException {
|
public void setUp() throws TimeoutException {
|
||||||
// Set up the mock content resolver
|
// Set up the mock content resolver
|
||||||
|
|||||||
-59
@@ -1,59 +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 android.os.Looper;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link AnnouncementPeriodicTaskFactory}.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class AnnouncementPeriodicTaskFactoryTest {
|
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void preSetUp() {
|
|
||||||
// Prepare looper for Android's message queue
|
|
||||||
if (Looper.myLooper() == null) Looper.prepare();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void finalTearDown() {
|
|
||||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreate() {
|
|
||||||
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
|
|
||||||
PeriodicTask task = factory.create(context);
|
|
||||||
assertTrue(task instanceof AnnouncementPeriodicTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package de.dennisguse.opentracks.services.tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class PeriodicTaskExecutorTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void preSetUp() {
|
||||||
|
// Prepare looper for Android's message queue
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void finalTearDown() {
|
||||||
|
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void calculateNextTaskDistance() throws TimeoutException {
|
||||||
|
// given
|
||||||
|
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);
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
statistics.setTotalDistance(Distance.of(13000));
|
||||||
|
assertEquals(Distance.of(15000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
|
||||||
|
|
||||||
|
statistics.setTotalDistance(Distance.of(15100));
|
||||||
|
assertEquals(Distance.of(20000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+11
-10
@@ -9,6 +9,7 @@ import org.junit.runner.RunWith;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||||
@@ -53,8 +54,8 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||||
|
|
||||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||||
@@ -71,8 +72,8 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||||
|
|
||||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||||
tp3.setSpeed(Speed.of(5f));
|
tp3.setSpeed(Speed.of(5f));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -90,11 +91,11 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||||
|
|
||||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||||
tp2.setSpeed(Speed.of(5f));
|
tp2.setSpeed(Speed.of(5f));
|
||||||
TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000));
|
TrackPoint tp3 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||||
tp2.setSpeed(Speed.of(5f));
|
tp2.setSpeed(Speed.of(5f));
|
||||||
TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000));
|
TrackPoint tp4 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||||
tp2.setSpeed(Speed.of(5f));
|
tp2.setSpeed(Speed.of(5f));
|
||||||
tp4.setSensorDistance(Distance.of(5f));
|
tp4.setSensorDistance(Distance.of(5f));
|
||||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||||
@@ -122,9 +123,9 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||||
|
|
||||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||||
TrackPoint tp4 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(4000));
|
TrackPoint tp4 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||||
tp4.setSensorDistance(Distance.of(5f));
|
tp4.setSensorDistance(Distance.of(5f));
|
||||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||||
tp5.setSensorDistance(Distance.of(10f));
|
tp5.setSensorDistance(Distance.of(10f));
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class EGM2008UtilsTest {
|
||||||
|
|
||||||
|
private static final double MAX_BILINEAR_ERROR = 0.478;
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fileVerification() throws IOException {
|
||||||
|
// given
|
||||||
|
int expectedLength = 18671444;
|
||||||
|
int expectedHeaderLength = 404;
|
||||||
|
|
||||||
|
String expectedHeader = "P5\n" +
|
||||||
|
"# Geoid file in PGM format for the GeographicLib::Geoid class\n" +
|
||||||
|
"# Description WGS84 EGM2008, 5-minute grid\n" +
|
||||||
|
"# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008\n" +
|
||||||
|
"# DateTime 2009-08-29 18:45:00\n" +
|
||||||
|
"# MaxBilinearError 0.478\n" +
|
||||||
|
"# RMSBilinearError 0.012\n" +
|
||||||
|
"# MaxCubicError 0.294\n" +
|
||||||
|
"# RMSCubicError 0.005\n" +
|
||||||
|
"# Offset -108\n" +
|
||||||
|
"# Scale 0.003\n" +
|
||||||
|
"# Origin 90N 0E\n" +
|
||||||
|
"# AREA_OR_POINT Point\n" +
|
||||||
|
"# Vertical_Datum WGS84\n" +
|
||||||
|
"4320 2161\n" +
|
||||||
|
"65535" +
|
||||||
|
"\n";
|
||||||
|
|
||||||
|
// when
|
||||||
|
try (InputStream inputStream = context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA)) {
|
||||||
|
assertEquals(expectedLength, inputStream.available());
|
||||||
|
|
||||||
|
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII);
|
||||||
|
char[] data = new char[expectedHeaderLength];
|
||||||
|
int length = reader.read(data);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(expectedHeaderLength, length);
|
||||||
|
assertEquals(expectedHeader, new String(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Northpole() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(90);
|
||||||
|
trackPoint.setLongitude(0.1);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-14.8980, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Southpole() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(0);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Southpole2() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(180);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Southpole3() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(-180);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_0() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(0);
|
||||||
|
trackPoint.setLongitude(0);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-17.2260, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Berlin() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(52.530644);
|
||||||
|
trackPoint.setLongitude(13.383068);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-39.4865, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Berlin_Caching() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint1.setLatitude(52.530644);
|
||||||
|
trackPoint1.setLongitude(13.383068);
|
||||||
|
trackPoint1.setAltitude(0);
|
||||||
|
|
||||||
|
TrackPoint trackPoint2 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint2.setLatitude(52.530000);
|
||||||
|
trackPoint2.setLongitude(13.380000);
|
||||||
|
trackPoint2.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint1.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()), altitude_egm2008.correctAltitude(trackPoint2.getLocation()), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_MaxUndulation() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-8.417);
|
||||||
|
trackPoint.setLongitude(147.367);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getIndices() {
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(90);
|
||||||
|
trackPoint.setLongitude(0);
|
||||||
|
assertEquals(new EGM2008Utils.Indices(0, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||||
|
|
||||||
|
trackPoint.setLatitude(0);
|
||||||
|
trackPoint.setLongitude(0);
|
||||||
|
assertEquals(new EGM2008Utils.Indices(1080, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||||
|
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(180);
|
||||||
|
assertEquals(new EGM2008Utils.Indices(2160, 4320 / 2), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||||
|
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(-180);
|
||||||
|
assertEquals(new EGM2008Utils.Indices(2160, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getUndulationRaw_ok() throws IOException {
|
||||||
|
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA))) {
|
||||||
|
assertEquals(40966, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(0, 0)));
|
||||||
|
assertEquals(41742, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(1081, 0)));
|
||||||
|
assertEquals(25950, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(2160, 4319)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EOFException.class)
|
||||||
|
public void getUndulationRaw_error() throws IOException {
|
||||||
|
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA))) {
|
||||||
|
assertEquals(0, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(2161, 4320)), 0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ package de.dennisguse.opentracks;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@@ -36,6 +38,8 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
|
//TODO It is actually not a List; we need a better name for this class
|
||||||
|
//TODO Check if this class is still such a good idea; inheritance might limit maintainability
|
||||||
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
|
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
|
||||||
|
|
||||||
private static final String TAG = AbstractListActivity.class.getSimpleName();
|
private static final String TAG = AbstractListActivity.class.getSimpleName();
|
||||||
@@ -43,8 +47,6 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
protected static final int GPS_REQUEST_CODE = 6;
|
protected static final int GPS_REQUEST_CODE = 6;
|
||||||
private static final int DELETE_REQUEST_CODE = 3;
|
private static final int DELETE_REQUEST_CODE = 3;
|
||||||
|
|
||||||
protected Track.Id recordingTrackId;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (requestCode == DELETE_REQUEST_CODE) {
|
if (requestCode == DELETE_REQUEST_CODE) {
|
||||||
@@ -68,7 +70,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
boolean stopRecording = false;
|
boolean stopRecording = false;
|
||||||
|
|
||||||
for (Track.Id trackId : trackIds) {
|
for (Track.Id trackId : trackIds) {
|
||||||
if (trackId.equals(recordingTrackId)) {
|
if (trackId.equals(getRecordingTrackId())) {
|
||||||
stopRecording = true;
|
stopRecording = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -87,10 +89,17 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
* Gets the track recording service connection.
|
* Gets the track recording service connection.
|
||||||
* For stopping the current recording if need to delete the current recording track.
|
* For stopping the current recording if need to delete the current recording track.
|
||||||
*/
|
*/
|
||||||
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection();
|
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after {@link TrackDeleteActivity} returns its result.
|
* Called after {@link TrackDeleteActivity} returns its result.
|
||||||
*/
|
*/
|
||||||
abstract protected void onTrackDeleted();
|
abstract protected void onTrackDeleted();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected Track.Id getRecordingTrackId() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.app.SearchManager;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -42,7 +43,6 @@ import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
|||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
|||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller, TrackRecordingServiceStatus.Listener {
|
public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller {
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
|
|
||||||
@@ -59,8 +59,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
private Track.Id recordingTrackId;
|
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
private boolean recordingTrackPaused;
|
|
||||||
private Track track;
|
private Track track;
|
||||||
private MarkerResourceCursorAdapter resourceCursorAdapter;
|
private MarkerResourceCursorAdapter resourceCursorAdapter;
|
||||||
|
|
||||||
@@ -74,9 +74,13 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
if (service != null) {
|
if (service == null) {
|
||||||
service.addListener(MarkerListActivity.this);
|
Log.w(TAG, "could not get TrackRecordingService");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service.getRecordingStatusObservable()
|
||||||
|
.observe(MarkerListActivity.this, status -> onRecordingStatusChanged(status));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -178,7 +182,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingTrackId) && !recordingTrackPaused);
|
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingStatus.getTrackId()) && !recordingStatus.isPaused());
|
||||||
return super.onPrepareOptionsMenu(menu);
|
return super.onPrepareOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +294,10 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
|
recordingStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
private class MarkerLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {
|
private class MarkerLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
private String searchQuery = null;
|
private String searchQuery = null;
|
||||||
@@ -344,14 +352,4 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
resourceCursorAdapter.swapCursor(null);
|
resourceCursorAdapter.swapCursor(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrackRecordingPaused(boolean isPaused) {
|
|
||||||
recordingTrackPaused = isPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrackRecordingId(Track.Id trackId) {
|
|
||||||
recordingTrackId = trackId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.drawable.TransitionDrawable;
|
import android.graphics.drawable.TransitionDrawable;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
@@ -37,6 +38,8 @@ import de.dennisguse.opentracks.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
|
//TODO Subscribe to TrackRecordingService.recordingStatus
|
||||||
|
//TODO Could be a fragment
|
||||||
public class TrackController implements View.OnTouchListener {
|
public class TrackController implements View.OnTouchListener {
|
||||||
|
|
||||||
private static final String TAG = TrackController.class.getSimpleName();
|
private static final String TAG = TrackController.class.getSimpleName();
|
||||||
@@ -54,17 +57,16 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
private final TrackControllerBinding viewBinding;
|
private final TrackControllerBinding viewBinding;
|
||||||
private final TransitionDrawable transition;
|
private final TransitionDrawable transition;
|
||||||
|
|
||||||
private boolean isRecording;
|
|
||||||
private boolean isRecordingPaused;
|
private TrackRecordingService.RecordingStatus recordingStatus;
|
||||||
private Duration totalTime;
|
private Duration totalTime;
|
||||||
private boolean isResumed = false;
|
|
||||||
|
|
||||||
private final Callback callback;
|
private final Callback callback;
|
||||||
|
|
||||||
// A runnable to update the total time.
|
// A runnable to update the total time.
|
||||||
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (isResumed && isRecording && !isRecordingPaused) {
|
if (isResumed() && recordingStatus.isRecordingAndNotPaused()) {
|
||||||
updateTotalTime();
|
updateTotalTime();
|
||||||
setTotalTime();
|
setTotalTime();
|
||||||
handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
|
handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
|
||||||
@@ -84,7 +86,7 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
|
|
||||||
viewBinding.trackControllerRecord.setOnTouchListener(this);
|
viewBinding.trackControllerRecord.setOnTouchListener(this);
|
||||||
viewBinding.trackControllerRecord.setOnClickListener((view) -> {
|
viewBinding.trackControllerRecord.setOnClickListener((view) -> {
|
||||||
if (buttonDelay != null || (isRecording && !isRecordingPaused)) {
|
if (buttonDelay != null || recordingStatus.isRecordingAndNotPaused()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +100,10 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouch(final View view, MotionEvent event) {
|
public boolean onTouch(final View view, MotionEvent event) {
|
||||||
|
if (!isResumed()) {
|
||||||
|
Log.w(TAG, "The UI is already gone, no need to update anything; seen on Android 7.1.2");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -120,7 +126,7 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
//To pause a recording
|
//To pause a recording
|
||||||
if (viewBinding.trackControllerRecord.equals(view)
|
if (viewBinding.trackControllerRecord.equals(view)
|
||||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||||
&& isRecording && !isRecordingPaused) {
|
&& recordingStatus.isRecordingAndNotPaused()) {
|
||||||
|
|
||||||
transition.startTransition(buttonDelayDuration);
|
transition.startTransition(buttonDelayDuration);
|
||||||
|
|
||||||
@@ -143,7 +149,7 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
//To stop a recording
|
//To stop a recording
|
||||||
if (viewBinding.trackControllerStop.equals(view)
|
if (viewBinding.trackControllerStop.equals(view)
|
||||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||||
&& isRecording) {
|
&& recordingStatus.isRecording()) {
|
||||||
|
|
||||||
|
|
||||||
transition.startTransition(buttonDelayDuration);
|
transition.startTransition(buttonDelayDuration);
|
||||||
@@ -165,13 +171,15 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(boolean recording, boolean paused) {
|
public void update(TrackRecordingService.RecordingStatus recordingStatus) {
|
||||||
if (!isResumed) {
|
if (!isResumed()) {
|
||||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isRecording = recording;
|
|
||||||
isRecordingPaused = paused;
|
this.recordingStatus = recordingStatus;
|
||||||
|
boolean isRecording = recordingStatus.isRecording();
|
||||||
|
boolean isRecordingPaused = recordingStatus.isPaused();
|
||||||
boolean visible = alwaysShow || isRecording;
|
boolean visible = alwaysShow || isRecording;
|
||||||
viewBinding.trackControllerContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
|
viewBinding.trackControllerContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
@@ -197,19 +205,19 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
|
|
||||||
viewBinding.trackControllerTotalTime.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
viewBinding.trackControllerTotalTime.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
if (isRecording) {
|
if (recordingStatus.isRecording()) {
|
||||||
updateTotalTime();
|
updateTotalTime();
|
||||||
setTotalTime();
|
setTotalTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onResume(boolean recording, boolean paused) {
|
void onResume(TrackRecordingService.RecordingStatus recordingStatus) {
|
||||||
isResumed = true;
|
this.recordingStatus = recordingStatus;
|
||||||
update(recording, paused);
|
update(recordingStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPause() {
|
void onPause() {
|
||||||
isResumed = false;
|
recordingStatus = null;
|
||||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,12 +243,18 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showStatusSetDefaultText() {
|
private void showStatusSetDefaultText() {
|
||||||
viewBinding.trackControllerStatus.setText(isRecordingPaused ? R.string.generic_paused : R.string.generic_recording);
|
viewBinding.trackControllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isResumed() {
|
||||||
|
return recordingStatus != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void recordStart();
|
void recordStart();
|
||||||
|
|
||||||
void recordPause();
|
void recordPause();
|
||||||
|
|
||||||
void recordStop();
|
void recordStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import android.view.View;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
@@ -55,7 +56,6 @@ import de.dennisguse.opentracks.databinding.TrackListBinding;
|
|||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
|
||||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
@@ -71,7 +71,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
|||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback, TrackRecordingServiceStatus.Listener {
|
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback {
|
||||||
|
|
||||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||||
|
|
||||||
@@ -80,7 +80,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private TrackController trackController;
|
private TrackController trackController;
|
||||||
private ResourceCursorAdapter resourceCursorAdapter;
|
private ResourceCursorAdapter resourceCursorAdapter;
|
||||||
private GpsStatusValue gpsStatusValue;
|
|
||||||
|
|
||||||
private TrackListBinding viewBinding;
|
private TrackListBinding viewBinding;
|
||||||
|
|
||||||
@@ -89,7 +88,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Preferences
|
// Preferences
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
|
|
||||||
private boolean recordingTrackPaused = true;
|
private GpsStatusValue gpsStatusValue = TrackRecordingService.STATUS_GPS_DEFAULT;
|
||||||
|
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
|
|
||||||
// Callback when an item is selected in the contextual action mode
|
// Callback when an item is selected in the contextual action mode
|
||||||
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||||
@@ -118,7 +118,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
TrackListActivity.this.invalidateOptionsMenu();
|
TrackListActivity.this.invalidateOptionsMenu();
|
||||||
loaderCallbacks.restart();
|
loaderCallbacks.restart();
|
||||||
trackController.update(isRecording(), recordingTrackPaused);
|
trackController.onResume(recordingStatus);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,27 +133,29 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// After binding changes (e.g., becomes available), update the total time in trackController.
|
// After binding changes (e.g., becomes available), update the total time in trackController.
|
||||||
runOnUiThread(() -> trackController.update(isRecording(), recordingTrackPaused));
|
runOnUiThread(() -> trackController.update(recordingStatus));
|
||||||
|
|
||||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
Log.d(TAG, "service not available to start gps or a new recording");
|
Log.e(TAG, "service not available to start gps or a new recording");
|
||||||
gpsStatusValue = GpsStatusValue.GPS_NONE;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service.getRecordingStatusObservable()
|
||||||
|
.observe(TrackListActivity.this, status -> onRecordingStatusChanged(status));
|
||||||
|
|
||||||
// Get GPS status and listen GPS status changes.
|
// Get GPS status and listen GPS status changes.
|
||||||
gpsStatusValue = service.getGpsStatus();
|
service.getGpsStatusObservable()
|
||||||
updateGpsMenuItem(true, isRecording());
|
.observe(TrackListActivity.this, status -> onGpsStatusChanged(status));
|
||||||
service.addListener(TrackListActivity.this);
|
|
||||||
|
|
||||||
if (isGpsStarted()) {
|
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||||
|
|
||||||
|
if (service.getGpsStatusObservable().getValue().isGpsStarted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Not cool to do this in a callback that might be called more than once!
|
||||||
service.tryStartGps();
|
service.tryStartGps();
|
||||||
gpsStatusValue = GpsStatusValue.GPS_ENABLED;
|
|
||||||
updateGpsMenuItem(true, isRecording());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -165,8 +167,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
|
||||||
gpsStatusValue = GpsStatusValue.GPS_NONE;
|
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
@@ -181,7 +181,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||||
if (isRecording() && trackId == recordingTrackId.getId()) {
|
if (recordingStatus.isRecording() && trackId == recordingStatus.getTrackId().getId()) {
|
||||||
// Is recording -> open record activity.
|
// Is recording -> open record activity.
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||||
@@ -211,7 +211,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
|
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
|
||||||
|
|
||||||
Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
|
Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
|
||||||
boolean isRecording = trackId.equals(recordingTrackId);
|
boolean isRecording = trackId.equals(recordingStatus.getTrackId());
|
||||||
String icon = cursor.getString(iconIndex);
|
String icon = cursor.getString(iconIndex);
|
||||||
int iconId = TrackIconUtils.getIconDrawable(icon);
|
int iconId = TrackIconUtils.getIconDrawable(icon);
|
||||||
String name = cursor.getString(nameIndex);
|
String name = cursor.getString(nameIndex);
|
||||||
@@ -222,7 +222,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
||||||
String description = cursor.getString(descriptionIndex);
|
String description = cursor.getString(descriptionIndex);
|
||||||
|
|
||||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused,
|
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingStatus.isPaused(),
|
||||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||||
startTime, true, category, description, false);
|
startTime, true, category, description, false);
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Update UI
|
// Update UI
|
||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||||
trackController.onResume(isRecording(), recordingTrackPaused);
|
trackController.onResume(recordingStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -306,7 +306,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
updateMenuItems(isGpsStarted(), isRecording());
|
updateMenuItems(gpsStatusValue.isGpsStarted(), recordingStatus.isRecording());
|
||||||
|
|
||||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
searchView.setQuery("", false);
|
searchView.setQuery("", false);
|
||||||
@@ -322,7 +322,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||||
} else {
|
} else {
|
||||||
// Invoke trackRecordingService
|
// Invoke trackRecordingService
|
||||||
if (!isGpsStarted()) {
|
if (!gpsStatusValue.isGpsStarted()) {
|
||||||
trackRecordingServiceConnection.startAndBind(this);
|
trackRecordingServiceConnection.startAndBind(this);
|
||||||
bindChangedCallback.run();
|
bindChangedCallback.run();
|
||||||
} else {
|
} else {
|
||||||
@@ -413,8 +413,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGpsStarted() {
|
@Nullable
|
||||||
return gpsStatusValue != GpsStatusValue.GPS_NONE && gpsStatusValue != GpsStatusValue.GPS_DISABLED;
|
@Override
|
||||||
|
protected Track.Id getRecordingTrackId() {
|
||||||
|
return recordingStatus.getTrackId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestGPSPermissions() {
|
private void requestGPSPermissions() {
|
||||||
@@ -554,16 +556,15 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordStart() {
|
public void recordStart() {
|
||||||
if (recordingTrackId == null) {
|
if (recordingStatus.getTrackId() == null) {
|
||||||
// Not recording -> Recording
|
// Not recording -> Recording
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
} else if (recordingTrackPaused) {
|
} else if (recordingStatus.isPaused()) {
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
trackRecordingServiceConnection.resumeTrack();
|
trackRecordingServiceConnection.resumeTrack();
|
||||||
trackController.update(true, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,7 +572,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
public void recordPause() {
|
public void recordPause() {
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
trackRecordingServiceConnection.pauseTrack();
|
trackRecordingServiceConnection.pauseTrack();
|
||||||
trackController.update(true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -580,29 +580,15 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRecording() {
|
public void onGpsStatusChanged(GpsStatusValue newStatus) {
|
||||||
return recordingTrackId != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGpsStatus(GpsStatusValue newStatus) {
|
|
||||||
gpsStatusValue = newStatus;
|
gpsStatusValue = newStatus;
|
||||||
updateGpsMenuItem(true, isRecording());
|
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrackRecordingPaused(boolean isPaused) {
|
|
||||||
if (recordingTrackPaused != isPaused) {
|
|
||||||
trackController.update(isRecording(), isPaused);
|
|
||||||
}
|
|
||||||
recordingTrackPaused = isPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
public void onTrackRecordingId(Track.Id trackId) {
|
recordingStatus = status;
|
||||||
if (!isRecording() && trackId != null) {
|
|
||||||
trackController.update(true, recordingTrackPaused);
|
trackController.update(recordingStatus);
|
||||||
}
|
|
||||||
recordingTrackId = trackId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -24,6 +25,7 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
@@ -42,10 +44,10 @@ import de.dennisguse.opentracks.fragments.IntervalsFragment;
|
|||||||
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to show the track detail, record a new track or resumes an existing one.
|
* An activity to show the track detail, record a new track or resumes an existing one.
|
||||||
@@ -54,7 +56,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
|||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
|
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
|
||||||
public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface, TrackRecordingServiceStatus.Listener {
|
public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
|
||||||
|
|
||||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||||
|
|
||||||
@@ -67,11 +69,13 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
|
|
||||||
// The following are set in onCreate.
|
// The following are set in onCreate.
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
private SharedPreferences sharedPreferences;
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
|
|
||||||
private TrackRecordedBinding viewBinding;
|
private TrackRecordedBinding viewBinding;
|
||||||
|
|
||||||
private Track.Id trackId;
|
private Track.Id trackId;
|
||||||
|
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
|
|
||||||
@@ -79,8 +83,22 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
if (service != null) {
|
if (service == null) {
|
||||||
service.addListener(TrackRecordedActivity.this);
|
Log.w(TAG, "could not get TrackRecordingService");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
service.getRecordingStatusObservable()
|
||||||
|
.observe(TrackRecordedActivity.this, status -> onRecordingStatusChanged(status));
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
if (PreferencesUtils.isKey(TrackRecordedActivity.this, R.string.recording_distance_interval_key, key)) {
|
||||||
|
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordedActivity.this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -90,6 +108,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
contentProviderUtils = new ContentProviderUtils(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
|
|
||||||
trackDataHub = new TrackDataHub(this);
|
trackDataHub = new TrackDataHub(this);
|
||||||
@@ -110,6 +130,10 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||||
|
|
||||||
trackDataHub.start();
|
trackDataHub.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +156,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
super.onStop();
|
super.onStop();
|
||||||
trackRecordingServiceConnection.unbind(this);
|
trackRecordingServiceConnection.unbind(this);
|
||||||
trackDataHub.stop();
|
trackDataHub.stop();
|
||||||
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,7 +187,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
menu.findItem(R.id.track_detail_resume_track).setVisible(recordingTrackId == null);
|
menu.findItem(R.id.track_detail_resume_track).setVisible(!recordingStatus.isRecording());
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
setTitle(track != null ? track.getName() : "");
|
setTitle(track != null ? track.getName() : "");
|
||||||
return super.onPrepareOptionsMenu(menu);
|
return super.onPrepareOptionsMenu(menu);
|
||||||
@@ -217,12 +242,13 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
protected Track.Id getRecordingTrackId() {
|
||||||
// Not needed.
|
return recordingStatus.getTrackId();
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onTrackDeleted() {
|
protected void onTrackDeleted() {
|
||||||
runOnUiThread(this::finish);
|
runOnUiThread(this::finish);
|
||||||
@@ -305,8 +331,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
startPostponedEnterTransition();
|
startPostponedEnterTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
public void onTrackRecordingId(Track.Id trackId) {
|
recordingStatus = status;
|
||||||
recordingTrackId = trackId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,6 @@ import de.dennisguse.opentracks.fragments.IntervalsFragment;
|
|||||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
@@ -44,7 +43,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
|||||||
*/
|
*/
|
||||||
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
||||||
//This mode of communication should be removed anyhow.
|
//This mode of communication should be removed anyhow.
|
||||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback, TrackRecordingServiceStatus.Listener {
|
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback {
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
|
|
||||||
@@ -62,20 +61,23 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
private TrackRecordingBinding viewBinding;
|
private TrackRecordingBinding viewBinding;
|
||||||
|
|
||||||
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
|
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
|
||||||
|
@Deprecated
|
||||||
private Track.Id trackId;
|
private Track.Id trackId;
|
||||||
private boolean recordingTrackPaused;
|
|
||||||
|
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
|
|
||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// After binding changes (is available), update the total time in trackController.
|
|
||||||
runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
|
|
||||||
|
|
||||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
Log.d(TAG, "could not get TrackRecordingService");
|
Log.w(TAG, "could not get TrackRecordingService");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service.getRecordingStatusObservable()
|
||||||
|
.observe(TrackRecordingActivity.this, status -> onRecordingStatusChanged(status));
|
||||||
|
|
||||||
if (!service.isRecording()) {
|
if (!service.isRecording()) {
|
||||||
if (trackId == null) {
|
if (trackId == null) {
|
||||||
// trackId isn't initialized -> leads a new recording.
|
// trackId isn't initialized -> leads a new recording.
|
||||||
@@ -87,12 +89,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
|
|
||||||
// A recording track is on.
|
// A recording track is on.
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackDataHub.setRecordingTrackId(trackId);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
|
|
||||||
trackController.update(true, false);
|
trackController.onResume(recordingStatus);
|
||||||
trackController.onResume(true, recordingTrackPaused);
|
|
||||||
}
|
}
|
||||||
service.addListener(TrackRecordingActivity.this);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,11 +108,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
|
||||||
setFullscreenPolicy();
|
setFullscreenPolicy();
|
||||||
}
|
}
|
||||||
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_distance_interval_key, key)) {
|
||||||
|
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordingActivity.this));
|
||||||
|
}
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
runOnUiThread(() -> {
|
|
||||||
TrackRecordingActivity.this.invalidateOptionsMenu();
|
runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu);
|
||||||
trackController.update(true, recordingTrackPaused);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -207,13 +208,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
this.invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
if (trackId != null) {
|
if (trackId != null) {
|
||||||
|
//TODO Pass recordingStatus directly to them
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackDataHub.setRecordingTrackId(trackId);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
trackDataHub.setRecordingTrackPaused(recordingTrackPaused);
|
trackController.onResume(recordingStatus);
|
||||||
trackController.onResume(true, recordingTrackPaused);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -264,7 +265,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
updateMenuItems(recordingTrackPaused);
|
updateMenuItems(recordingStatus.isPaused());
|
||||||
return super.onPrepareOptionsMenu(menu);
|
return super.onPrepareOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,14 +340,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
public void recordStart() {
|
public void recordStart() {
|
||||||
updateMenuItems(false);
|
updateMenuItems(false);
|
||||||
trackRecordingServiceConnection.resumeTrack();
|
trackRecordingServiceConnection.resumeTrack();
|
||||||
trackController.update(true, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordPause() {
|
public void recordPause() {
|
||||||
updateMenuItems(true);
|
updateMenuItems(true);
|
||||||
trackRecordingServiceConnection.pauseTrack();
|
trackRecordingServiceConnection.pauseTrack();
|
||||||
trackController.update(true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -404,24 +403,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
public void onTrackRecordingPaused(boolean isPaused) {
|
recordingStatus = status;
|
||||||
if (recordingTrackPaused != isPaused) {
|
|
||||||
trackController.update(true, isPaused);
|
trackController.update(recordingStatus);
|
||||||
trackDataHub.setRecordingTrackPaused(isPaused);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
}
|
|
||||||
recordingTrackPaused = isPaused;
|
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
setScreenOnPolicy();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Why should this be interesting? This activity is only used for currently recording tracks.
|
|
||||||
@Override
|
|
||||||
public void onTrackRecordingId(Track.Id newTrackId) {
|
|
||||||
if (newTrackId != null && !newTrackId.equals(trackId)) {
|
|
||||||
trackId = newTrackId;
|
|
||||||
trackController.update(true, recordingTrackPaused);
|
|
||||||
trackDataHub.setRecordingTrackId(newTrackId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ public class ChartPoint {
|
|||||||
|
|
||||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, double smoothedAltitude_m, boolean chartByDistance, boolean metricUnits) {
|
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, double smoothedAltitude_m, boolean chartByDistance, boolean metricUnits) {
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
timeOrDistance = trackStatistics.getTotalDistance().to(metricUnits);
|
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(metricUnits);
|
||||||
} else {
|
} else {
|
||||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
altitude = Distance.of(smoothedAltitude_m).to(metricUnits);
|
altitude = Distance.of(smoothedAltitude_m).toM_FT(metricUnits);
|
||||||
|
|
||||||
speed = smoothedSpeed.to(metricUnits);
|
speed = smoothedSpeed.to(metricUnits);
|
||||||
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||||
@@ -76,4 +76,9 @@ public class ChartPoint {
|
|||||||
public Double getPower() {
|
public Double getPower() {
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ChartPoint{" + "timeOrDistance=" + timeOrDistance + '}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -993,7 +993,7 @@ public class ChartView extends View {
|
|||||||
|
|
||||||
private double getMarkerXValue(Marker marker) {
|
private double getMarkerXValue(Marker marker) {
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
return marker.getLength().to(metricUnits);
|
return marker.getLength().toKM_Miles(metricUnits);
|
||||||
} else {
|
} else {
|
||||||
return marker.getDuration().toMillis();
|
return marker.getDuration().toMillis();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ import android.util.Log;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||||
@@ -39,7 +41,9 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
|||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
|
import de.dennisguse.opentracks.util.EGM2008Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track data hub.
|
* Track data hub.
|
||||||
@@ -71,22 +75,23 @@ public class TrackDataHub {
|
|||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
private final int targetNumPoints;
|
private final int targetNumPoints;
|
||||||
|
|
||||||
|
private EGM2008Utils.EGM2008Correction egm2008Correction;
|
||||||
|
|
||||||
private boolean started;
|
private boolean started;
|
||||||
private HandlerThread handlerThread;
|
private HandlerThread handlerThread;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
|
|
||||||
private Track.Id selectedTrackId;
|
private Track.Id selectedTrackId;
|
||||||
@Deprecated
|
|
||||||
//TODO Is there a better way to handle this? e.g., passing information rather from Activity than from Activity to TrackDataHub
|
private Distance recordingDistanceInterval;
|
||||||
private Track.Id recordingTrackId;
|
|
||||||
@Deprecated
|
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
//TODO Is there a better way to handle this? e.g., passing information rather from Activity than from Activity to TrackDataHub
|
|
||||||
private boolean recordingTrackPaused;
|
|
||||||
|
|
||||||
// Track points sampling state
|
// Track points sampling state
|
||||||
private int numLoadedPoints;
|
private int numLoadedPoints;
|
||||||
private TrackPoint.Id firstSeenTrackPointId;
|
private TrackPoint.Id firstSeenTrackPointId;
|
||||||
private TrackPoint.Id lastSeenTrackPointId;
|
private TrackPoint.Id lastSeenTrackPointId;
|
||||||
|
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||||
|
|
||||||
// Registered listeners
|
// Registered listeners
|
||||||
private ContentObserver tracksTableObserver;
|
private ContentObserver tracksTableObserver;
|
||||||
@@ -141,12 +146,6 @@ public class TrackDataHub {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
||||||
|
|
||||||
handler.post(() -> {
|
|
||||||
if (started) {
|
|
||||||
loadDataForAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
@@ -156,6 +155,7 @@ public class TrackDataHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
started = false;
|
started = false;
|
||||||
|
trackStatisticsUpdater = null;
|
||||||
|
|
||||||
//Unregister listeners
|
//Unregister listeners
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
@@ -208,14 +208,14 @@ public class TrackDataHub {
|
|||||||
* Returns true if the selected track is recording.
|
* Returns true if the selected track is recording.
|
||||||
*/
|
*/
|
||||||
public boolean isSelectedTrackRecording() {
|
public boolean isSelectedTrackRecording() {
|
||||||
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId);
|
return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the selected track is paused.
|
* Returns true if the selected track is paused.
|
||||||
*/
|
*/
|
||||||
public boolean isSelectedTrackPaused() {
|
public boolean isSelectedTrackPaused() {
|
||||||
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && recordingTrackPaused;
|
return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId()) && recordingStatus.isPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -351,8 +351,6 @@ public class TrackDataHub {
|
|||||||
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
|
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
|
||||||
|
|
||||||
TrackPoint trackPoint = null;
|
TrackPoint trackPoint = null;
|
||||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
||||||
|
|
||||||
@@ -365,6 +363,8 @@ public class TrackDataHub {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
correctAltitude(trackPoint);
|
||||||
|
|
||||||
if (localFirstSeenTrackPointId == null) {
|
if (localFirstSeenTrackPointId == null) {
|
||||||
localFirstSeenTrackPointId = trackPointId;
|
localFirstSeenTrackPointId = trackPointId;
|
||||||
}
|
}
|
||||||
@@ -374,7 +374,7 @@ public class TrackDataHub {
|
|||||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, Distance.of(50)); //TODO
|
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||||
|
|
||||||
// Also include the last point if the selected track is not recording.
|
// Also include the last point if the selected track is not recording.
|
||||||
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||||
@@ -408,6 +408,24 @@ public class TrackDataHub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void correctAltitude(TrackPoint trackPoint) {
|
||||||
|
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
|
||||||
|
Log.d(TAG, "No altitude correction necessary.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (egm2008Correction == null || !egm2008Correction.canCorrect(trackPoint.getLocation())) {
|
||||||
|
try {
|
||||||
|
egm2008Correction = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Could not load altitude correction for " + trackPoint, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trackPoint.setAltitude(Altitude.EGM2008.of(egm2008Correction.correctAltitude(trackPoint.getLocation())));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the track points sampling states.
|
* Resets the track points sampling states.
|
||||||
*/
|
*/
|
||||||
@@ -415,13 +433,14 @@ public class TrackDataHub {
|
|||||||
numLoadedPoints = 0;
|
numLoadedPoints = 0;
|
||||||
firstSeenTrackPointId = null;
|
firstSeenTrackPointId = null;
|
||||||
lastSeenTrackPointId = null;
|
lastSeenTrackPointId = null;
|
||||||
|
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRecordingTrackId(Track.Id recordingTrackId) {
|
public void setRecordingStatus(TrackRecordingService.RecordingStatus recordingStatus) {
|
||||||
this.recordingTrackId = recordingTrackId;
|
this.recordingStatus = recordingStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRecordingTrackPaused(boolean recordingTrackPaused) {
|
public void setRecordingDistanceInterval(Distance recordingDistanceInterval) {
|
||||||
this.recordingTrackPaused = recordingTrackPaused;
|
this.recordingDistanceInterval = recordingDistanceInterval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package de.dennisguse.opentracks.content.data;
|
||||||
|
|
||||||
|
public abstract class Altitude {
|
||||||
|
|
||||||
|
private final double altitude_m;
|
||||||
|
|
||||||
|
private Altitude(double altitude_m) {
|
||||||
|
this.altitude_m = altitude_m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double toM() {
|
||||||
|
return altitude_m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WGS84 extends Altitude {
|
||||||
|
|
||||||
|
private WGS84(double altitude_m) {
|
||||||
|
super(altitude_m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Altitude of(double altitude_m) {
|
||||||
|
return new WGS84(altitude_m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EGM2008 extends Altitude {
|
||||||
|
|
||||||
|
private EGM2008(double altitude_m) {
|
||||||
|
super(altitude_m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Altitude of(double altitude_m) {
|
||||||
|
return new EGM2008(altitude_m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -98,24 +98,12 @@ public class Distance {
|
|||||||
return toKM() * UnitConversions.KM_TO_MI;
|
return toKM() * UnitConversions.KM_TO_MI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double to(boolean metricUnit) {
|
public double toKM_Miles(boolean metricUnit) {
|
||||||
return to(metricUnit ? Unit.KM : Unit.MILES);
|
return metricUnit ? toKM() : toMI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double to(Unit unit) {
|
public double toM_FT(boolean metricUnit) {
|
||||||
switch (unit) {
|
return metricUnit ? toM() : toFT();
|
||||||
case KM:
|
|
||||||
return toKM();
|
|
||||||
case MILES:
|
|
||||||
return toMI();
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Unit {
|
|
||||||
KM,
|
|
||||||
MILES
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public final class Marker {
|
|||||||
private Double latitude;
|
private Double latitude;
|
||||||
private Double longitude;
|
private Double longitude;
|
||||||
private Float accuracy;
|
private Float accuracy;
|
||||||
private Double altitude_m;
|
private Altitude altitude;
|
||||||
private Float bearing;
|
private Float bearing;
|
||||||
|
|
||||||
//TODO It is the distance from the track starting point; rename to something more meaningful
|
//TODO It is the distance from the track starting point; rename to something more meaningful
|
||||||
@@ -77,7 +77,7 @@ public final class Marker {
|
|||||||
this.latitude = trackPoint.getLatitude();
|
this.latitude = trackPoint.getLatitude();
|
||||||
this.longitude = trackPoint.getLongitude();
|
this.longitude = trackPoint.getLongitude();
|
||||||
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
||||||
if (trackPoint.hasAltitude()) this.altitude_m = trackPoint.getAltitude();
|
if (trackPoint.hasAltitude()) this.altitude = trackPoint.getAltitude();
|
||||||
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
||||||
|
|
||||||
this.length = Distance.of(0); //TODO Not cool!
|
this.length = Distance.of(0); //TODO Not cool!
|
||||||
@@ -167,7 +167,7 @@ public final class Marker {
|
|||||||
location.setAccuracy(accuracy);
|
location.setAccuracy(accuracy);
|
||||||
}
|
}
|
||||||
if (hasAltitude()) {
|
if (hasAltitude()) {
|
||||||
location.setAltitude(altitude_m);
|
location.setAltitude(altitude.toM());
|
||||||
}
|
}
|
||||||
|
|
||||||
return location;
|
return location;
|
||||||
@@ -202,15 +202,15 @@ public final class Marker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasAltitude() {
|
public boolean hasAltitude() {
|
||||||
return altitude_m != null;
|
return altitude != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Double getAltitude() {
|
public Altitude getAltitude() {
|
||||||
return altitude_m;
|
return altitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAltitude(double altitude_m) {
|
public void setAltitude(Altitude altitude) {
|
||||||
this.altitude_m = altitude_m;
|
this.altitude = altitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasBearing() {
|
public boolean hasBearing() {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.os.Parcel;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -51,7 +52,7 @@ public class TrackPoint {
|
|||||||
private Double latitude;
|
private Double latitude;
|
||||||
private Double longitude;
|
private Double longitude;
|
||||||
private Float accuracy;
|
private Float accuracy;
|
||||||
private Double altitude_m;
|
private Altitude altitude;
|
||||||
private Speed speed;
|
private Speed speed;
|
||||||
private Float bearing;
|
private Float bearing;
|
||||||
private Distance sensorDistance_m;
|
private Distance sensorDistance_m;
|
||||||
@@ -102,7 +103,7 @@ public class TrackPoint {
|
|||||||
|
|
||||||
this.latitude = location.getLatitude();
|
this.latitude = location.getLatitude();
|
||||||
this.longitude = location.getLongitude();
|
this.longitude = location.getLongitude();
|
||||||
this.altitude_m = location.getAltitude();
|
this.altitude = Altitude.WGS84.of(location.getAltitude());
|
||||||
this.speed = Speed.of(location.getSpeed());
|
this.speed = Speed.of(location.getSpeed());
|
||||||
this.accuracy = location.getAccuracy();
|
this.accuracy = location.getAccuracy();
|
||||||
|
|
||||||
@@ -114,11 +115,11 @@ public class TrackPoint {
|
|||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackPoint(double latitude, double longitude, Double altitude, Instant time) {
|
public TrackPoint(double latitude, double longitude, Altitude altitude, Instant time) {
|
||||||
this(Type.TRACKPOINT);
|
this(Type.TRACKPOINT);
|
||||||
this.latitude = latitude;
|
this.latitude = latitude;
|
||||||
this.longitude = longitude;
|
this.longitude = longitude;
|
||||||
this.altitude_m = altitude;
|
this.altitude = altitude;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +213,7 @@ public class TrackPoint {
|
|||||||
location.setAccuracy(accuracy);
|
location.setAccuracy(accuracy);
|
||||||
}
|
}
|
||||||
if (hasAltitude()) {
|
if (hasAltitude()) {
|
||||||
location.setAltitude(altitude_m);
|
location.setAltitude(altitude.toM());
|
||||||
}
|
}
|
||||||
|
|
||||||
return location;
|
return location;
|
||||||
@@ -257,15 +258,20 @@ public class TrackPoint {
|
|||||||
|
|
||||||
|
|
||||||
public boolean hasAltitude() {
|
public boolean hasAltitude() {
|
||||||
return altitude_m != null;
|
return altitude != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getAltitude() {
|
public Altitude getAltitude() {
|
||||||
return altitude_m;
|
return altitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAltitude(double altitude) {
|
@VisibleForTesting
|
||||||
this.altitude_m = altitude;
|
public void setAltitude(double altitude_m) {
|
||||||
|
this.altitude = Altitude.WGS84.of(altitude_m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAltitude(Altitude altitude) {
|
||||||
|
this.altitude = altitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSpeed() {
|
public boolean hasSpeed() {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.BuildConfig;
|
import de.dennisguse.opentracks.BuildConfig;
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||||
@@ -320,7 +321,7 @@ public class ContentProviderUtils {
|
|||||||
marker.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6);
|
marker.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6);
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(altitudeIndex)) {
|
if (!cursor.isNull(altitudeIndex)) {
|
||||||
marker.setAltitude(cursor.getFloat(altitudeIndex));
|
marker.setAltitude(Altitude.WGS84.of(cursor.getFloat(altitudeIndex)));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(accuracyIndex)) {
|
if (!cursor.isNull(accuracyIndex)) {
|
||||||
marker.setAccuracy(cursor.getFloat(accuracyIndex));
|
marker.setAccuracy(cursor.getFloat(accuracyIndex));
|
||||||
@@ -489,7 +490,7 @@ public class ContentProviderUtils {
|
|||||||
values.put(MarkerColumns.LATITUDE, (int) (marker.getLatitude() * 1E6));
|
values.put(MarkerColumns.LATITUDE, (int) (marker.getLatitude() * 1E6));
|
||||||
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
|
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
|
||||||
if (marker.hasAltitude()) {
|
if (marker.hasAltitude()) {
|
||||||
values.put(MarkerColumns.ALTITUDE, marker.getAltitude());
|
values.put(MarkerColumns.ALTITUDE, marker.getAltitude().toM());
|
||||||
}
|
}
|
||||||
if (marker.hasAccuracy()) {
|
if (marker.hasAccuracy()) {
|
||||||
values.put(MarkerColumns.ACCURACY, marker.getAccuracy());
|
values.put(MarkerColumns.ACCURACY, marker.getAccuracy());
|
||||||
@@ -539,7 +540,7 @@ public class ContentProviderUtils {
|
|||||||
trackPoint.setTime(Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex)));
|
trackPoint.setTime(Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex)));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(indexes.altitudeIndex)) {
|
if (!cursor.isNull(indexes.altitudeIndex)) {
|
||||||
trackPoint.setAltitude(cursor.getFloat(indexes.altitudeIndex));
|
trackPoint.setAltitude(Altitude.WGS84.of(cursor.getFloat(indexes.altitudeIndex)));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(indexes.accuracyIndex)) {
|
if (!cursor.isNull(indexes.accuracyIndex)) {
|
||||||
trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
|
trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
|
||||||
@@ -690,7 +691,7 @@ public class ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
values.put(TrackPointsColumns.TIME, trackPoint.getTime().toEpochMilli());
|
values.put(TrackPointsColumns.TIME, trackPoint.getTime().toEpochMilli());
|
||||||
if (trackPoint.hasAltitude()) {
|
if (trackPoint.hasAltitude()) {
|
||||||
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude());
|
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude().toM());
|
||||||
}
|
}
|
||||||
if (trackPoint.hasAccuracy()) {
|
if (trackPoint.hasAccuracy()) {
|
||||||
values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy());
|
values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy());
|
||||||
|
|||||||
@@ -318,24 +318,4 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
void setChartView(ChartView view) {
|
void setChartView(ChartView view) {
|
||||||
chartView = view;
|
chartView = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
void setMetricUnits(boolean value) {
|
|
||||||
chartView.setMetricUnits(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
void setReportSpeed(boolean value) {
|
|
||||||
chartView.setReportSpeed(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
void setChartByDistance(boolean value) {
|
|
||||||
chartByDistance = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
void setRecordingDistanceInterval(Distance value) {
|
|
||||||
recordingDistanceInterval = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
|
|
||||||
if (preferenceShowAltitude) {
|
if (preferenceShowAltitude) {
|
||||||
// Current altitude
|
// Current altitude
|
||||||
Float altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? (float) lastTrackPoint.getAltitude() : null;
|
Float altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? (float) lastTrackPoint.getAltitude().toM() : null;
|
||||||
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), altitude, preferenceMetricUnits);
|
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), altitude, preferenceMetricUnits);
|
||||||
viewBinding.statsAltitudeCurrentValue.setText(parts.first);
|
viewBinding.statsAltitudeCurrentValue.setText(parts.first);
|
||||||
viewBinding.statsAltitudeCurrentUnit.setText(parts.second);
|
viewBinding.statsAltitudeCurrentUnit.setText(parts.second);
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
if (printWriter != null) {
|
if (printWriter != null) {
|
||||||
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
|
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
|
||||||
if (marker.hasAltitude()) {
|
if (marker.hasAltitude()) {
|
||||||
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude()) + "</ele>");
|
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
|
||||||
}
|
}
|
||||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(marker.getTime()) + "</time>");
|
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(marker.getTime()) + "</time>");
|
||||||
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
|
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
|
||||||
@@ -281,7 +281,7 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
||||||
|
|
||||||
if (trackPoint.hasAltitude()) {
|
if (trackPoint.hasAltitude()) {
|
||||||
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
|
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(trackPoint.getAltitude().toM()) + "</ele>");
|
||||||
}
|
}
|
||||||
|
|
||||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||||
|
|||||||
+11
-86
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.io.file.importer;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.sqlite.SQLiteConstraintException;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@@ -29,8 +28,6 @@ import org.xml.sax.SAXException;
|
|||||||
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -38,10 +35,8 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.parsers.SAXParserFactory;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
@@ -62,7 +57,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter {
|
abstract class AbstractFileTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
||||||
|
|
||||||
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
|
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
|
||||||
|
|
||||||
@@ -127,24 +122,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public List<Track.Id> importFile(InputStream inputStream) {
|
|
||||||
try {
|
|
||||||
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, this);
|
|
||||||
return trackIds;
|
|
||||||
} catch (IOException | SAXException | ParserConfigurationException | ParsingException e) {
|
|
||||||
Log.e(TAG, "Unable to import file", e);
|
|
||||||
if (trackIds.size() > 0) {
|
|
||||||
cleanImport();
|
|
||||||
}
|
|
||||||
throw new ImportParserException(e);
|
|
||||||
} catch (SQLiteConstraintException e) {
|
|
||||||
Log.e(TAG, "Unable to import file", e);
|
|
||||||
throw new ImportAlreadyExistsException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onFileEnd() {
|
protected void onFileEnd() {
|
||||||
// Add markers to the last imported track
|
// Add markers to the last imported track
|
||||||
int size = trackIds.size();
|
int size = trackIds.size();
|
||||||
@@ -430,7 +407,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackPoint createTrackPoint() throws ParsingException {
|
protected TrackPoint createTrackPoint() throws ParsingException {
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -450,68 +427,14 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (altitude != null) {
|
if (altitude != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setAltitude(Double.parseDouble(altitude));
|
trackPoint.setAltitude(Altitude.WGS84.of(Double.parseDouble(altitude)));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speed != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setSpeed(Speed.of(speed));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (heartrate != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cadence != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (power != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setPower(Float.parseFloat(power));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gain != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setAltitudeGain(Float.parseFloat(gain));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s", gain)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (loss != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setAltitudeLoss(Float.parseFloat(loss));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude loss: %s", loss)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (distance != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setSensorDistance(Distance.of(distance));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,10 +461,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Cleans up import.
|
public List<Track.Id> getImportTrackIds() {
|
||||||
*/
|
return trackIds;
|
||||||
private void cleanImport() {
|
}
|
||||||
|
|
||||||
|
public void cleanImport() {
|
||||||
contentProviderUtils.deleteTracks(context, trackIds);
|
contentProviderUtils.deleteTracks(context, trackIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,7 +496,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ParsingException(@NonNull String message, Exception cause) {
|
protected ParsingException(@NonNull String message, Exception cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
|
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
|
||||||
@@ -79,6 +84,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
super(context, contentProviderUtils);
|
super(context, contentProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHandler getHandler() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
@@ -199,6 +209,66 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
category = null;
|
category = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TrackPoint createTrackPoint() throws ParsingException {
|
||||||
|
TrackPoint trackPoint = super.createTrackPoint();
|
||||||
|
|
||||||
|
if (speed != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setSpeed(Speed.of(speed));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (heartrate != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cadence != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (power != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setPower(Float.parseFloat(power));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gain != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setAltitudeGain(Float.parseFloat(gain));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s", gain)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (loss != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setAltitudeLoss(Float.parseFloat(loss));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude loss: %s", loss)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (distance != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setSensorDistance(Distance.of(distance));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackPoint;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On track point start.
|
* On track point start.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import androidx.core.app.JobIntentService;
|
|||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -47,23 +46,22 @@ public class ImportService extends JobIntentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void importFile(DocumentFile file) {
|
private void importFile(DocumentFile file) {
|
||||||
TrackImporter trackImporter;
|
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||||
|
|
||||||
String fileExtension = FileUtils.getExtension(file);
|
String fileExtension = FileUtils.getExtension(file);
|
||||||
|
try {
|
||||||
|
|
||||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||||
trackImporter = new GpxFileTrackImporter(this);
|
trackIds.addAll(new XMLImporter(new GpxFileTrackImporter(this)).importFile(this, file.getUri()));
|
||||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||||
trackImporter = new KmlFileTrackImporter(this);
|
trackIds.addAll(new XMLImporter(new KmlFileTrackImporter(this)).importFile(this, file.getUri()));
|
||||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||||
trackImporter = new KmzTrackImporter(this, file.getUri());
|
trackIds.addAll(new KmzTrackImporter().importFile(this, file.getUri()));
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Unsupported file format.");
|
Log.d(TAG, "Unsupported file format.");
|
||||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (InputStream inputStream = getContentResolver().openInputStream(file.getUri())) {
|
|
||||||
ArrayList<Track.Id> trackIds = new ArrayList<>(trackImporter.importFile(inputStream));
|
|
||||||
|
|
||||||
if (!trackIds.isEmpty()) {
|
if (!trackIds.isEmpty()) {
|
||||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName()));
|
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName()));
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
|
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@@ -37,7 +38,6 @@ import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
//TODO Do not use AbstractFileTrackImporter as TrackPoint sensor generation is done differently here.
|
|
||||||
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||||
|
|
||||||
private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
|
private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
|
||||||
@@ -84,6 +84,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
super(context, contentProviderUtils);
|
super(context, contentProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultHandler getHandler() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
|
|||||||
@@ -22,10 +22,9 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -46,35 +45,23 @@ import de.dennisguse.opentracks.util.FileUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class KmzTrackImporter implements TrackImporter {
|
public class KmzTrackImporter {
|
||||||
|
|
||||||
private static final String TAG = KmzTrackImporter.class.getSimpleName();
|
private static final String TAG = KmzTrackImporter.class.getSimpleName();
|
||||||
|
|
||||||
private static final List<String> KMZ_IMAGES_EXT = Arrays.asList("jpeg", "jpg", "png");
|
private static final List<String> KMZ_IMAGES_EXT = Arrays.asList("jpeg", "jpg", "png");
|
||||||
|
|
||||||
private static final int BUFFER_SIZE = 4096;
|
private Context context;
|
||||||
|
|
||||||
private final Context context;
|
@NonNull
|
||||||
private final Uri uriKmzFile;
|
public List<Track.Id> importFile(Context context, Uri fileUri) throws IOException {
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context the context
|
|
||||||
* @param uriFile URI of the kmz file.
|
|
||||||
*/
|
|
||||||
public KmzTrackImporter(Context context, Uri uriFile) {
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.uriKmzFile = uriFile;
|
List<Track.Id> trackIds = findAndParseKmlFile(fileUri);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
List<Track.Id> trackIdsWithImages = new ArrayList<>();
|
||||||
public @NonNull
|
|
||||||
List<Track.Id> importFile(InputStream inputStream) {
|
|
||||||
List<Track.Id> trackIds = findAndParseKmlFile(inputStream);
|
|
||||||
|
|
||||||
ArrayList<Track.Id> trackIdsWithImages = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Track.Id trackId : trackIds) {
|
for (Track.Id trackId : trackIds) {
|
||||||
if (copyKmzImages(trackId)) {
|
if (copyKmzImages(fileUri, trackId)) {
|
||||||
trackIdsWithImages.add(trackId);
|
trackIdsWithImages.add(trackId);
|
||||||
deleteOrphanImages(trackId);
|
deleteOrphanImages(trackId);
|
||||||
} else {
|
} else {
|
||||||
@@ -90,8 +77,8 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
*
|
*
|
||||||
* @return false if there are errors or true otherwise.
|
* @return false if there are errors or true otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean copyKmzImages(Track.Id trackId) {
|
private boolean copyKmzImages(Uri uri, Track.Id trackId) throws IOException {
|
||||||
try (InputStream inputStream = context.getContentResolver().openInputStream(uriKmzFile);
|
try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||||
ZipEntry zipEntry;
|
ZipEntry zipEntry;
|
||||||
|
|
||||||
@@ -110,9 +97,6 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Unable to import file", e);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,13 +138,9 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
return KMZ_IMAGES_EXT.contains(fileExt);
|
return KMZ_IMAGES_EXT.contains(fileExt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private List<Track.Id> findAndParseKmlFile(Uri uri) throws IOException {
|
||||||
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it.
|
try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
*
|
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||||
* @param inputStream kmz input stream.
|
|
||||||
*/
|
|
||||||
private List<Track.Id> findAndParseKmlFile(InputStream inputStream) {
|
|
||||||
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
|
||||||
ZipEntry zipEntry;
|
ZipEntry zipEntry;
|
||||||
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||||
|
|
||||||
@@ -190,9 +170,6 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
throw e;
|
throw e;
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Unable to import file", e);
|
|
||||||
throw new ImportParserException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,32 +204,17 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Track.Id> parseKml(ZipInputStream zipInputStream) {
|
private List<Track.Id> parseKml(ZipInputStream zipInputStream) throws IOException {
|
||||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
|
XMLImporter kmlFileTrackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||||
|
|
||||||
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
|
InputStream nonClosableInputStream = new FilterInputStream(zipInputStream) {
|
||||||
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
@Override
|
||||||
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
public void close() {
|
||||||
throw e;
|
// SAX2 always tries close InputStreams; but that would also close our ZIP file.
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ImportParserException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the kml as byte array.
|
|
||||||
*
|
|
||||||
* @param zipInputStream the zip input stream
|
|
||||||
*/
|
|
||||||
private byte[] getKml(ZipInputStream zipInputStream) throws IOException {
|
|
||||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
|
||||||
int count;
|
|
||||||
while ((count = zipInputStream.read(buffer)) != -1) {
|
|
||||||
byteArrayOutputStream.write(buffer, 0, count);
|
|
||||||
}
|
}
|
||||||
return byteArrayOutputStream.toByteArray();
|
};
|
||||||
}
|
|
||||||
|
return kmlFileTrackImporter.importFile(nonClosableInputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -262,6 +224,7 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* @param trackId the track's id which image belongs to.
|
* @param trackId the track's id which image belongs to.
|
||||||
* @param fileName the file name
|
* @param fileName the file name
|
||||||
*/
|
*/
|
||||||
|
@Deprecated //TODO Use JDK9's inputStream.transferTo() instead of manual buffer
|
||||||
private void readAndSaveImageFile(ZipInputStream zipInputStream, Track.Id trackId, String fileName) throws IOException {
|
private void readAndSaveImageFile(ZipInputStream zipInputStream, Track.Id trackId, String fileName) throws IOException {
|
||||||
if (trackId == null || fileName.equals("")) {
|
if (trackId == null || fileName.equals("")) {
|
||||||
return;
|
return;
|
||||||
@@ -271,7 +234,7 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
File file = new File(dir, fileName);
|
File file = new File(dir, fileName);
|
||||||
|
|
||||||
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
byte[] buffer = new byte[4096];
|
||||||
int count;
|
int count;
|
||||||
while ((count = zipInputStream.read(buffer)) != -1) {
|
while ((count = zipInputStream.read(buffer)) != -1) {
|
||||||
fileOutputStream.write(buffer, 0, count);
|
fileOutputStream.write(buffer, 0, count);
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 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.io.file.importer;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a track importer.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
public interface TrackImporter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import a file.
|
|
||||||
*
|
|
||||||
* @param inputStream the file's input stream
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
List<Track.Id> importFile(InputStream inputStream) throws ImportParserException;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteConstraintException;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses SAX2 to parse XML files.
|
||||||
|
* <p>
|
||||||
|
* NOTE: SAX2 always closes InputStreams after processing.
|
||||||
|
*/
|
||||||
|
public class XMLImporter {
|
||||||
|
|
||||||
|
private static final String TAG = XMLImporter.class.getSimpleName();
|
||||||
|
|
||||||
|
private final TrackParser parser;
|
||||||
|
|
||||||
|
public XMLImporter(TrackParser parser) {
|
||||||
|
this.parser = parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Track.Id> importFile(Context context, Uri uri) throws ImportParserException, ImportAlreadyExistsException, IOException {
|
||||||
|
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
|
||||||
|
return importFile(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Track.Id> importFile(InputStream inputStream) throws ImportParserException, ImportAlreadyExistsException, IOException {
|
||||||
|
try {
|
||||||
|
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, parser.getHandler());
|
||||||
|
return parser.getImportTrackIds();
|
||||||
|
} catch (SAXException | ParserConfigurationException | AbstractFileTrackImporter.ParsingException e) {
|
||||||
|
Log.e(TAG, "Unable to import file", e);
|
||||||
|
if (parser.getImportTrackIds().size() > 0) {
|
||||||
|
parser.cleanImport();
|
||||||
|
}
|
||||||
|
throw new ImportParserException(e);
|
||||||
|
} catch (SQLiteConstraintException e) {
|
||||||
|
Log.e(TAG, "Unable to import file", e);
|
||||||
|
throw new ImportAlreadyExistsException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TrackParser {
|
||||||
|
DefaultHandler getHandler();
|
||||||
|
|
||||||
|
List<Track.Id> getImportTrackIds();
|
||||||
|
|
||||||
|
void cleanImport();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,8 @@ import android.util.Log;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.core.app.TaskStackBuilder;
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -52,7 +54,7 @@ import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
|||||||
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
||||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTask;
|
||||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
@@ -74,6 +76,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
|
|
||||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final RecordingStatus STATUS_DEFAULT = new RecordingStatus(null, false);
|
||||||
|
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
||||||
|
|
||||||
// The following variables are set in onCreate:
|
// The following variables are set in onCreate:
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private PeriodicTaskExecutor voiceExecutor;
|
private PeriodicTaskExecutor voiceExecutor;
|
||||||
@@ -119,16 +124,22 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
|
|
||||||
private HandlerServer handlerServer;
|
private HandlerServer handlerServer;
|
||||||
|
|
||||||
private final TrackRecordingServiceStatus serviceStatus = new TrackRecordingServiceStatus();
|
private RecordingStatus recordingStatus;
|
||||||
|
private MutableLiveData<RecordingStatus> recordingStatusObservable;
|
||||||
|
private MutableLiveData<GpsStatusValue> gpsStatusObservable;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
|
recordingStatusObservable = new MutableLiveData<>();
|
||||||
|
updateRecordingStatus(STATUS_DEFAULT);
|
||||||
|
gpsStatusObservable = new MutableLiveData<>(STATUS_GPS_DEFAULT);
|
||||||
|
|
||||||
handlerServer = new HandlerServer(this);
|
handlerServer = new HandlerServer(this);
|
||||||
|
|
||||||
contentProviderUtils = new ContentProviderUtils(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTask.Factory());
|
||||||
|
|
||||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||||
|
|
||||||
@@ -149,8 +160,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
serviceStatus.onStop();
|
|
||||||
|
|
||||||
handlerServer.stop(this);
|
handlerServer.stop(this);
|
||||||
handlerServer = null;
|
handlerServer = null;
|
||||||
|
|
||||||
@@ -181,19 +190,22 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
// This should be the next to last operation
|
// This should be the next to last operation
|
||||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||||
|
|
||||||
|
recordingStatusObservable = null;
|
||||||
|
gpsStatusObservable = null;
|
||||||
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRecording() {
|
public boolean isRecording() {
|
||||||
return serviceStatus.isRecording();
|
return recordingStatus.isRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPaused() {
|
public boolean isPaused() {
|
||||||
return serviceStatus.getRecordingTrackPaused();
|
return recordingStatus.isPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Track.Id getRecordingTrackId() {
|
public Track.Id getRecordingTrackId() {
|
||||||
return serviceStatus.getRecordingTrackId();
|
return recordingStatus.getTrackId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackStatistics getTrackStatistics() {
|
public TrackStatistics getTrackStatistics() {
|
||||||
@@ -223,14 +235,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(serviceStatus.getRecordingTrackId());
|
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(getRecordingTrackId());
|
||||||
if (nextMarkerNumber == null) {
|
if (nextMarkerNumber == null) {
|
||||||
nextMarkerNumber = 1;
|
nextMarkerNumber = 1;
|
||||||
}
|
}
|
||||||
name = getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
name = getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(serviceStatus.getRecordingTrackId());
|
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(getRecordingTrackId());
|
||||||
if (trackPoint == null) {
|
if (trackPoint == null) {
|
||||||
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
|
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
|
||||||
return null;
|
return null;
|
||||||
@@ -244,7 +256,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
||||||
|
|
||||||
// Insert marker
|
// Insert marker
|
||||||
Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), stats, trackPoint, photoUrl);
|
Marker marker = new Marker(name, description, category, icon, getRecordingTrackId(), stats, trackPoint, photoUrl);
|
||||||
Uri uri = contentProviderUtils.insertMarker(marker);
|
Uri uri = contentProviderUtils.insertMarker(marker);
|
||||||
return new Marker.Id(ContentUris.parseId(uri));
|
return new Marker.Id(ContentUris.parseId(uri));
|
||||||
}
|
}
|
||||||
@@ -266,7 +278,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
|
Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
serviceStatus.onChange(trackId, false);
|
updateRecordingStatus(new RecordingStatus(trackId, false));
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
track.setId(trackId);
|
track.setId(trackId);
|
||||||
@@ -308,7 +320,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
|
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
serviceStatus.onChange(trackId, false);
|
updateRecordingStatus(new RecordingStatus(trackId, false));
|
||||||
|
|
||||||
startRecording();
|
startRecording();
|
||||||
}
|
}
|
||||||
@@ -321,10 +333,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
serviceStatus.onChange(false);
|
updateRecordingStatus(new RecordingStatus(getRecordingTrackId(), false));
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
|
Track track = contentProviderUtils.getTrack(getRecordingTrackId());
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
|
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
|
||||||
}
|
}
|
||||||
@@ -373,10 +385,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
|
|
||||||
// Need to remember the recordingTrackId before setting it to null
|
// Need to remember the recordingTrackId before setting it to null
|
||||||
boolean wasPause = isPaused();
|
boolean wasPause = isPaused();
|
||||||
Track.Id trackId = serviceStatus.getRecordingTrackId();
|
Track.Id trackId = getRecordingTrackId();
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
serviceStatus.onChange(null, true);
|
updateRecordingStatus(STATUS_DEFAULT);
|
||||||
|
|
||||||
if (!wasPause) {
|
if (!wasPause) {
|
||||||
// Update database
|
// Update database
|
||||||
@@ -404,10 +416,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
serviceStatus.onChange(true);
|
updateRecordingStatus(new RecordingStatus(getRecordingTrackId(), true));
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
|
Track track = contentProviderUtils.getTrack(getRecordingTrackId());
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
if (lastTrackPoint != null) {
|
if (lastTrackPoint != null) {
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||||
@@ -490,7 +502,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
|
Track track = contentProviderUtils.getTrack(getRecordingTrackId());
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
Log.w(TAG, "Ignore newTrackPoint. No track.");
|
Log.w(TAG, "Ignore newTrackPoint. No track.");
|
||||||
return;
|
return;
|
||||||
@@ -564,17 +576,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
@Override
|
@Override
|
||||||
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
|
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
|
||||||
notificationManager.updateContent(getString(gpsStatusValue.message));
|
notificationManager.updateContent(getString(gpsStatusValue.message));
|
||||||
serviceStatus.onChange(gpsStatusValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
|
//TODO This check should not be necessary, but prevents a crash; somehow the shutdown is not working correctly as we should not receive a notification then.
|
||||||
serviceStatus.addListener(listener);
|
if (gpsStatusObservable != null) {
|
||||||
}
|
gpsStatusObservable.postValue(gpsStatusValue);
|
||||||
|
}
|
||||||
//TODO Check that this is used everywhere, where addListener is called!
|
|
||||||
//Otherwise, we keep objects in referenced and waste memory until this service instance is terminated.
|
|
||||||
public void removeListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
|
|
||||||
serviceStatus.removeListener(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -666,7 +672,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
if (isRecording()) {
|
if (isRecording()) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, serviceStatus.getRecordingTrackId());
|
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, getRecordingTrackId());
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
.addParentStack(TrackRecordingActivity.class)
|
.addParentStack(TrackRecordingActivity.class)
|
||||||
.addNextIntent(intent)
|
.addNextIntent(intent)
|
||||||
@@ -702,8 +708,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
this.altitudeSumManager = altitudeSumManager;
|
this.altitudeSumManager = altitudeSumManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GpsStatusValue getGpsStatus() {
|
public LiveData<GpsStatusValue> getGpsStatusObservable() {
|
||||||
return handlerServer.getGpsStatus();
|
return gpsStatusObservable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<RecordingStatus> getRecordingStatusObservable() {
|
||||||
|
return recordingStatusObservable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRecordingStatus(RecordingStatus status) {
|
||||||
|
Log.i(TAG, "new status " + recordingStatus + " -> " + status);
|
||||||
|
recordingStatus = status;
|
||||||
|
recordingStatusObservable.postValue(recordingStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -717,6 +733,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class Binder extends android.os.Binder {
|
public class Binder extends android.os.Binder {
|
||||||
|
|
||||||
private Binder() {
|
private Binder() {
|
||||||
@@ -727,4 +744,52 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
return TrackRecordingService.this;
|
return TrackRecordingService.this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class RecordingStatus {
|
||||||
|
private final Track.Id trackId;
|
||||||
|
private final boolean paused;
|
||||||
|
|
||||||
|
public RecordingStatus(Track.Id trackId, boolean paused) {
|
||||||
|
this.trackId = trackId;
|
||||||
|
this.paused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Track.Id getTrackId() {
|
||||||
|
return trackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRecording() {
|
||||||
|
return trackId != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPaused() {
|
||||||
|
return paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRecordingAndNotPaused() {
|
||||||
|
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);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public RecordingStatus stop() {
|
||||||
|
return STATUS_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RecordingStatus{" +
|
||||||
|
"trackId=" + trackId +
|
||||||
|
", paused=" + paused +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.services;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
|
||||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles the status of the recording service.
|
|
||||||
* Also offer an interface through which to know the recording Track.Id, pause status and GPS status.
|
|
||||||
*/
|
|
||||||
public class TrackRecordingServiceStatus {
|
|
||||||
|
|
||||||
private Track.Id recordingTrackId;
|
|
||||||
private boolean recordingTrackPaused;
|
|
||||||
|
|
||||||
private final List<Listener> listeners = new ArrayList<>();
|
|
||||||
|
|
||||||
void addListener(@NonNull Listener listener) {
|
|
||||||
if (this.listeners.contains(listener)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.listeners.add(listener);
|
|
||||||
listener.onTrackRecordingId(recordingTrackId);
|
|
||||||
listener.onTrackRecordingPaused(recordingTrackPaused);
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeListener(@NonNull Listener listener) {
|
|
||||||
this.listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getRecordingTrackPaused() {
|
|
||||||
return this.recordingTrackPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Track.Id getRecordingTrackId() {
|
|
||||||
return this.recordingTrackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onStop() {
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onGpsStatus(GpsStatusValue.GPS_NONE);
|
|
||||||
listener.onTrackRecordingId(null);
|
|
||||||
listener.onTrackRecordingPaused(true);
|
|
||||||
}
|
|
||||||
listeners.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRecording() {
|
|
||||||
return recordingTrackId != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChange(Track.Id trackId, boolean paused) {
|
|
||||||
recordingTrackId = trackId;
|
|
||||||
recordingTrackPaused = paused;
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onTrackRecordingId(recordingTrackId);
|
|
||||||
listener.onTrackRecordingPaused(recordingTrackPaused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChange(boolean paused) {
|
|
||||||
recordingTrackPaused = paused;
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onTrackRecordingPaused(recordingTrackPaused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChange(GpsStatusValue statusValue) {
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onGpsStatus(statusValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
default void onGpsStatus(GpsStatusValue newValue) {}
|
|
||||||
default void onTrackRecordingPaused(boolean paused) {}
|
|
||||||
default void onTrackRecordingId(Track.Id trackId) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
|||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class handle GPS status according to received locations and some thresholds.
|
* This class handle GPS status according to received locations` and some thresholds.
|
||||||
*/
|
*/
|
||||||
//TODO should handle sharedpreference changes
|
//TODO should handle sharedpreference changes
|
||||||
class GpsStatus {
|
class GpsStatus {
|
||||||
@@ -56,6 +56,7 @@ class GpsStatus {
|
|||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
stopped = true;
|
stopped = true;
|
||||||
|
sendStatus(gpsStatus, GpsStatusValue.GPS_NONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +76,15 @@ class GpsStatus {
|
|||||||
gpsStatusHandler = new Handler();
|
gpsStatusHandler = new Handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
client.onGpsStatusChanged(GpsStatusValue.GPS_NONE, GpsStatusValue.GPS_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client that uses GpsStatus has to call this method to stop the Runnable if needed.
|
* The client that uses GpsStatus has to call this method to stop the Runnable if needed.
|
||||||
*/
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
client.onGpsStatusChanged(gpsStatus, GpsStatusValue.GPS_NONE);
|
||||||
client = null;
|
client = null;
|
||||||
if (gpsStatusRunner != null) {
|
if (gpsStatusRunner != null) {
|
||||||
gpsStatusRunner.stop();
|
gpsStatusRunner.stop();
|
||||||
|
|||||||
@@ -20,4 +20,8 @@ public enum GpsStatusValue {
|
|||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGpsStarted() {
|
||||||
|
return this != GpsStatusValue.GPS_NONE && this != GpsStatusValue.GPS_DISABLED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,6 @@ public class HandlerServer {
|
|||||||
service.newGpsStatus(gpsStatusValue);
|
service.newGpsStatus(gpsStatusValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GpsStatusValue getGpsStatus() {
|
|
||||||
return locationHandler.getGpsStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface HandlerServerInterface {
|
public interface HandlerServerInterface {
|
||||||
void newTrackPoint(TrackPoint trackPoint, int gpsAccuracy);
|
void newTrackPoint(TrackPoint trackPoint, int gpsAccuracy);
|
||||||
void newGpsStatus(GpsStatusValue gpsStatusValue);
|
void newGpsStatus(GpsStatusValue gpsStatusValue);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
|
|||||||
gpsStatus = new GpsStatus(context, this);
|
gpsStatus = new GpsStatus(context, this);
|
||||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
registerLocationListener();
|
registerLocationListener();
|
||||||
|
gpsStatus.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+17
-1
@@ -23,6 +23,8 @@ import android.speech.tts.TextToSpeech;
|
|||||||
import android.speech.tts.UtteranceProgressListener;
|
import android.speech.tts.UtteranceProgressListener;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -135,7 +137,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TrackRecordingService trackRecordingService) {
|
public void run(@NonNull TrackRecordingService trackRecordingService) {
|
||||||
if (trackRecordingService == null) {
|
if (trackRecordingService == null) {
|
||||||
Log.e(TAG, "TrackRecordingService is null.");
|
Log.e(TAG, "TrackRecordingService is null.");
|
||||||
return;
|
return;
|
||||||
@@ -231,4 +233,18 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
|
// 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");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-31
@@ -1,31 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link PeriodicTaskFactory} for text-to-speech announcement periodic task.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class AnnouncementPeriodicTaskFactory implements PeriodicTaskFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PeriodicTask create(Context context) {
|
|
||||||
return new AnnouncementPeriodicTask(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.services.tasks;
|
package de.dennisguse.opentracks.services.tasks;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +37,7 @@ public interface PeriodicTask {
|
|||||||
*
|
*
|
||||||
* @param trackRecordingService the track recording service
|
* @param trackRecordingService the track recording service
|
||||||
*/
|
*/
|
||||||
void run(TrackRecordingService trackRecordingService);
|
void run(@NonNull TrackRecordingService trackRecordingService);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuts down this task and clean up resources.
|
* Shuts down this task and clean up resources.
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ package de.dennisguse.opentracks.services.tasks;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -52,14 +55,13 @@ public class PeriodicTaskExecutor {
|
|||||||
|
|
||||||
private boolean metricUnits;
|
private boolean metricUnits;
|
||||||
|
|
||||||
// The next distance for the distance periodic task
|
private Distance nextTaskDistance = Distance.of(Double.MAX_VALUE);
|
||||||
private double nextTaskDistance = Double.MAX_VALUE;
|
|
||||||
|
|
||||||
public PeriodicTaskExecutor(TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
|
public PeriodicTaskExecutor(@NonNull TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
|
||||||
this.trackRecordingService = trackRecordingService;
|
this.trackRecordingService = trackRecordingService;
|
||||||
this.periodicTaskFactory = periodicTaskFactory;
|
this.periodicTaskFactory = periodicTaskFactory;
|
||||||
|
|
||||||
TASK_FREQUENCY_OFF = Integer.parseInt(trackRecordingService.getBaseContext().getResources().getString(R.string.frequency_off));
|
TASK_FREQUENCY_OFF = Integer.parseInt(trackRecordingService.getString(R.string.frequency_off));
|
||||||
taskFrequency = TASK_FREQUENCY_OFF;
|
taskFrequency = TASK_FREQUENCY_OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ public class PeriodicTaskExecutor {
|
|||||||
timerTaskExecutor.scheduleTask(Duration.ofMinutes(taskFrequency));
|
timerTaskExecutor.scheduleTask(Duration.ofMinutes(taskFrequency));
|
||||||
} else {
|
} else {
|
||||||
// For distance periodic task
|
// For distance periodic task
|
||||||
calculateNextTaskDistance();
|
updateNextTaskDistance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,59 +130,48 @@ public class PeriodicTaskExecutor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Distance distance = trackStatistics.getTotalDistance();
|
if (trackStatistics.getTotalDistance().greaterThan(nextTaskDistance)) {
|
||||||
|
|
||||||
if (distance.greaterThan(Distance.of(nextTaskDistance))) {
|
|
||||||
periodicTask.run(trackRecordingService);
|
periodicTask.run(trackRecordingService);
|
||||||
calculateNextTaskDistance();
|
updateNextTaskDistance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets task frequency.
|
|
||||||
*
|
|
||||||
* @param taskFrequency the task frequency
|
|
||||||
*/
|
|
||||||
public void setTaskFrequency(int taskFrequency) {
|
public void setTaskFrequency(int taskFrequency) {
|
||||||
this.taskFrequency = taskFrequency;
|
this.taskFrequency = taskFrequency;
|
||||||
restore();
|
restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets metricUnits.
|
|
||||||
*
|
|
||||||
* @param metricUnits true to use metric units
|
|
||||||
*/
|
|
||||||
public void setMetricUnits(boolean metricUnits) {
|
public void setMetricUnits(boolean metricUnits) {
|
||||||
this.metricUnits = metricUnits;
|
this.metricUnits = metricUnits;
|
||||||
calculateNextTaskDistance();
|
updateNextTaskDistance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void updateNextTaskDistance() {
|
||||||
* Calculates the next distance for the distance periodic task.
|
|
||||||
*/
|
|
||||||
private void calculateNextTaskDistance() {
|
|
||||||
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused() || periodicTask == null) {
|
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused() || periodicTask == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDistanceFrequency()) {
|
||||||
|
nextTaskDistance = Distance.of(Double.MAX_VALUE);
|
||||||
|
Log.d(TAG, "SplitManager: Distance splits disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TrackStatistics trackStatistics = trackRecordingService.getTrackStatistics();
|
TrackStatistics trackStatistics = trackRecordingService.getTrackStatistics();
|
||||||
if (trackStatistics == null) {
|
if (trackStatistics == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDistanceFrequency()) {
|
nextTaskDistance = calculateNextTaskDistance(trackStatistics);
|
||||||
nextTaskDistance = Double.MAX_VALUE;
|
}
|
||||||
Log.d(TAG, "SplitManager: Distance splits disabled.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
double distance = trackStatistics.getTotalDistance().to(metricUnits);
|
@VisibleForTesting
|
||||||
|
public Distance calculateNextTaskDistance(TrackStatistics trackStatistics) {
|
||||||
|
Distance distance = trackStatistics.getTotalDistance();
|
||||||
|
|
||||||
// The index will be negative since the frequency is negative.
|
Distance announcementInterval = Distance.one(metricUnits).multipliedBy(Math.abs(taskFrequency));
|
||||||
int index = (int) (distance / taskFrequency);
|
int index = (int) (distance.dividedBy(announcementInterval));
|
||||||
index -= 1;
|
return announcementInterval.multipliedBy(index + 1);
|
||||||
nextTaskDistance = taskFrequency * index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package de.dennisguse.opentracks.services.tasks;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for classes that can create {@link PeriodicTask}.
|
* An interface for classes that can create {@link PeriodicTask}.
|
||||||
*
|
*
|
||||||
@@ -30,5 +32,6 @@ interface PeriodicTaskFactory {
|
|||||||
*
|
*
|
||||||
* @return the task, or null if the task is not supported
|
* @return the task, or null if the task is not supported
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
PeriodicTask create(Context context);
|
PeriodicTask create(Context context);
|
||||||
}
|
}
|
||||||
@@ -20,12 +20,10 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.AbstractActivity;
|
import de.dennisguse.opentracks.AbstractActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
|
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
|
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
|
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
|
||||||
@@ -93,7 +91,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit();
|
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PrefsFragment extends PreferenceFragmentCompat implements TrackRecordingServiceStatus.Listener {
|
public static class PrefsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
@@ -107,15 +105,20 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
};
|
};
|
||||||
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private boolean isRecording;
|
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
|
|
||||||
private final Runnable bindServiceCallback = new Runnable() {
|
private final Runnable bindServiceCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
if (service != null) {
|
if (service == null) {
|
||||||
service.addListener(PrefsFragment.this);
|
Log.w(TAG, "could not get TrackRecordingService");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service.getRecordingStatusObservable()
|
||||||
|
.observe(PrefsFragment.this, status -> onRecordingStatusChanged(status));
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -218,6 +221,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
Preference importPreference = findPreference(getString(R.string.settings_import));
|
Preference importPreference = findPreference(getString(R.string.settings_import));
|
||||||
Preference exportPreference = findPreference(getString(R.string.settings_export));
|
Preference exportPreference = findPreference(getString(R.string.settings_export));
|
||||||
|
|
||||||
|
boolean isRecording = recordingStatus.isRecording();
|
||||||
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
||||||
importPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
importPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
||||||
exportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
exportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
||||||
@@ -292,10 +296,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
public void onTrackRecordingId(Track.Id trackId) {
|
this.recordingStatus = status;
|
||||||
if (trackId != null && !isRecording && isAdded()) {
|
if (!status.isRecording() && isAdded()) {
|
||||||
isRecording = true;
|
|
||||||
updatePrefsDependOnRecording();
|
updatePrefsDependOnRecording();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ public class TrackStatisticsUpdater {
|
|||||||
|
|
||||||
//Update absolute (GPS-based) altitude
|
//Update absolute (GPS-based) altitude
|
||||||
if (trackPoint.hasAltitude()) {
|
if (trackPoint.hasAltitude()) {
|
||||||
updateAbsoluteAltitude(trackPoint.getAltitude());
|
updateAbsoluteAltitude(trackPoint.getAltitude().toM());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class AnnouncementUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||||
double distanceInUnit = distance.to(isMetricUnits);
|
double distanceInUnit = distance.toKM_Miles(isMetricUnits);
|
||||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distanceInUnit), distanceInUnit);
|
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distanceInUnit), distanceInUnit);
|
||||||
|
|
||||||
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
|
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts WGS84 altitude to EGM2008 (should be close to height above sea level).
|
||||||
|
* <p>
|
||||||
|
* Uses <a href="https://geographiclib.sourceforge.io/">GeographicLib</a>] EGM2008 5minute undulation data.
|
||||||
|
* https://geographiclib.sourceforge.io/html/geoid.html
|
||||||
|
* <p>
|
||||||
|
* File starts at 90N, 0E (North pole) and is encoded in parallel bands as unsigned shorts.
|
||||||
|
*/
|
||||||
|
public class EGM2008Utils {
|
||||||
|
|
||||||
|
static final int EGM2008_5_DATA = R.raw.egm2008_5;
|
||||||
|
|
||||||
|
private static final int HEADER_LENGTH = 404;
|
||||||
|
|
||||||
|
private static final int RESOLUTION_IN_MINUTES = 60 / 5;
|
||||||
|
|
||||||
|
private static final int LATITUDE_CORRECTION = 360 * RESOLUTION_IN_MINUTES;
|
||||||
|
|
||||||
|
private EGM2008Utils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EGM2008Correction createCorrection(Context context, Location location) throws IOException {
|
||||||
|
Indices indices = getIndices(location);
|
||||||
|
|
||||||
|
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008_5_DATA))) {
|
||||||
|
return new EGM2008Correction(indices, dataInputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static int getUndulationRaw(DataInputStream dataInputStream, Indices indices) throws IOException {
|
||||||
|
dataInputStream.reset();
|
||||||
|
int absoluteIndex = indices.getAbsoluteIndex();
|
||||||
|
return getUndulationRaw(dataInputStream, absoluteIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getUndulationRaw(DataInputStream dataInputStream, int undulationIndex) throws IOException {
|
||||||
|
dataInputStream.reset();
|
||||||
|
int index = HEADER_LENGTH + undulationIndex * 2; //byte size is 2
|
||||||
|
long ignored = dataInputStream.skip(index);
|
||||||
|
|
||||||
|
return dataInputStream.readUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static Indices getIndices(Location location) {
|
||||||
|
double latitude = -location.getLatitude() + 90;
|
||||||
|
int latitudeIndex = (int) (latitude * RESOLUTION_IN_MINUTES);
|
||||||
|
|
||||||
|
double longitude;
|
||||||
|
if (location.getLongitude() >= 0) {
|
||||||
|
longitude = location.getLongitude();
|
||||||
|
} else {
|
||||||
|
longitude = 180 + Math.abs(location.getLongitude());
|
||||||
|
}
|
||||||
|
int longitudeIndex = (int) (longitude * RESOLUTION_IN_MINUTES);
|
||||||
|
|
||||||
|
if (longitudeIndex >= 360 * RESOLUTION_IN_MINUTES) {
|
||||||
|
longitudeIndex = 0;
|
||||||
|
}
|
||||||
|
return new Indices(latitudeIndex, longitudeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EGM2008Correction {
|
||||||
|
|
||||||
|
protected final Indices indices;
|
||||||
|
|
||||||
|
protected final int v00;
|
||||||
|
protected final int v10;
|
||||||
|
protected final int v01;
|
||||||
|
protected final int v11;
|
||||||
|
|
||||||
|
private EGM2008Correction(Indices indices, DataInputStream dataInputStream) throws IOException {
|
||||||
|
this.indices = indices;
|
||||||
|
|
||||||
|
v00 = getUndulationRaw(dataInputStream, indices);
|
||||||
|
if (!isSouthPole()) {
|
||||||
|
v10 = getUndulationRaw(dataInputStream, indices.offset(0, 1));
|
||||||
|
v01 = getUndulationRaw(dataInputStream, indices.offset(1, 0));
|
||||||
|
v11 = getUndulationRaw(dataInputStream, indices.offset(1, 1));
|
||||||
|
} else {
|
||||||
|
v10 = 0;
|
||||||
|
v01 = 0;
|
||||||
|
v11 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canCorrect(@NonNull Location location) {
|
||||||
|
return indices.getAbsoluteIndex() == getIndices(location).getAbsoluteIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double correctAltitude(@NonNull Location location) {
|
||||||
|
if (!canCorrect(location))
|
||||||
|
throw new RuntimeException("Undulation data not loaded for this location.");
|
||||||
|
if (!location.hasAltitude())
|
||||||
|
throw new RuntimeException("Location has no altitude");
|
||||||
|
|
||||||
|
double undulationRaw;
|
||||||
|
if (isSouthPole()) {
|
||||||
|
// No bilinear interpolation on South Pole (not worth the time)
|
||||||
|
undulationRaw = v00;
|
||||||
|
} else {
|
||||||
|
double fLongitude = location.getLongitude() * RESOLUTION_IN_MINUTES -
|
||||||
|
(int) (location.getLongitude() * RESOLUTION_IN_MINUTES);
|
||||||
|
|
||||||
|
double fLatitude = (-location.getLatitude() + 90) * RESOLUTION_IN_MINUTES
|
||||||
|
- (int) ((-location.getLatitude() + 90) * RESOLUTION_IN_MINUTES);
|
||||||
|
|
||||||
|
// Bilinear interpolation (optimized; taken from GeopgrahicLib/Geoid.cpp)
|
||||||
|
double
|
||||||
|
a = (1 - fLongitude) * v00 + fLongitude * v01,
|
||||||
|
b = (1 - fLongitude) * v10 + fLongitude * v11;
|
||||||
|
undulationRaw = (1 - fLatitude) * a + fLatitude * b;
|
||||||
|
}
|
||||||
|
// Bilinear interpolation (not optimized)
|
||||||
|
//undulationRaw = v00 * (1 - fLongitude) * (1 - fLatitude)
|
||||||
|
// + v10 * fLongitude * (1 - fLatitude)
|
||||||
|
// + v01 * (1 - fLongitude) * fLatitude
|
||||||
|
// + v11 * fLongitude * fLatitude;
|
||||||
|
|
||||||
|
double h = 0.003 * undulationRaw - 108;
|
||||||
|
|
||||||
|
return location.getAltitude() - h;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSouthPole() {
|
||||||
|
return indices.latitudeIndex == 2160;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static class Indices {
|
||||||
|
final int latitudeIndex;
|
||||||
|
final int longitudeIndex;
|
||||||
|
|
||||||
|
Indices(int latitudeIndex, int longitudeIndex) {
|
||||||
|
this.latitudeIndex = latitudeIndex;
|
||||||
|
this.longitudeIndex = longitudeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Indices offset(int latitudeOffset, int longitudeOffset) {
|
||||||
|
return new Indices(latitudeIndex + latitudeOffset, longitudeIndex + longitudeOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getAbsoluteIndex() {
|
||||||
|
return latitudeIndex * LATITUDE_CORRECTION + longitudeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Indices indices = (Indices) o;
|
||||||
|
return latitudeIndex == indices.latitudeIndex &&
|
||||||
|
longitudeIndex == indices.longitudeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(latitudeIndex, longitudeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,31 +16,28 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="20dp">
|
android:padding="20dp">
|
||||||
|
|
||||||
<ImageView
|
<LinearLayout
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="239dp"
|
|
||||||
android:contentDescription="@string/app_name"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/ic_logo_color_24dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextLarge"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:orientation="horizontal"
|
||||||
android:text="@string/app_name"
|
android:layout_margin="8dp">
|
||||||
android:textSize="24sp" />
|
|
||||||
|
|
||||||
<View style="@style/StatsHorizontalLine" />
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_logo_color_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/about_text_description"
|
android:id="@+id/about_text_description"
|
||||||
style="@style/TextMedium"
|
style="@style/TextMedium"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="12dp" />
|
android:padding="12dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<View style="@style/StatsHorizontalLine" />
|
<View style="@style/StatsHorizontalLine" />
|
||||||
|
|
||||||
@@ -74,12 +71,28 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="6dp"
|
|
||||||
android:linksClickable="true"
|
android:linksClickable="true"
|
||||||
|
android:padding="6dp"
|
||||||
android:text="@string/about_history" />
|
android:text="@string/about_history" />
|
||||||
|
|
||||||
<View style="@style/StatsHorizontalLine" />
|
<View style="@style/StatsHorizontalLine" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:text="@string/about_third_party_data" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:text="@string/third_party_data" />
|
||||||
|
|
||||||
|
<View style="@style/StatsHorizontalLine" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -281,6 +281,17 @@
|
|||||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_horizontal_line" />
|
app:layout_constraintTop_toBottomOf="@id/stats_altitude_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_current_label_egm"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:text="@string/egm2008"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_altitude_current_value" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/stats_altitude_current_value"
|
android:id="@+id/stats_altitude_current_value"
|
||||||
style="@style/StatsSmallValue"
|
style="@style/StatsSmallValue"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -613,4 +613,5 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
|
|||||||
<string name="track_pace_notification">Fart: %1$s</string>
|
<string name="track_pace_notification">Fart: %1$s</string>
|
||||||
<string name="track_distance_notification">Afstand: %1$s</string>
|
<string name="track_distance_notification">Afstand: %1$s</string>
|
||||||
<string name="track_speed_notification">Hastighed: %1$s</string>
|
<string name="track_speed_notification">Hastighed: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Data fra tredjeparter</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -610,4 +610,5 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
|
|||||||
<string name="track_distance_notification">Kaugus: %1$s</string>
|
<string name="track_distance_notification">Kaugus: %1$s</string>
|
||||||
<string name="track_speed_notification">Kiirus: %1$s</string>
|
<string name="track_speed_notification">Kiirus: %1$s</string>
|
||||||
<string name="track_pace_notification">Tempo: %1$s</string>
|
<string name="track_pace_notification">Tempo: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Kolmanda osapoole andmed</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -611,4 +611,5 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
|
|||||||
<string name="track_distance_notification">Distantzia: %1$s</string>
|
<string name="track_distance_notification">Distantzia: %1$s</string>
|
||||||
<string name="track_speed_notification">Abiadura: %1$s</string>
|
<string name="track_speed_notification">Abiadura: %1$s</string>
|
||||||
<string name="track_pace_notification">Erritmoa: %1$s</string>
|
<string name="track_pace_notification">Erritmoa: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Hirugarrenen datuak</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -55,13 +55,14 @@ limitations under the License.
|
|||||||
\n
|
\n
|
||||||
\nJos kaikki ei auta, ilmoita virheestä. </string>
|
\nJos kaikki ei auta, ilmoita virheestä. </string>
|
||||||
<string name="help_support_title">Miten voin tukea OpenTracksin kehitystä?</string>
|
<string name="help_support_title">Miten voin tukea OpenTracksin kehitystä?</string>
|
||||||
<string name="help_support_content"> Voit:
|
<string name="help_support_content"> Sinä pystyt:
|
||||||
\n a) levittää sanaa OpenTracksista,
|
\n \u0020a) levittää sanaa OpenTracksista,
|
||||||
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ehdottaa ominaisuuksia</a>,
|
\n \u0020b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> ehdottaa ominaisuuksia </a>,
|
||||||
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">ilmoittaa ohjelmistovirheistä</a>,
|
\n \u0020c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> ilmoita virheistä </a>,
|
||||||
\n d) auttaa ohjelmiston kehittämisessä,
|
\n \u0020d) auttaa ohjelmistokehityksessä,
|
||||||
\n e) ostaa sovelluksen <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play -kaupasta</a>,
|
\n \u0020e) osta <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play -kaupasta </a>,
|
||||||
\n f) lahjoittaa ja g) tehdä mikä mielestäsi edistäisi OpenTracksia. </string>
|
\n \u0020f) lahjoittaa ja
|
||||||
|
\n \u0020g) tee mitä tahansa mielestäsi auttaa OpenTracksia. </string>
|
||||||
<string name="open_track_using_dashboard">Avaa OpenTracks-Dashboardilla</string>
|
<string name="open_track_using_dashboard">Avaa OpenTracks-Dashboardilla</string>
|
||||||
<string name="open_track_as_KMZ">Avaa KMZ-muodossa</string>
|
<string name="open_track_as_KMZ">Avaa KMZ-muodossa</string>
|
||||||
<string name="open_track_as_GPX">Avaa GPX-muodossa</string>
|
<string name="open_track_as_GPX">Avaa GPX-muodossa</string>
|
||||||
@@ -614,4 +615,5 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
|
|||||||
<string name="track_distance_notification">Etäisyys: %1$s</string>
|
<string name="track_distance_notification">Etäisyys: %1$s</string>
|
||||||
<string name="track_pace_notification">Vauhti: %1$s</string>
|
<string name="track_pace_notification">Vauhti: %1$s</string>
|
||||||
<string name="track_speed_notification">Nopeus: %1$s</string>
|
<string name="track_speed_notification">Nopeus: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Kolmannen osapuolen tiedot</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -154,7 +154,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="description_sensor_heart_rate">Frequenza cardiaca (bpm)</string>
|
<string name="description_sensor_heart_rate">Frequenza cardiaca (bpm)</string>
|
||||||
<string name="description_sensor_power">Potenza (W)</string>
|
<string name="description_sensor_power">Potenza (W)</string>
|
||||||
<string name="description_speed_imperial">Velocità (mi/hr)</string>
|
<string name="description_speed_imperial">Velocità (mi/hr)</string>
|
||||||
<string name="description_speed_metric">Velocità (km/hr)</string>
|
<string name="description_speed_metric">Velocità (km/h)</string>
|
||||||
<string name="description_time">Durata</string>
|
<string name="description_time">Durata</string>
|
||||||
<string name="description_total_distance">Distanza totale: %1$.2f km (%2$.1f mi)</string>
|
<string name="description_total_distance">Distanza totale: %1$.2f km (%2$.1f mi)</string>
|
||||||
<string name="description_total_time">Tempo totale: %1$s</string>
|
<string name="description_total_time">Tempo totale: %1$s</string>
|
||||||
@@ -334,7 +334,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="stats_average_speed">Velocità media</string>
|
<string name="stats_average_speed">Velocità media</string>
|
||||||
<string name="stats_altitude">Altitudine</string>
|
<string name="stats_altitude">Altitudine</string>
|
||||||
<string name="stats_fastest_pace">Ritmo più veloce</string>
|
<string name="stats_fastest_pace">Ritmo più veloce</string>
|
||||||
<string name="stats_gain">Guadagno</string>
|
<string name="stats_gain">Salita</string>
|
||||||
<string name="stats_grade">Pendenza</string>
|
<string name="stats_grade">Pendenza</string>
|
||||||
<string name="stats_latitude">Latitudine</string>
|
<string name="stats_latitude">Latitudine</string>
|
||||||
<string name="stats_longitude">Longitude</string>
|
<string name="stats_longitude">Longitude</string>
|
||||||
@@ -536,8 +536,8 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
\ng) Fare tutto ciò che si pensa possa aiutare OpenTracks. </string>
|
\ng) Fare tutto ciò che si pensa possa aiutare OpenTracks. </string>
|
||||||
<string name="activity_type_escooter">Scooter elettrico</string>
|
<string name="activity_type_escooter">Scooter elettrico</string>
|
||||||
<string name="app_not_installed_show_on_map">Si prega di installare un\'applicazione che possa mostrare i dati delle mappe.</string>
|
<string name="app_not_installed_show_on_map">Si prega di installare un\'applicazione che possa mostrare i dati delle mappe.</string>
|
||||||
<string name="export_with_photos">Con le foto</string>
|
<string name="export_with_photos">con foto</string>
|
||||||
<string name="export_without_photos">Senza foto</string>
|
<string name="export_without_photos">senza foto</string>
|
||||||
<string name="export_track_already_exists_msg">Il percorso %1$s esiste già nella cartella di salvataggio.</string>
|
<string name="export_track_already_exists_msg">Il percorso %1$s esiste già nella cartella di salvataggio.</string>
|
||||||
<plurals name="generic_completed_with_errors">
|
<plurals name="generic_completed_with_errors">
|
||||||
<item quantity="one">Completato con un errore</item>
|
<item quantity="one">Completato con un errore</item>
|
||||||
@@ -573,9 +573,9 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="bluetooth_disabled">Per favore, attiva il Bluetooth.</string>
|
<string name="bluetooth_disabled">Per favore, attiva il Bluetooth.</string>
|
||||||
<string name="settings_recording_show_on_lockscreen_while_recording_title">Mostra le statistiche sulla schermata di blocco</string>
|
<string name="settings_recording_show_on_lockscreen_while_recording_title">Mostra le statistiche sulla schermata di blocco</string>
|
||||||
<string name="settings_recording_keep_screen_on_while_recording_title">Mantieni lo schermo acceso</string>
|
<string name="settings_recording_keep_screen_on_while_recording_title">Mantieni lo schermo acceso</string>
|
||||||
<string name="settings_recording_keep_screen_on_while_recording_summary">Durante la registrazione, tenere acceso lo schermo.</string>
|
<string name="settings_recording_keep_screen_on_while_recording_summary">Durante la registrazione, tiene acceso lo schermo.</string>
|
||||||
<string name="settings_recording_fullscreen_on_while_recording_title">Schermo intero</string>
|
<string name="settings_recording_fullscreen_on_while_recording_title">Schermo intero</string>
|
||||||
<string name="settings_recording_fullscreen_on_while_recording_summary">Durante la registrazione, espandi a schermo intero.</string>
|
<string name="settings_recording_fullscreen_on_while_recording_summary">Durante la registrazione, espande a schermo intero.</string>
|
||||||
<string name="settings_night_mode_option_system">Sistema</string>
|
<string name="settings_night_mode_option_system">Sistema</string>
|
||||||
<string name="settings_night_mode_option_day">Chiaro</string>
|
<string name="settings_night_mode_option_day">Chiaro</string>
|
||||||
<string name="settings_night_mode_option_night">Scuro</string>
|
<string name="settings_night_mode_option_night">Scuro</string>
|
||||||
@@ -607,7 +607,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="sensor_state_heart_rate_max">Frequenza cardiaca max</string>
|
<string name="sensor_state_heart_rate_max">Frequenza cardiaca max</string>
|
||||||
<string name="value_pace_kilometer">%1$d:%2$02d min/cm</string>
|
<string name="value_pace_kilometer">%1$d:%2$02d min/cm</string>
|
||||||
<string name="value_float_kilometer_hour">%1$.2f cp/s</string>
|
<string name="value_float_kilometer_hour">%1$.2f cp/s</string>
|
||||||
<string name="stats_loss">Perdita</string>
|
<string name="stats_loss">Discesa</string>
|
||||||
<string name="settings_sensor_wheel_circumference">Circonferenza della ruota (mm)</string>
|
<string name="settings_sensor_wheel_circumference">Circonferenza della ruota (mm)</string>
|
||||||
<string name="description_altitude_loss">Perdita di quota: %1$d m (%2$d ft)</string>
|
<string name="description_altitude_loss">Perdita di quota: %1$d m (%2$d ft)</string>
|
||||||
<string name="sensor_state_heart_rate_avg">Tasso cardiaco medio</string>
|
<string name="sensor_state_heart_rate_avg">Tasso cardiaco medio</string>
|
||||||
|
|||||||
@@ -157,9 +157,9 @@ limitations under the License.
|
|||||||
<string name="gps_disabled">GPS-модуль не включен. Нажмите здесь, чтобы открыть \"%1$s\". Нажмите \"Назад\", чтобы вернуться.</string>
|
<string name="gps_disabled">GPS-модуль не включен. Нажмите здесь, чтобы открыть \"%1$s\". Нажмите \"Назад\", чтобы вернуться.</string>
|
||||||
<string name="gps_disabled_my_location">Не удалось определить местоположение. Добавьте другие источники передачи данных в настройках (%1$s) и убедитесь, что приему сигнала ничего не препятствует. Затем повторите попытку.</string>
|
<string name="gps_disabled_my_location">Не удалось определить местоположение. Добавьте другие источники передачи данных в настройках (%1$s) и убедитесь, что приему сигнала ничего не препятствует. Затем повторите попытку.</string>
|
||||||
<string name="gps_not_found">GPS-модуль не найден</string>
|
<string name="gps_not_found">GPS-модуль не найден</string>
|
||||||
<string name="gps_starting">Включение GPS-модуля…</string>
|
<string name="gps_starting">Включение GPS-модуля</string>
|
||||||
<string name="gps_wait_for_better_signal">Поиск спутников GPS…</string>
|
<string name="gps_wait_for_better_signal">Поиск спутников GPS</string>
|
||||||
<string name="gps_wait_for_signal">Ожидание сигнала GPS…</string>
|
<string name="gps_wait_for_signal">Ожидание сигнала GPS</string>
|
||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
<string name="image_application">Приложение</string>
|
<string name="image_application">Приложение</string>
|
||||||
<string name="image_arrow">Стрелка</string>
|
<string name="image_arrow">Стрелка</string>
|
||||||
@@ -172,7 +172,7 @@ limitations under the License.
|
|||||||
<string name="image_stop">Стоп</string>
|
<string name="image_stop">Стоп</string>
|
||||||
<string name="image_track">Трек</string>
|
<string name="image_track">Трек</string>
|
||||||
<!-- Import -->
|
<!-- Import -->
|
||||||
<string name="import_error">Импортировано: %1$d из %2$s (из %3$s)</string>
|
<string name="import_error">Импортировано: %1$d из %2$s (от %3$s)</string>
|
||||||
<string name="import_no_directory">%1$s не существует</string>
|
<string name="import_no_directory">%1$s не существует</string>
|
||||||
<string name="import_no_file">Нет файлов для импорта. Подсоедините устройство к компьютеру и укажите путь к файлам, которые нужно импортировать в %1$s.</string>
|
<string name="import_no_file">Нет файлов для импорта. Подсоедините устройство к компьютеру и укажите путь к файлам, которые нужно импортировать в %1$s.</string>
|
||||||
<string name="import_no_file_title">Импорт файлов</string>
|
<string name="import_no_file_title">Импорт файлов</string>
|
||||||
@@ -278,7 +278,9 @@ limitations under the License.
|
|||||||
<string name="settings_stats_units_title">Система измерений</string>
|
<string name="settings_stats_units_title">Система измерений</string>
|
||||||
<!-- Share Track -->
|
<!-- Share Track -->
|
||||||
<string name="share_track_share_file">Опубликовать трек</string>
|
<string name="share_track_share_file">Опубликовать трек</string>
|
||||||
<string name="share_track_share_file_body">Я думаю этот маршрут покажется вам интересным.\n\nИспользуйте Google Планета Земля или другое картографическое ПО для просмотра трека.\n\n%1$s</string>
|
<string name="share_track_share_file_body">Я думаю этот маршрут покажется вам интересным.
|
||||||
|
\n
|
||||||
|
\n%1$s</string>
|
||||||
<string name="share_track_subject">Хочу поделиться треком</string>
|
<string name="share_track_subject">Хочу поделиться треком</string>
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<string name="stats_average_moving_pace">Средняя длина шага</string>
|
<string name="stats_average_moving_pace">Средняя длина шага</string>
|
||||||
@@ -341,7 +343,7 @@ limitations under the License.
|
|||||||
<string name="value_float_feet">%1$.2f фт</string>
|
<string name="value_float_feet">%1$.2f фт</string>
|
||||||
<string name="value_float_kilometer">%1$.2f км</string>
|
<string name="value_float_kilometer">%1$.2f км</string>
|
||||||
<string name="value_float_meter">%1$.2f м</string>
|
<string name="value_float_meter">%1$.2f м</string>
|
||||||
<string name="value_float_mile">%1$.2f мил.</string>
|
<string name="value_float_mile">%1$.2f мил</string>
|
||||||
<string name="value_float_mile_poor_gps">%1$.1f мил. (слабый сигнал)</string>
|
<string name="value_float_mile_poor_gps">%1$.1f мил. (слабый сигнал)</string>
|
||||||
<string name="value_integer_feet">%1$d фт</string>
|
<string name="value_integer_feet">%1$d фт</string>
|
||||||
<string name="value_integer_feet_excellent_gps">%1$d фт (отличный сигнал)</string>
|
<string name="value_integer_feet_excellent_gps">%1$d фт (отличный сигнал)</string>
|
||||||
@@ -352,11 +354,11 @@ limitations under the License.
|
|||||||
<string name="value_integer_meter_excellent_gps">%1$d м (отличный сигнал)</string>
|
<string name="value_integer_meter_excellent_gps">%1$d м (отличный сигнал)</string>
|
||||||
<string name="value_integer_meter_poor_gps">%1$d м (слабый сигнал)</string>
|
<string name="value_integer_meter_poor_gps">%1$d м (слабый сигнал)</string>
|
||||||
<string name="value_integer_meter_recommended">%1$d м (рекомендуется)</string>
|
<string name="value_integer_meter_recommended">%1$d м (рекомендуется)</string>
|
||||||
<string name="value_integer_mile">%1$d мил.</string>
|
<string name="value_integer_mile">%1$d мил</string>
|
||||||
<string name="value_integer_mile_hour">%1$d миль/ч</string>
|
<string name="value_integer_mile_hour">%1$d миль/ч</string>
|
||||||
<string name="value_integer_minute">%1$d мин.</string>
|
<string name="value_integer_minute">%1$d мин</string>
|
||||||
<string name="value_integer_percent">%1$d %%</string>
|
<string name="value_integer_percent">%1$d %%</string>
|
||||||
<string name="value_integer_second">%1$d сек.</string>
|
<string name="value_integer_second">%1$d сек</string>
|
||||||
<string name="value_none">Нет</string>
|
<string name="value_none">Нет</string>
|
||||||
<string name="value_off">Выкл.</string>
|
<string name="value_off">Выкл.</string>
|
||||||
<string name="value_smallest">Мин. время</string>
|
<string name="value_smallest">Мин. время</string>
|
||||||
@@ -394,7 +396,7 @@ limitations under the License.
|
|||||||
<string name="voice_speed_miles_per_hour_other">%1$.1f мили в час</string>
|
<string name="voice_speed_miles_per_hour_other">%1$.1f мили в час</string>
|
||||||
<string name="voice_speed_miles_per_hour_two">2 мили в час</string>
|
<string name="voice_speed_miles_per_hour_two">2 мили в час</string>
|
||||||
<string name="voice_speed_miles_per_hour_zero">0 миль в час</string>
|
<string name="voice_speed_miles_per_hour_zero">0 миль в час</string>
|
||||||
<string name="voice_template">Длина треков: %1$s за %2$s (%3$s).</string>
|
<string name="voice_template">Длина треков: %1$s за %2$s (%3$s)</string>
|
||||||
<string name="voice_total_distance_kilometers_few">Общая длина: %1$.2f километра</string>
|
<string name="voice_total_distance_kilometers_few">Общая длина: %1$.2f километра</string>
|
||||||
<string name="voice_total_distance_kilometers_many">Общая длина: %1$.2f километров</string>
|
<string name="voice_total_distance_kilometers_many">Общая длина: %1$.2f километров</string>
|
||||||
<string name="voice_total_distance_kilometers_one">Общая длина: 1 километр</string>
|
<string name="voice_total_distance_kilometers_one">Общая длина: 1 километр</string>
|
||||||
@@ -486,4 +488,116 @@ limitations under the License.
|
|||||||
<string name="help_recording_title">Как OpenTracks записывает данные\?</string>
|
<string name="help_recording_title">Как OpenTracks записывает данные\?</string>
|
||||||
<string name="help_track_export_content"> Трек может быть экспортирован из \"Настроек\" в виде KMZ, KML или GPX. KMZ/KML все данные; GPX только местоположения. Поделиться треком можно через значок <i>поделиться</i> предоставляется как KMZ. </string>
|
<string name="help_track_export_content"> Трек может быть экспортирован из \"Настроек\" в виде KMZ, KML или GPX. KMZ/KML все данные; GPX только местоположения. Поделиться треком можно через значок <i>поделиться</i> предоставляется как KMZ. </string>
|
||||||
<string name="help_recording_content">OpenTracks использует датчик GPS устройства для непрерывной записи местоположения. Если устройство GPS сообщает неточные данные (например, местоположение, скорость, высоту), OpenTracks ничего не может с этим поделать.</string>
|
<string name="help_recording_content">OpenTracks использует датчик GPS устройства для непрерывной записи местоположения. Если устройство GPS сообщает неточные данные (например, местоположение, скорость, высоту), OpenTracks ничего не может с этим поделать.</string>
|
||||||
|
<string name="export_progress_summary_new_msg">Новые экспортируемые треки</string>
|
||||||
|
<string name="help_data_storage_title">Как OpenTracks записывает данные с датчиков\?</string>
|
||||||
|
<string name="export_track_already_exists_msg">Дорожка %1$s уже существует в каталоге назначения.</string>
|
||||||
|
<string name="export_without_photos">без фотографий</string>
|
||||||
|
<string name="export_with_photos">с фотографией</string>
|
||||||
|
<string name="description_default_speed_or_pace">По типу деятельности</string>
|
||||||
|
<string name="export_all_do_it_for_all">делать это во всех конфликтах</string>
|
||||||
|
<string name="stats_loss">Потери</string>
|
||||||
|
<string name="export_progress_summary_skip_msg">Пропущенные дорожки</string>
|
||||||
|
<string name="generic_click_twice_cancel">Нажмите назад, чтобы отменить</string>
|
||||||
|
<string name="generic_completed">Завершено</string>
|
||||||
|
<string name="generic_show_errors">Показать ошибки</string>
|
||||||
|
<string name="generic_separator_done_total">/</string>
|
||||||
|
<string name="generic_skip">Скип</string>
|
||||||
|
<string name="generic_open_track">Открытая трасса</string>
|
||||||
|
<string name="export_progress_summary_errors_msg">Ошибки</string>
|
||||||
|
<string name="import_file_imported">Файл %1$s импортирован</string>
|
||||||
|
<string name="import_error_list_dialog_title">Файлы не импортированы</string>
|
||||||
|
<string name="import_error_info">%1$s: %2$s</string>
|
||||||
|
<string name="import_no_kml_file_found">Файл doc.kml не найден в KMZ</string>
|
||||||
|
<string name="import_unable_to_import_file">Невозможно импортировать файл: %1$s</string>
|
||||||
|
<string name="import_parser_error">Ошибка парсера: %1$s</string>
|
||||||
|
<string name="import_unsupported_format">Недопустимый формат файла</string>
|
||||||
|
<string name="import_thread_interrupted">Нить прервана</string>
|
||||||
|
<string name="gps_fixed_and_ready">GPS зафиксирован и готов</string>
|
||||||
|
<string name="gps_disabled_msg">GPS отключен</string>
|
||||||
|
<string name="marker_add_photo_canceled">Отменено, фотомаркер не добавлен</string>
|
||||||
|
<string name="import_progress_summary_exists_msg">Уже существует</string>
|
||||||
|
<string name="import_progress_summary_ok_msg">Импорт файлов</string>
|
||||||
|
<string name="menu_resume_track">След от резюме</string>
|
||||||
|
<string name="settings_night_mode_title">UI Тема</string>
|
||||||
|
<string name="open_track_as_trackfileformat">Открыть как %1$s</string>
|
||||||
|
<string name="open_track_as_GPX">Открыть как GPX</string>
|
||||||
|
<string name="open_track_as_KMZ">Открыть как KMZ</string>
|
||||||
|
<string name="open_track_using_dashboard">Открыть с помощью OpenTracks-Dashboard</string>
|
||||||
|
<string name="help_support_title">Как поддержать OpenTracks\?</string>
|
||||||
|
<string name="help_gps_troubleshooting_title">OpenTracks не регистрирует местоположения\?</string>
|
||||||
|
<string name="activity_type_escooter">эскулап</string>
|
||||||
|
<string name="app_not_installed_show_on_map">Пожалуйста, установите приложение, которое поддерживает представление данных карты.</string>
|
||||||
|
<string name="permission_gps_failed">OpenTracks требует разрешения на использование GPS.</string>
|
||||||
|
<string name="settings_prevent_reimport_tracks_title">Предотвращение повторного импорта треков</string>
|
||||||
|
<string name="sensor_state_cadence_max">Макс. каденция</string>
|
||||||
|
<string name="sensor_state_cadence_avg">Средний каденс</string>
|
||||||
|
<string name="settings_recording_fullscreen_on_while_recording_title">Полный экран</string>
|
||||||
|
<string name="settings_recording_keep_screen_on_while_recording_summary">Во время записи не выключайте экран.</string>
|
||||||
|
<string name="settings_recording_keep_screen_on_while_recording_title">Не выключайте экран</string>
|
||||||
|
<string name="settings_recording_show_on_lockscreen_while_recording_title">Показывать статистику на экране блокировки</string>
|
||||||
|
<string name="settings_default_trackfileformat">Формат экспорта/обмена файлами</string>
|
||||||
|
<string name="sensor_state_heart_rate_max">Максимальный пульс</string>
|
||||||
|
<string name="sensor_state_heart_rate_avg">Средний пульс</string>
|
||||||
|
<string name="bluetooth_disabled">Включите Bluetooth.</string>
|
||||||
|
<string name="sensor_not_known">Датчик %1$s неизвестен. Пожалуйста, проверьте настройки.</string>
|
||||||
|
<string name="sensor_state_power_avg">Средняя мощность</string>
|
||||||
|
<string name="track_recording_notification_accuracy">Точность расположения: %1$s</string>
|
||||||
|
<string name="stats_split_interval">Раздельный интервал</string>
|
||||||
|
<string name="stats_intervals">Интервалы</string>
|
||||||
|
<string name="settings_sensor_wheel_circumference">Окружность колеса (мм)</string>
|
||||||
|
<string name="settings_sensor_disconnected">Отключен от %1$s</string>
|
||||||
|
<string name="settings_sensor_connecting">Подключен к %1$s</string>
|
||||||
|
<string name="settings_night_mode_option_night">Ночь</string>
|
||||||
|
<string name="settings_night_mode_option_day">День</string>
|
||||||
|
<string name="export_progress_summary_overwrite_msg">Перезапись дорожек</string>
|
||||||
|
<string name="export_track_errors">Треки не экспортируются</string>
|
||||||
|
<string name="export_error_post_workout">Сбой экспорта после тренировки, пожалуйста, проверьте каталог экспорта.</string>
|
||||||
|
<string name="description_altitude_loss">Потеря высоты: %1$d м (%2$d футов)</string>
|
||||||
|
<string name="settings_default_export_uri_title">Каталог экспорта треков</string>
|
||||||
|
<string name="instant_export_enabled_summary">Экспорт дорожки в хранилище после завершения записи</string>
|
||||||
|
<string name="share_image_subject">Я хотел бы поделиться с вами изображением</string>
|
||||||
|
<string name="track_detail_record_success">Сейчас записываю новый трек</string>
|
||||||
|
<string name="track_detail_intervals_tab">Интервалы</string>
|
||||||
|
<string name="help_data_storage_content"> Данные датчика хранятся в каждом месте записи. Применительно к датчику пульса это означает, что для каждого места сохраняется только <i>одно</i> измерение пульса. Если вы стоите на месте или находитесь в помещении, данные датчика не будут записаны (но они будут отображаться). </string>
|
||||||
|
<string name="help_gps_troubleshooting_content"> Что вы можете сделать:
|
||||||
|
\n a) проверить, включен ли GPS,
|
||||||
|
\n б) улучшить прием GPS (например, выехать на природу),
|
||||||
|
\n c) проверить с помощью приложения <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, и
|
||||||
|
\n г) отключите энергосбережение.
|
||||||
|
\n
|
||||||
|
\n Если все это не поможет, пожалуйста, сообщите об ошибке. </string>
|
||||||
|
<string name="track_pace_notification">Темп: %1$s</string>
|
||||||
|
<string name="track_speed_notification">Скорость: %1$s</string>
|
||||||
|
<string name="track_distance_notification">Расстояние: %1$s</string>
|
||||||
|
<string name="value_pace_kilometer">%1$d:%2$02d мин/км</string>
|
||||||
|
<string name="value_pace_mile">%1$d:%2$02d мин/миль</string>
|
||||||
|
<string name="value_float_mile_hour">%1$.2f mph</string>
|
||||||
|
<string name="value_float_kilometer_hour">%1$.2f км/ч</string>
|
||||||
|
<string name="interval_list_empty_message">Интервалов пока нет</string>
|
||||||
|
<string name="aggregated_stats_empty_message">Запишите свой первый трек, чтобы увидеть агрегированную статистику</string>
|
||||||
|
<string name="track_detail_resume_success">Теперь возобновляем путь</string>
|
||||||
|
<string name="voice_pace_lap">Время круга %1$s</string>
|
||||||
|
<string name="voice_speed_lap">Скорость круга %1$s</string>
|
||||||
|
<string name="instant_export_enabled_title">Мгновенный экспорт после тренировки</string>
|
||||||
|
<string name="toast_message_instant_track_was_exported">Последний экспортированный трек</string>
|
||||||
|
<string name="hold_to_pause">Удерживайте для паузы</string>
|
||||||
|
<string name="hold_to_stop">Удерживать для остановки</string>
|
||||||
|
<string name="share_image_body">Я думаю, вас может заинтересовать это изображение.</string>
|
||||||
|
<string name="help_support_content"> Вы можете:
|
||||||
|
\n a) распространять информацию об OpenTracks,
|
||||||
|
\n б) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">предлагать возможности</a>,
|
||||||
|
\n в) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">сообщать об ошибках</a>,
|
||||||
|
\n d) помогать в разработке программного обеспечения,
|
||||||
|
\n д) покупать в <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||||
|
\n f) жертвовать, и
|
||||||
|
\n g) делать все, что, по вашему мнению, поможет OpenTracks. </string>
|
||||||
|
<string name="settings_error_initial_values_restored">Извините, но произошла ошибка. Все настройки были возвращены к значениям по умолчанию.</string>
|
||||||
|
<string name="settings_night_mode_option_system">Система</string>
|
||||||
|
<string name="settings_recording_fullscreen_on_while_recording_summary">Во время записи присутствует полноэкранный режим.</string>
|
||||||
|
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Во время записи показывайте статистику, не разблокируя устройство.</string>
|
||||||
|
<string name="menu_insert_gallery_img">Вставка изображения галереи</string>
|
||||||
|
<string name="sensor_could_not_scan">Не удалось выполнить сканирование устройств Bluetooth (ошибка %1$i).</string>
|
||||||
|
<string name="import_progress_summary_errors_msg">Ошибки</string>
|
||||||
|
<string name="generic_overwrite">Перезаписать</string>
|
||||||
|
<string name="import_prevent_reimport">Вы настроили предотвращение повторного импорта трека</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -17,6 +17,13 @@ limitations under the License.
|
|||||||
<string name="app_web_url" translatable="false">https://github.com/OpenTracksApp/OpenTracks</string>
|
<string name="app_web_url" translatable="false">https://github.com/OpenTracksApp/OpenTracks</string>
|
||||||
<string name="app_name" translatable="false">OpenTracks</string>
|
<string name="app_name" translatable="false">OpenTracks</string>
|
||||||
|
|
||||||
|
<string name="egm2008" translatable="false">EGM2008</string>
|
||||||
|
|
||||||
|
<string name="third_party_data" translatable="false">
|
||||||
|
<a href="https://earth-info.nga.mil/">National Geospatial-Intelligence Agency (NGA)\'s EGM2008 model</a> 5\' minute resolution.
|
||||||
|
Data provided by <a href="https://geographiclib.sourceforge.io/html/geoid.html">GeographicLib</a>.
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- List here all used libraries (will be shown in the about activity -->
|
<!-- List here all used libraries (will be shown in the about activity -->
|
||||||
<string name="third_party_libraries" translatable="false">
|
<string name="third_party_libraries" translatable="false">
|
||||||
<a href="https://developer.android.com/topic/libraries/support-library/">Google\'s androidX support</a> (Apache License, Version 2.0) \n
|
<a href="https://developer.android.com/topic/libraries/support-library/">Google\'s androidX support</a> (Apache License, Version 2.0) \n
|
||||||
|
|||||||
@@ -219,6 +219,26 @@
|
|||||||
<item>@string/settings_recording_track_name_number_option</item>
|
<item>@string/settings_recording_track_name_number_option</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
|
||||||
|
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
|
||||||
|
<string name="frequency_off" translatable="false">0</string>
|
||||||
|
<string-array name="frequency_values">
|
||||||
|
<item>@string/frequency_off</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>5</item>
|
||||||
|
<item>10</item>
|
||||||
|
<item>15</item>
|
||||||
|
<item>30</item>
|
||||||
|
<item>60</item>
|
||||||
|
<item>-1</item>
|
||||||
|
<item>-5</item>
|
||||||
|
<item>-10</item>
|
||||||
|
<item>-25</item>
|
||||||
|
<item>-50</item>
|
||||||
|
<item>-100</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string name="export_trackfileformat_key" translatable="false">exportTrackFileFormat</string>
|
<string name="export_trackfileformat_key" translatable="false">exportTrackFileFormat</string>
|
||||||
<!-- See TrackFileFormat -->
|
<!-- See TrackFileFormat -->
|
||||||
<string name="export_trackfileformat_default" translatable="false">KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES</string>
|
<string name="export_trackfileformat_default" translatable="false">KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES</string>
|
||||||
|
|||||||
@@ -18,25 +18,6 @@
|
|||||||
|
|
||||||
<string name="split_frequency_key" translatable="false" tools:keep="@string/split_frequency_key">splitFrequency</string>
|
<string name="split_frequency_key" translatable="false" tools:keep="@string/split_frequency_key">splitFrequency</string>
|
||||||
<string name="split_frequency_default" translatable="false" tools:keep="@string/split_frequency_default">@string/frequency_off</string>
|
<string name="split_frequency_default" translatable="false" tools:keep="@string/split_frequency_default">@string/frequency_off</string>
|
||||||
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
|
|
||||||
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
|
|
||||||
<string name="frequency_off" translatable="false">0</string>
|
|
||||||
<string-array name="frequency_values">
|
|
||||||
<item>@string/frequency_off</item>
|
|
||||||
<item>1</item>
|
|
||||||
<item>2</item>
|
|
||||||
<item>5</item>
|
|
||||||
<item>10</item>
|
|
||||||
<item>15</item>
|
|
||||||
<item>30</item>
|
|
||||||
<item>60</item>
|
|
||||||
<item>-1</item>
|
|
||||||
<item>-5</item>
|
|
||||||
<item>-10</item>
|
|
||||||
<item>-25</item>
|
|
||||||
<item>-50</item>
|
|
||||||
<item>-100</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string name="chart_show_cadence_key" translatable="false" tools:keep="@string/chart_show_cadence_key">chartShowCadence</string>
|
<string name="chart_show_cadence_key" translatable="false" tools:keep="@string/chart_show_cadence_key">chartShowCadence</string>
|
||||||
<bool name="chart_show_cadence_default" translatable="false" tools:keep="@bool/chart_show_cadence_default">
|
<bool name="chart_show_cadence_default" translatable="false" tools:keep="@bool/chart_show_cadence_default">
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
<string name="about_version_name">Version name: %1$s</string>
|
<string name="about_version_name">Version name: %1$s</string>
|
||||||
<string name="about_version_code">Version code: %1$d</string>
|
<string name="about_version_code">Version code: %1$d</string>
|
||||||
<string name="about_history">OpenTracks is a fork of <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (discontinued in 2016).</string>
|
<string name="about_history">OpenTracks is a fork of <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (discontinued in 2016).</string>
|
||||||
|
<string name="about_third_party_data">Third-party data</string>
|
||||||
<string name="about_libraries">Libraries</string>
|
<string name="about_libraries">Libraries</string>
|
||||||
<!-- Help -->
|
<!-- Help -->
|
||||||
<string name="help_feature_title">OpenTracks has the following features:</string>
|
<string name="help_feature_title">OpenTracks has the following features:</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user