forked from upstream-mirrors/OpenTracks
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0b84df8f6 | |||
| a431850793 | |||
| 5c17c8ffcf | |||
| 0691524dfa | |||
| 1c825b052e | |||
| eb8d28dfc1 | |||
| 9f1ea4d745 | |||
| dbd64a8075 | |||
| 75a88ca0e1 | |||
| 2af19b00e5 | |||
| 11db0d1a4f | |||
| 6a4adee2f2 | |||
| d04b420c56 | |||
| 0122539d0f | |||
| 0b8c2a0ef6 | |||
| cef363562c | |||
| e681531186 | |||
| 65c4b85bc4 | |||
| d7022a07a2 | |||
| 81b090893b | |||
| 3dca9304c4 | |||
| 7fc6492e13 | |||
| 80d968dd0f | |||
| f336a1da12 | |||
| 1657bf5ec1 | |||
| d046ca1cd8 | |||
| 6c852091ce | |||
| 4906d3f942 | |||
| 0395394e26 | |||
| ebcc7dc46e | |||
| 1e4246e73a | |||
| 3ca8381504 | |||
| b92457cb23 | |||
| 4baec1e672 | |||
| c702c5a4a0 |
+6
-6
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.0-beta03'
|
||||
classpath 'com.android.tools.build:gradle:8.1.0-beta04'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
compileSdk 33
|
||||
compileSdk 34
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
@@ -64,13 +64,13 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 5222
|
||||
versionName "v4.4.0"
|
||||
versionCode 5262
|
||||
versionName "v4.5.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
targetSdk 34
|
||||
|
||||
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
@@ -141,7 +141,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.3.1'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.4.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.2'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
v4.5.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Material3
|
||||
- App shortcut for aggregated statistics
|
||||
- New Unit: miles with meters
|
||||
|
||||
Bugfix:
|
||||
- GPS disabled warnings shown repeatedly (#1429)
|
||||
|
||||
Developer:
|
||||
- Use Java Records
|
||||
- Target API34
|
||||
- Refactor sensor handling
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,10 +80,10 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
@@ -143,12 +143,16 @@ fi
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
||||
Vendored
+1
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
||||
@@ -11,10 +11,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
@@ -32,11 +31,7 @@ public class EspressoDeleteTrackTest {
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleLocation = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleBluetooth = GrantPermissionRule.grant(Manifest.permission.BLUETOOTH_CONNECT);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleNotifications = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS);
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
@Test
|
||||
public void espressoDeleteTrackTest() {
|
||||
@@ -57,7 +52,15 @@ public class EspressoDeleteTrackTest {
|
||||
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(longClick());
|
||||
|
||||
// open menu and delete selected track
|
||||
onView(allOf(withContentDescription("More options"), isDisplayed()))
|
||||
//TODO openActionBarOverflowOrOptionsMenu(); doesn't work
|
||||
onView(
|
||||
allOf(withContentDescription("More options"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(androidx.appcompat.R.id.action_mode_bar),
|
||||
1),
|
||||
2),
|
||||
isDisplayed()))
|
||||
.perform(click());
|
||||
|
||||
onView(withText("Delete")).perform(click());
|
||||
|
||||
@@ -40,11 +40,7 @@ public class EspressoEditTrackRecordingTest {
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleLocation = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleBluetooth = GrantPermissionRule.grant(android.Manifest.permission.BLUETOOTH_CONNECT);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleNotifications = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS);
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
|
||||
@@ -33,12 +33,7 @@ public class EspressoUITest {
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleLocation = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleBluetooth = GrantPermissionRule.grant(android.Manifest.permission.BLUETOOTH_CONNECT);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleNotifications = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS);
|
||||
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
@LargeTest
|
||||
@Test
|
||||
public void record_stop_resume_stop_finish() {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import de.dennisguse.opentracks.test.BuildConfig;
|
||||
|
||||
public class TestUtil {
|
||||
|
||||
public static GrantPermissionRule createGrantPermissionRule() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
return GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.POST_NOTIFICATIONS);
|
||||
}
|
||||
return GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_CONNECT);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class ChartPointTest {
|
||||
statistics.setTotalTime(Duration.ofSeconds(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), false, UnitSystem.IMPERIAL);
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), false, UnitSystem.IMPERIAL_FEET);
|
||||
|
||||
// then
|
||||
assertEquals(1000000, (long) point.getTimeOrDistance());
|
||||
|
||||
@@ -96,16 +96,7 @@ public class TestDataUtil {
|
||||
return new TrackData(track, trackPoints, markers);
|
||||
}
|
||||
|
||||
public static class TrackData {
|
||||
public final Track track;
|
||||
public final List<TrackPoint> trackPoints;
|
||||
public final List<Marker> markers;
|
||||
|
||||
public TrackData(Track track, List<TrackPoint> trackPoints, List<Marker> markers) {
|
||||
this.track = track;
|
||||
this.trackPoints = trackPoints;
|
||||
this.markers = markers;
|
||||
}
|
||||
public record TrackData(Track track, List<TrackPoint> trackPoints, List<Marker> markers) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -424,7 +424,7 @@ public class CustomContentProviderUtilsTest {
|
||||
Marker.Id markerId = new Marker.Id(System.currentTimeMillis());
|
||||
marker.setId(markerId);
|
||||
ContentValues contentValues = contentProviderUtils.createContentValues(marker);
|
||||
assertEquals(markerId.getId(), contentValues.get(MarkerColumns._ID));
|
||||
assertEquals(markerId.id(), contentValues.get(MarkerColumns._ID));
|
||||
assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(MarkerColumns.LONGITUDE));
|
||||
assertEquals(TEST_DESC, contentValues.get(MarkerColumns.DESCRIPTION));
|
||||
}
|
||||
@@ -458,7 +458,7 @@ public class CustomContentProviderUtilsTest {
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
|
||||
Marker marker = contentProviderUtils.createMarker(cursorMock);
|
||||
assertEquals(id, marker.getId().getId());
|
||||
assertEquals(id, marker.getId().id());
|
||||
assertEquals(name, marker.getName());
|
||||
assertEquals(trackId, marker.getTrackId().getId());
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class TrackSelectionTest extends TestCase {
|
||||
@Test
|
||||
public void testFilterBuildSelection_onlyOneCategory() {
|
||||
// given
|
||||
TrackSelection filter = new TrackSelection().addCategory("running");
|
||||
TrackSelection filter = new TrackSelection().addActivityType("running");
|
||||
|
||||
// when
|
||||
SelectionData selection = filter.buildSelection();
|
||||
@@ -82,10 +82,10 @@ public class TrackSelectionTest extends TestCase {
|
||||
public void testFilterBuildSelection_severalCategories() {
|
||||
// given
|
||||
TrackSelection filter = new TrackSelection()
|
||||
.addCategory("running")
|
||||
.addCategory("road biking")
|
||||
.addCategory("mountain biking")
|
||||
.addCategory("trail walking");
|
||||
.addActivityType("running")
|
||||
.addActivityType("road biking")
|
||||
.addActivityType("mountain biking")
|
||||
.addActivityType("trail walking");
|
||||
|
||||
// when
|
||||
SelectionData selection = filter.buildSelection();
|
||||
@@ -136,8 +136,8 @@ public class TrackSelectionTest extends TestCase {
|
||||
.addTrackId(trackId1)
|
||||
.addTrackId(trackId2)
|
||||
.addTrackId(trackId3)
|
||||
.addCategory("running")
|
||||
.addCategory("road biking");
|
||||
.addActivityType("running")
|
||||
.addActivityType("road biking");
|
||||
|
||||
// when
|
||||
SelectionData selection = filter.buildSelection();
|
||||
@@ -188,8 +188,8 @@ public class TrackSelectionTest extends TestCase {
|
||||
long oneDay = 24 * 60 * 60 * 1000;
|
||||
|
||||
TrackSelection filter = new TrackSelection()
|
||||
.addCategory("running")
|
||||
.addCategory("road biking")
|
||||
.addActivityType("running")
|
||||
.addActivityType("road biking")
|
||||
.addDateRange(instant, instant.plusMillis(oneDay));
|
||||
|
||||
// when
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ public class DistanceFormatterTest {
|
||||
public void testFormatDistance_imperial() {
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.IMPERIAL)
|
||||
.setUnit(UnitSystem.IMPERIAL_FEET)
|
||||
.build(context);
|
||||
|
||||
// A large number in imperial
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SpeedFormatterTest {
|
||||
public void testGetSpeedParts_pace_imperial() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.IMPERIAL)
|
||||
.setUnit(UnitSystem.IMPERIAL_FEET)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(context);
|
||||
|
||||
@@ -72,7 +72,7 @@ public class SpeedFormatterTest {
|
||||
public void testGetSpeedParts_speed_imperial() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.IMPERIAL)
|
||||
.setUnit(UnitSystem.IMPERIAL_FEET)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(context);
|
||||
|
||||
|
||||
+19
-26
@@ -57,7 +57,6 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||
@@ -97,7 +96,7 @@ public class ExportImportTest {
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
private static final String TRACK_ICON = "the track icon";
|
||||
private static final String TRACK_CATEGORY = "the category";
|
||||
private static final String TRACK_ACTIVITY_TYPE = "the activity type";
|
||||
private static final String TRACK_DESCRIPTION = "the description";
|
||||
|
||||
private File tmpFile;
|
||||
@@ -165,8 +164,7 @@ public class ExportImportTest {
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||
BluetoothRemoteSensorManager mockRemoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
|
||||
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet();
|
||||
service.endCurrentTrack();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||
@@ -179,12 +177,11 @@ public class ExportImportTest {
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:24Z");
|
||||
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
|
||||
service.endCurrentTrack();
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
track.setIcon(TRACK_ICON);
|
||||
track.setCategory(TRACK_CATEGORY);
|
||||
track.setActivityType(TRACK_ACTIVITY_TYPE);
|
||||
track.setDescription(TRACK_DESCRIPTION);
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
@@ -219,7 +216,7 @@ public class ExportImportTest {
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getActivityType(), importedTrack.getActivityType());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
@@ -326,7 +323,7 @@ public class ExportImportTest {
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getActivityType(), importedTrack.getActivityType());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
|
||||
@@ -495,9 +492,6 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
||||
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
Mockito.when(remoteSensorManager.fill(Mockito.any())).thenAnswer(invocation -> {
|
||||
TrackPoint trackPoint = invocation.getArgument(0);
|
||||
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
||||
@@ -508,21 +502,20 @@ public class ExportImportTest {
|
||||
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
|
||||
sensorDataSet.set(cyclingCadence);
|
||||
|
||||
if (distance != null && speed != null) {
|
||||
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
|
||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
|
||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||
sensorDataSet.set(distanceSpeed);
|
||||
}
|
||||
if (distance != null && speed != null) {
|
||||
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
|
||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
|
||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||
sensorDataSet.set(distanceSpeed);
|
||||
}
|
||||
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
});
|
||||
trackPointCreator.getSensorManager().setBluetoothSensorManager(remoteSensorManager);
|
||||
trackPointCreator.onChange(new SensorDataSet());
|
||||
trackPointCreator.getSensorManager().sensorDataSet = sensorDataSet;
|
||||
trackPointCreator.onChange(sensorDataSet);
|
||||
|
||||
//TODO Might require: getSensorManager().sensorDataSet = new SensorDataSet()
|
||||
}
|
||||
|
||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
||||
@@ -545,6 +538,6 @@ public class ExportImportTest {
|
||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||
|
||||
trackPointCreator.setClock(time);
|
||||
trackPointCreator.getGpsHandler().onLocationChanged(location);
|
||||
trackPointCreator.onChange(location);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -79,7 +79,7 @@ public class GPXTrackImporterTest {
|
||||
// 2. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("the category", importedTrack.getCategory());
|
||||
assertEquals("the category", importedTrack.getActivityType());
|
||||
assertEquals("the description", importedTrack.getDescription());
|
||||
assertEquals("2021-01-07 22:51", importedTrack.getName());
|
||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||
@@ -133,7 +133,7 @@ public class GPXTrackImporterTest {
|
||||
// 2. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("", importedTrack.getCategory());
|
||||
assertEquals("", importedTrack.getActivityType());
|
||||
assertEquals("", importedTrack.getDescription());
|
||||
assertEquals("20210907_213924.gpx", importedTrack.getName());
|
||||
assertEquals("", importedTrack.getIcon());
|
||||
@@ -181,7 +181,7 @@ public class GPXTrackImporterTest {
|
||||
// 2. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("", importedTrack.getCategory());
|
||||
assertEquals("", importedTrack.getActivityType());
|
||||
assertEquals("", importedTrack.getDescription());
|
||||
assertEquals("20210907_213924.gpx", importedTrack.getName());
|
||||
assertEquals("", importedTrack.getIcon());
|
||||
|
||||
+3
-3
@@ -70,7 +70,7 @@ public class KMLTrackImporterTest {
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("", importedTrack.getCategory());
|
||||
assertEquals("", importedTrack.getActivityType());
|
||||
assertEquals("", importedTrack.getDescription());
|
||||
assertEquals("", importedTrack.getName());
|
||||
assertEquals("", importedTrack.getIcon());
|
||||
@@ -119,7 +119,7 @@ public class KMLTrackImporterTest {
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("", importedTrack.getCategory());
|
||||
assertEquals("", importedTrack.getActivityType());
|
||||
assertEquals("", importedTrack.getDescription());
|
||||
assertEquals("", importedTrack.getName());
|
||||
assertEquals("", importedTrack.getIcon());
|
||||
@@ -159,7 +159,7 @@ public class KMLTrackImporterTest {
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("unknown", importedTrack.getCategory());
|
||||
assertEquals("unknown", importedTrack.getActivityType());
|
||||
assertEquals("Test Track", importedTrack.getDescription());
|
||||
assertEquals("2020-11-28 18:06", importedTrack.getName());
|
||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||
|
||||
@@ -1,47 +1,55 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
||||
|
||||
/**
|
||||
* NOTE: Test data is completely artificial.
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AltitudeSumManagerTest {
|
||||
|
||||
private final AltitudeSumManager subject = new AltitudeSumManager();
|
||||
|
||||
private static void addSensorValue(AltitudeSumManager altitudeSumManager, float[] values) {
|
||||
for (float f : values) {
|
||||
altitudeSumManager.onSensorValueChanged(f);
|
||||
altitudeSumManager.onSensorValueChanged(AtmosphericPressure.ofHPA(f));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject.reset();
|
||||
}
|
||||
@Mock
|
||||
BarometerInternal driver;
|
||||
|
||||
@Test
|
||||
public void getAltitudeGainLoss_downhill() {
|
||||
// given
|
||||
subject.setConnected(true);
|
||||
AltitudeSumManager subject = new AltitudeSumManager(driver);
|
||||
|
||||
when(driver.isConnected()).thenReturn(true);
|
||||
|
||||
// then
|
||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1015.14f, 1015.15f});
|
||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f});
|
||||
|
||||
// then
|
||||
Assert.assertEquals(0f, subject.getAltitudeGain_m(), 0.01);
|
||||
Assert.assertEquals(48.0, subject.getAltitudeLoss_m(), 0.01);
|
||||
Assert.assertEquals(15f, subject.getAltitudeLoss_m(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sensorUnavailable() {
|
||||
// given
|
||||
subject.setConnected(false);
|
||||
AltitudeSumManager subject = new AltitudeSumManager(driver);
|
||||
|
||||
when(driver.isConnected()).thenReturn(true);
|
||||
|
||||
// then
|
||||
subject.onSensorValueChanged(999f);
|
||||
subject.onSensorValueChanged(AtmosphericPressure.ofHPA(999f));
|
||||
|
||||
// then
|
||||
Assert.assertNull(subject.getAltitudeGain_m());
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
@@ -20,7 +21,7 @@ public class BluetoothUtilsTest {
|
||||
@Test
|
||||
public void parseHeartRate_uint8() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||
|
||||
// when
|
||||
@@ -33,7 +34,7 @@ public class BluetoothUtilsTest {
|
||||
@Test
|
||||
public void parseHeartRate_uint16() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||
|
||||
// when
|
||||
@@ -43,9 +44,22 @@ public class BluetoothUtilsTest {
|
||||
assertEquals(HeartRate.of(257), heartRate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseEnvironmentalSensing_Pa() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.BAROMETRIC_PRESSURE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{(byte) 0xB2, (byte) 0x48, (byte) 0x0F, (byte) 0x00});
|
||||
|
||||
// when
|
||||
AtmosphericPressure pressure = BluetoothUtils.parseEnvironmentalSensing(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(AtmosphericPressure.ofPA(100165), pressure);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||
|
||||
// when
|
||||
@@ -58,7 +72,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
@@ -71,7 +85,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankWheel() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
@@ -84,34 +98,34 @@ public class BluetoothUtilsTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
||||
|
||||
// when
|
||||
SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower("", "", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(40, powerCadence.getPower().getValue().getW(), 0.01);
|
||||
assertEquals(40, powerCadence.power().getValue().getW(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power_with_cadence() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
|
||||
|
||||
// when
|
||||
SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower("", "", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(0, powerCadence.getPower().getValue().getW(), 0.01);
|
||||
assertEquals(0, powerCadence.power().getValue().getW(), 0.01);
|
||||
|
||||
assertEquals(12, powerCadence.getCadence().getCrankRevolutionsCount());
|
||||
assertEquals(17125, powerCadence.getCadence().getCrankRevolutionsTime());
|
||||
assertEquals(12, powerCadence.cadence().getCrankRevolutionsCount());
|
||||
assertEquals(17125, powerCadence.cadence().getCrankRevolutionsTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseRunningSpeedAndCadence_with_distance() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
||||
|
||||
// when
|
||||
|
||||
@@ -5,6 +5,8 @@ import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
|
||||
public class PressureSensorUtilsTest {
|
||||
|
||||
// real data at 500Hz stationary
|
||||
@@ -12,11 +14,11 @@ public class PressureSensorUtilsTest {
|
||||
public void altitudeChanges_none() {
|
||||
// given
|
||||
float[] sensorValues_hPa = new float[]{1015.6876f, 1015.699f, 1015.70905f, 1015.71075f, 1015.7224f, 1015.72f, 1015.7373f, 1015.7481f, 1015.74133f, 1015.73553f, 1015.7462f, 1015.74896f, 1015.752f, 1015.754f, 1015.7456f, 1015.73303f, 1015.7288f, 1015.7239f, 1015.7298f, 1015.7387f, 1015.72534f, 1015.72577f, 1015.72797f, 1015.71796f, 1015.7113f, 1015.7097f, 1015.7096f, 1015.7013f, 1015.7072f, 1015.71063f, 1015.7088f, 1015.7115f, 1015.713f, 1015.7121f, 1015.7062f, 1015.7062f, 1015.6994f, 1015.69617f, 1015.68945f, 1015.698f, 1015.6913f, 1015.69696f, 1015.69293f, 1015.6955f, 1015.6928f, 1015.6939f, 1015.68787f, 1015.6881f, 1015.68536f, 1015.68726f, 1015.6879f, 1015.6904f, 1015.6937f, 1015.69476f, 1015.7046f, 1015.7019f, 1015.7079f, 1015.71124f, 1015.71216f, 1015.71436f, 1015.7171f, 1015.7186f, 1015.7186f, 1015.7246f, 1015.72046f, 1015.7211f, 1015.72375f, 1015.7194f, 1015.72125f, 1015.7161f, 1015.71185f, 1015.7188f, 1015.721f, 1015.7252f, 1015.72266f, 1015.7236f, 1015.72614f, 1015.7266f, 1015.7344f, 1015.7376f, 1015.7337f, 1015.7253f, 1015.71875f, 1015.7219f, 1015.7219f, 1015.7317f, 1015.7284f, 1015.73444f, 1015.73584f, 1015.7369f, 1015.73254f, 1015.7369f, 1015.7336f, 1015.73254f, 1015.73676f, 1015.74176f, 1015.74506f, 1015.7478f, 1015.755f, 1015.7515f, 1015.7543f, 1015.7459f, 1015.7468f, 1015.7492f, 1015.74585f, 1015.74756f, 1015.74756f, 1015.75085f, 1015.754f, 1015.75336f, 1015.7568f, 1015.75165f, 1015.761f, 1015.7527f, 1015.75684f, 1015.7635f, 1015.7585f, 1015.7552f, 1015.7475f, 1015.74506f, 1015.7542f, 1015.75586f, 1015.74176f, 1015.74414f, 1015.7501f, 1015.751f, 1015.7478f, 1015.755f, 1015.75836f, 1015.7592f, 1015.75256f, 1015.7559f, 1015.76654f, 1015.76154f, 1015.766f, 1015.77014f, 1015.76904f, 1015.76013f, 1015.7674f, 1015.76514f, 1015.7674f, 1015.7635f, 1015.76404f, 1015.7607f, 1015.754f, 1015.7574f, 1015.7607f, 1015.7607f, 1015.7607f, 1015.754f, 1015.7567f, 1015.7616f, 1015.7643f, 1015.7666f, 1015.7609f, 1015.766f, 1015.76764f, 1015.755f, 1015.75757f, 1015.74927f, 1015.74927f, 1015.7426f, 1015.745f, 1015.745f, 1015.7409f, 1015.7376f, 1015.7326f, 1015.74164f, 1015.74664f, 1015.745f, 1015.7459f, 1015.7502f, 1015.7493f, 1015.7476f, 1015.7493f, 1015.751f, 1015.7601f, 1015.76434f, 1015.766f, 1015.756f, 1015.7617f, 1015.7623f, 1015.76404f, 1015.76733f, 1015.7607f, 1015.7623f, 1015.76337f, 1015.7584f, 1015.7573f, 1015.7502f, 1015.7534f, 1015.7534f, 1015.7601f, 1015.75684f, 1015.7518f, 1015.75574f, 1015.7524f, 1015.7507f, 1015.7484f, 1015.7502f, 1015.7534f, 1015.7623f, 1015.75903f, 1015.75574f, 1015.75574f, 1015.7573f, 1015.7524f, 1015.75574f, 1015.75574f, 1015.7607f, 1015.7573f, 1015.754f, 1015.74677f, 1015.754f, 1015.75574f, 1015.75903f, 1015.7573f, 1015.7551f, 1015.7573f, 1015.754f, 1015.7507f, 1015.7374f, 1015.739f, 1015.73566f, 1015.73566f, 1015.7367f, 1015.7417f, 1015.7417f, 1015.739f, 1015.74835f, 1015.74567f, 1015.7517f, 1015.74066f, 1015.7374f, 1015.739f, 1015.7417f, 1015.73065f, 1015.72894f, 1015.73505f, 1015.73334f, 1015.74005f, 1015.74005f, 1015.7367f, 1015.7434f, 1015.744f, 1015.7423f, 1015.7384f, 1015.739f, 1015.7374f, 1015.7374f, 1015.7374f, 1015.7341f, 1015.73505f, 1015.7341f, 1015.7267f, 1015.72833f, 1015.7234f, 1015.7167f, 1015.725f, 1015.7217f, 1015.7234f, 1015.73004f, 1015.7324f, 1015.7423f, 1015.74005f, 1015.73505f, 1015.73004f, 1015.739f, 1015.7417f, 1015.74567f, 1015.74066f, 1015.74146f, 1015.74146f, 1015.73816f, 1015.7274f, 1015.72906f, 1015.7274f, 1015.7341f, 1015.739f, 1015.744f, 1015.7417f, 1015.7374f, 1015.7324f, 1015.73816f, 1015.74146f, 1015.7374f, 1015.7341f, 1015.7365f, 1015.7365f, 1015.74146f, 1015.74414f, 1015.7475f, 1015.7448f, 1015.74146f, 1015.74585f, 1015.7475f, 1015.75146f, 1015.75146f, 1015.7591f, 1015.75476f, 1015.7614f, 1015.75476f, 1015.7492f, 1015.74817f, 1015.7498f, 1015.7515f, 1015.75586f, 1015.7509f, 1015.7448f, 1015.7525f, 1015.7581f, 1015.7592f};
|
||||
float firstSensorValue = sensorValues_hPa[0];
|
||||
AtmosphericPressure firstSensorValue = AtmosphericPressure.ofHPA(sensorValues_hPa[0]);
|
||||
|
||||
// when // then
|
||||
for (float v : sensorValues_hPa) {
|
||||
assertNull(PressureSensorUtils.computeChanges(firstSensorValue, v));
|
||||
assertNull(PressureSensorUtils.computeChanges(firstSensorValue, AtmosphericPressure.ofHPA(v)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,25 +27,25 @@ public class PressureSensorUtilsTest {
|
||||
@Test
|
||||
public void computeChanges_downhill() {
|
||||
float[] sensorValues_hPa = new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f};
|
||||
float firstSensorValue = sensorValues_hPa[0];
|
||||
AtmosphericPressure firstSensorValue = AtmosphericPressure.ofHPA(sensorValues_hPa[0]);
|
||||
|
||||
// when
|
||||
float altitudeGain_m = 0;
|
||||
float altitudeLoss = 0;
|
||||
float lastUsedPressureValue_hPa = firstSensorValue;
|
||||
AtmosphericPressure lastUsedPressureValue = firstSensorValue;
|
||||
|
||||
for (float v : sensorValues_hPa) {
|
||||
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChanges(lastUsedPressureValue_hPa, v);
|
||||
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChanges(lastUsedPressureValue, AtmosphericPressure.ofHPA(v));
|
||||
if (altitudeChange != null) {
|
||||
altitudeGain_m += altitudeChange.getAltitudeGain_m();
|
||||
altitudeLoss += altitudeChange.getAltitudeLoss_m();
|
||||
lastUsedPressureValue_hPa = altitudeChange.getCurrentSensorValue_hPa();
|
||||
lastUsedPressureValue = altitudeChange.currentSensorValue();
|
||||
}
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals(0f, altitudeGain_m, 0.01);
|
||||
assertEquals(15.0f, altitudeLoss, 0.01);
|
||||
assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing
|
||||
assertEquals(1016.80f, lastUsedPressureValue.getHPA(), 0.01); //Expect exponential smoothing
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -30,6 +30,6 @@ public class TrackRecordingServiceTestUtils {
|
||||
location.setSpeed(speed);
|
||||
|
||||
trackPointCreator.setClock(time);
|
||||
trackPointCreator.getGpsHandler().onLocationChanged(location);
|
||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -185,7 +185,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, true, null, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 12.4 miles. 1 hour 5 minutes 10 seconds. Average moving speed 11.4 miles per hour.", announcement);
|
||||
@@ -207,7 +207,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, true, lastInterval, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, true, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 8.8 miles. 16 minutes 39 seconds. Average moving speed 31.8 miles per hour. Lap speed 31.8 miles per hour.", announcement);
|
||||
@@ -223,7 +223,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, false, null, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, false, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 12.4 miles. 1 hour 5 minutes 10 seconds. Pace 5 minutes 15 seconds per mile.", announcement);
|
||||
@@ -245,7 +245,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, false, lastInterval, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL_FEET, false, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 8.8 miles. 16 minutes 39 seconds. Pace 1 minute 53 seconds per mile. Lap time 1 minute 53 seconds per mile.", announcement);
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static de.dennisguse.opentracks.services.handlers.GpsStatusValue.GPS_DISABLED;
|
||||
import static de.dennisguse.opentracks.services.handlers.GpsStatusValue.GPS_ENABLED;
|
||||
import static de.dennisguse.opentracks.services.handlers.GpsStatusValue.GPS_NONE;
|
||||
import static de.dennisguse.opentracks.services.handlers.GpsStatusValue.GPS_SIGNAL_BAD;
|
||||
import static de.dennisguse.opentracks.services.handlers.GpsStatusValue.GPS_SIGNAL_FIX;
|
||||
import static de.dennisguse.opentracks.services.handlers.GpsStatusValue.GPS_SIGNAL_LOST;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class GpsStatusTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
private final static Location badFix = new Location("gps");
|
||||
private final static Location ok = new Location("gps");
|
||||
|
||||
static {
|
||||
badFix.setAccuracy(50);
|
||||
|
||||
ok.setAccuracy(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartDisabledEnabledStop() {
|
||||
ArrayList<GpsStatusValue> statusList = new ArrayList<>();
|
||||
|
||||
// given
|
||||
GpsStatusManager subject = new GpsStatusManager(context, statusList::add, new Handler());
|
||||
|
||||
// when / then
|
||||
subject.start();
|
||||
assertEquals(List.of(GPS_ENABLED), statusList);
|
||||
|
||||
subject.onGpsDisabled();
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_DISABLED), statusList);
|
||||
|
||||
subject.onGpsEnabled();
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_DISABLED, GPS_ENABLED), statusList);
|
||||
|
||||
subject.stop();
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_DISABLED, GPS_ENABLED, GPS_NONE), statusList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartBadfixOk() {
|
||||
ArrayList<GpsStatusValue> statusList = new ArrayList<>();
|
||||
|
||||
// given
|
||||
GpsStatusManager subject = new GpsStatusManager(context, statusList::add, new Handler());
|
||||
subject.onRecordingDistanceChanged(Distance.of(10));
|
||||
|
||||
// when / then
|
||||
subject.start();
|
||||
subject.onNewTrackPoint(new TrackPoint(badFix, Instant.now()));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_BAD), statusList);
|
||||
|
||||
subject.onNewTrackPoint(new TrackPoint(ok, Instant.now()));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_BAD, GPS_SIGNAL_FIX), statusList);
|
||||
|
||||
subject.onNewTrackPoint(new TrackPoint(ok, Instant.now()));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_BAD, GPS_SIGNAL_FIX), statusList);
|
||||
|
||||
subject.onNewTrackPoint(new TrackPoint(badFix, Instant.now()));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_BAD, GPS_SIGNAL_FIX, GPS_SIGNAL_BAD), statusList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartSignalLost() {
|
||||
ArrayList<GpsStatusValue> statusList = new ArrayList<>();
|
||||
|
||||
// given
|
||||
GpsStatusManager subject = new GpsStatusManager(context, statusList::add, new Handler());
|
||||
subject.onRecordingDistanceChanged(Distance.of(10));
|
||||
subject.onMinRecordingIntervalChanged(GpsStatusManager.SIGNAL_LOST_THRESHOLD.multipliedBy(-1));
|
||||
|
||||
// when / then
|
||||
subject.start();
|
||||
subject.onNewTrackPoint(new TrackPoint(ok, Instant.now().minusMillis(1000)));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_FIX), statusList);
|
||||
|
||||
subject.determineGpsStatusByTime(Instant.now());
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_FIX, GPS_SIGNAL_LOST), statusList);
|
||||
|
||||
subject.onNewTrackPoint(new TrackPoint(ok, Instant.now()));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_FIX, GPS_SIGNAL_LOST, GPS_SIGNAL_FIX), statusList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartSignalLostByTimer() throws InterruptedException {
|
||||
ArrayList<GpsStatusValue> statusList = new ArrayList<>();
|
||||
|
||||
final HandlerThread handlerThread = new HandlerThread("solution!");
|
||||
handlerThread.start();
|
||||
|
||||
// given
|
||||
GpsStatusManager subject = new GpsStatusManager(context, statusList::add, new Handler(handlerThread.getLooper()));
|
||||
subject.onRecordingDistanceChanged(Distance.of(10));
|
||||
subject.onMinRecordingIntervalChanged(GpsStatusManager.SIGNAL_LOST_THRESHOLD.multipliedBy(-1).plus(Duration.ofMillis(10)));
|
||||
|
||||
// when / then
|
||||
subject.start();
|
||||
Thread.sleep(100);
|
||||
assertEquals(List.of(GPS_ENABLED), statusList);
|
||||
|
||||
subject.onNewTrackPoint(new TrackPoint(ok, Instant.now()));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_FIX), statusList);
|
||||
|
||||
Thread.sleep(100);
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_FIX, GPS_SIGNAL_LOST), statusList);
|
||||
|
||||
subject.onNewTrackPoint(new TrackPoint(badFix, Instant.now()));
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_FIX, GPS_SIGNAL_LOST, GPS_SIGNAL_BAD), statusList);
|
||||
|
||||
Thread.sleep(100);
|
||||
assertEquals(List.of(GPS_ENABLED, GPS_SIGNAL_FIX, GPS_SIGNAL_LOST, GPS_SIGNAL_BAD, GPS_SIGNAL_LOST), statusList);
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TrackPointCreatorTest {
|
||||
|
||||
@Mock
|
||||
private Context context;
|
||||
|
||||
@Mock
|
||||
private TrackPointCreator.Callback server;
|
||||
|
||||
@Mock
|
||||
private GPSManager locationHandler;
|
||||
|
||||
private TrackPointCreator subject;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject = new TrackPointCreator(locationHandler, server);
|
||||
subject.start(context, null);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
subject.stop();
|
||||
}
|
||||
|
||||
@Ignore("ServiceExecutor disabled for #822")
|
||||
@Test
|
||||
public void sendTrackPoint() throws InterruptedException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, null);
|
||||
Distance horizontalAccuracyThreshold = Distance.of(50);
|
||||
|
||||
// when
|
||||
subject.onNewTrackPoint(trackPoint);
|
||||
|
||||
// then
|
||||
Thread.sleep(10); // Wait for executor service
|
||||
verify(server).newTrackPoint(trackPoint, horizontalAccuracyThreshold);
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ public class DescriptionGeneratorTest {
|
||||
stats.setTotalAltitudeLoss(6000f);
|
||||
stats.setStartTime(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
track.setActivityType("hiking");
|
||||
String expected = //"Created by"
|
||||
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
|
||||
+ "Name: -<br>"
|
||||
@@ -118,7 +118,7 @@ public class DescriptionGeneratorTest {
|
||||
stats.setTotalAltitudeLoss(6000f);
|
||||
stats.setStartTime(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
track.setActivityType("hiking");
|
||||
String expected = //"Created by"
|
||||
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
|
||||
+ "Name: -<br>"
|
||||
|
||||
@@ -78,7 +78,7 @@ public class TrackStatisticsUpdaterTest {
|
||||
|
||||
// when
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(subject::addTrackPoint);
|
||||
data.trackPoints().forEach(subject::addTrackPoint);
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = subject.getTrackStatistics();
|
||||
|
||||
+3
-3
@@ -41,7 +41,7 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
Track track = new Track();
|
||||
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||
track.setCategory(category);
|
||||
track.setActivityType(category);
|
||||
track.setTrackStatistics(statistics);
|
||||
return track;
|
||||
}
|
||||
@@ -243,8 +243,8 @@ public class AggregatedStatisticsTest {
|
||||
// Check order
|
||||
|
||||
{
|
||||
assertEquals(biking, aggregatedStatistics.getItem(0).getCategory());
|
||||
assertEquals(driving, aggregatedStatistics.getItem(3).getCategory());
|
||||
assertEquals(biking, aggregatedStatistics.getItem(0).getActivityType());
|
||||
assertEquals(driving, aggregatedStatistics.getItem(3).getActivityType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -51,7 +51,7 @@ public class EspressoAggregatedFilterTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
Pair<Track, List<TrackPoint>> pair = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 20);
|
||||
pair.first.setCategory(CATEGORY);
|
||||
pair.first.setActivityType(CATEGORY);
|
||||
TestDataUtil.insertTrackWithLocations(new ContentProviderUtils(ApplicationProvider.getApplicationContext()), pair.first, pair.second);
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ public class EspressoAggregatedFilterTest {
|
||||
public void espressoAggregatedFilterTest() {
|
||||
// open AggregatedStatisticsActivity through toolbar's menu item
|
||||
onView(ViewMatchers.withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.track_list_aggregated_stats))));
|
||||
onView(withId(R.id.track_list_aggregated_stats)).perform(click());
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_stats_button))));
|
||||
onView(withId(R.id.aggregated_stats_button)).perform(click());
|
||||
|
||||
// open FilterDialogFragment through toolbar's menu item
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_statistics_24dp"
|
||||
android:shortcutId="stats"
|
||||
android:shortcutLongLabel="@string/menu_aggregated_statistics"
|
||||
android:shortcutShortLabel="@string/menu_aggregated_statistics">
|
||||
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:targetClass="de.dennisguse.opentracks.ui.aggregatedStatistics.AggregatedStatisticsActivity"
|
||||
android:targetPackage="de.dennisguse.opentracks.debug" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
@@ -99,6 +99,11 @@ limitations under the License.
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ShowErrorActivity extends AbstractActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
viewBinding.textViewError.setText(getIntent().getStringExtra(EXTRA_ERROR_TEXT));
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(createErrorTitle());
|
||||
viewBinding.showErrorToolbar.setTitle(createErrorTitle());
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
viewBinding.fields.trackEditName.setText(track.getName());
|
||||
|
||||
viewBinding.fields.trackEditActivityType.setText(track.getCategory());
|
||||
viewBinding.fields.trackEditActivityType.setText(track.getActivityType());
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
|
||||
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
|
||||
@@ -109,7 +109,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
viewBinding.trackEditCancel.setOnClickListener(v -> finish());
|
||||
viewBinding.trackEditCancel.setVisibility(View.VISIBLE);
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.menu_edit));
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
@@ -42,12 +43,12 @@ import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
@@ -82,7 +83,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||
|
||||
// The following are setFrequency in onCreate
|
||||
// The following are set in onCreate
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private ResourceCursorAdapter resourceCursorAdapter;
|
||||
|
||||
@@ -136,7 +137,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
// Menu items
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem startGpsMenuItem;
|
||||
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||
service.getRecordingStatusObservable()
|
||||
@@ -164,6 +164,25 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
|
||||
viewBinding.aggregatedStatsButton.setOnClickListener((view) -> startActivity(IntentUtils.newIntent(this, AggregatedStatisticsActivity.class)));
|
||||
viewBinding.sensorStartButton.setOnClickListener((view) -> {
|
||||
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
} else {
|
||||
// Invoke trackRecordingService
|
||||
if (!gpsStatusValue.isGpsStarted()) {
|
||||
trackRecordingServiceConnection.startAndBindWithCallback(this);
|
||||
} else {
|
||||
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
trackRecordingService.stopSensorsAndShutdown(); //TODO Handle this in TrackRecordingServiceConnection
|
||||
}
|
||||
trackRecordingServiceConnection.unbindAndStop(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackIdId) -> {
|
||||
Track.Id trackId = new Track.Id(trackIdId);
|
||||
@@ -193,7 +212,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
|
||||
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
|
||||
int startTimeOffsetIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME_OFFSET);
|
||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||
int activityTypeIndex = cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE);
|
||||
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
|
||||
|
||||
@@ -209,13 +228,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
int markerCount = cursor.getInt(markerCountIndex);
|
||||
long startTime = cursor.getLong(startTimeIndex);
|
||||
int startTimeOffset = cursor.getInt(startTimeOffsetIndex);
|
||||
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
||||
String activityType = icon != null && !icon.equals("") ? null : cursor.getString(activityTypeIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording,
|
||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneOffset.ofTotalSeconds(startTimeOffset)),
|
||||
category, description, false);
|
||||
activityType, description, false);
|
||||
}
|
||||
};
|
||||
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
||||
@@ -253,7 +272,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
return true;
|
||||
});
|
||||
|
||||
setSupportActionBar(viewBinding.bottomAppBar);
|
||||
setSupportActionBar(viewBinding.trackListToolbar);
|
||||
|
||||
loadData(getIntent());
|
||||
}
|
||||
@@ -308,21 +327,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
getMenuInflater().inflate(R.menu.track_list, menu);
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||
|
||||
searchView.findViewById(androidx.appcompat.R.id.search_edit_frame).setPadding(0, 0, 48, 0);
|
||||
|
||||
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(androidx.appcompat.R.id.search_src_text);
|
||||
searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
|
||||
searchView.setOnCloseListener(() -> {
|
||||
searchView.clearFocus();
|
||||
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
return true;
|
||||
});
|
||||
|
||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -339,34 +344,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.track_list_start_gps) {
|
||||
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
} else {
|
||||
// Invoke trackRecordingService
|
||||
if (!gpsStatusValue.isGpsStarted()) {
|
||||
trackRecordingServiceConnection.startAndBindWithCallback(this);
|
||||
} else {
|
||||
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
trackRecordingService.stopSensorsAndShutdown(); //TODO Handle this in TrackRecordingServiceConnection
|
||||
}
|
||||
trackRecordingServiceConnection.unbindAndStop(this);
|
||||
}
|
||||
|
||||
// Update menu after starting or stopping gps
|
||||
this.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_aggregated_stats) {
|
||||
startActivity(IntentUtils.newIntent(this, AggregatedStatisticsActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_markers) {
|
||||
startActivity(IntentUtils.newIntent(this, MarkerListActivity.class));
|
||||
return true;
|
||||
@@ -460,14 +437,12 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
*/
|
||||
//TODO Check if if can be avoided to call this outside of onGpsStatusChanged()
|
||||
private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) {
|
||||
if (startGpsMenuItem != null) {
|
||||
startGpsMenuItem.setVisible(!isRecording);
|
||||
if (!isRecording) {
|
||||
startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps);
|
||||
startGpsMenuItem.setIcon(isGpsStarted ? gpsStatusValue.icon : R.drawable.ic_gps_off_24dp);
|
||||
if (startGpsMenuItem.getIcon() instanceof AnimatedVectorDrawable) {
|
||||
((AnimatedVectorDrawable) startGpsMenuItem.getIcon()).start();
|
||||
}
|
||||
MaterialButton startGpsMenuItem = viewBinding.sensorStartButton;
|
||||
startGpsMenuItem.setVisibility(!isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (!isRecording) {
|
||||
startGpsMenuItem.setIcon(AppCompatResources.getDrawable(this, isGpsStarted ? gpsStatusValue.icon : R.drawable.ic_gps_off_24dp));
|
||||
if (startGpsMenuItem.getIcon() instanceof AnimatedVectorDrawable) {
|
||||
((AnimatedVectorDrawable) startGpsMenuItem.getIcon()).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,13 +484,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_aggregated_stats) {
|
||||
Intent intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class)
|
||||
.putParcelableArrayListExtra(AggregatedStatisticsActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_select_all) {
|
||||
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||
viewBinding.trackList.setItemChecked(i, true);
|
||||
@@ -537,11 +505,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
public void setSearch(String searchQuery) {
|
||||
this.searchQuery = searchQuery;
|
||||
restart();
|
||||
if (searchQuery != null) {
|
||||
setTitle(searchQuery);
|
||||
} else {
|
||||
setTitle(R.string.app_name);
|
||||
}
|
||||
viewBinding.trackListToolbar.setTitle(searchQuery == null ? getString(R.string.app_name) : searchQuery);
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
@@ -552,7 +516,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||
final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
|
||||
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME, TracksColumns.STARTTIME_OFFSET,
|
||||
TracksColumns.DESCRIPTION, TracksColumns.ACTIVITY_TYPE, TracksColumns.STARTTIME, TracksColumns.STARTTIME_OFFSET,
|
||||
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON, TracksColumns.MARKER_COUNT};
|
||||
|
||||
final String sortOrder = TracksColumns.STARTTIME + " DESC";
|
||||
@@ -562,7 +526,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
} else {
|
||||
final String SEARCH_QUERY = TracksColumns.NAME + " LIKE ? OR " +
|
||||
TracksColumns.DESCRIPTION + " LIKE ? OR " +
|
||||
TracksColumns.CATEGORY + " LIKE ?";
|
||||
TracksColumns.ACTIVITY_TYPE + " LIKE ?";
|
||||
final String[] selectionArgs = new String[]{"%" + searchQuery + "%", "%" + searchQuery + "%", "%" + searchQuery + "%"};
|
||||
return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, SEARCH_QUERY, selectionArgs, sortOrder);
|
||||
}
|
||||
@@ -586,7 +550,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
private void setFloatButton() {
|
||||
viewBinding.trackListFabAction.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_baseline_stop_24 : R.drawable.ic_baseline_record_24);
|
||||
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks_secondary_color : R.color.red_dark));
|
||||
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks : R.color.red_dark));
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||
|
||||
@@ -103,7 +103,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(track != null ? track.getName() : "");
|
||||
viewBinding.bottomAppBarLayout.bottomAppBar.replaceMenu(R.menu.track_detail);
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
|
||||
|
||||
@@ -133,8 +133,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24);
|
||||
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color));
|
||||
viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks_secondary_color));
|
||||
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks));
|
||||
viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks));
|
||||
viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> {
|
||||
ActivityUtils.vibrate(this, 1000);
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
||||
@@ -319,8 +319,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
@Override
|
||||
public void onChooseActivityTypeDone(String iconValue) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
String category = getString(TrackIconUtils.getIconActivityType(iconValue));
|
||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||
String activityType = getString(TrackIconUtils.getIconActivityType(iconValue));
|
||||
TrackUtils.updateTrack(this, track, null, activityType, null, contentProviderUtils);
|
||||
}
|
||||
|
||||
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
|
||||
|
||||
@@ -50,7 +50,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
|
||||
viewBinding.trackEditName.setText(track.getName());
|
||||
|
||||
viewBinding.trackEditActivityType.setText(track.getCategory());
|
||||
viewBinding.trackEditActivityType.setText(track.getActivityType());
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
|
||||
viewBinding.trackEditActivityType.setAdapter(adapter);
|
||||
@@ -74,7 +74,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
{
|
||||
Pair<String, String> parts = SpeedFormatter.Builder()
|
||||
.setUnit(PreferencesUtils.getUnitSystem())
|
||||
.setReportSpeedOrPace(PreferencesUtils.isReportSpeed(track.getCategory()))
|
||||
.setReportSpeedOrPace(PreferencesUtils.isReportSpeed(track.getActivityType()))
|
||||
.build(this)
|
||||
.getSpeedParts(track.getTrackStatistics().getAverageMovingSpeed());
|
||||
viewBinding.speed.setText(parts.first);
|
||||
|
||||
@@ -164,7 +164,7 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
category = track.getCategory();
|
||||
category = track.getActivityType();
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(category);
|
||||
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
|
||||
viewBinding.chartView.setReportSpeed(reportSpeed);
|
||||
|
||||
@@ -212,7 +212,7 @@ abstract class ChartValueSeries {
|
||||
int getTitleId(UnitSystem unitSystem) {
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> metricTitleId;
|
||||
case IMPERIAL -> imperialTitleId;
|
||||
case IMPERIAL_FEET, IMPERIAL_METER -> imperialTitleId;
|
||||
case NAUTICAL_IMPERIAL -> nauticalTitleId;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -735,7 +735,7 @@ public class ChartView extends View {
|
||||
if (chartByDistance) {
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> context.getString(R.string.unit_kilometer);
|
||||
case IMPERIAL -> context.getString(R.string.unit_mile);
|
||||
case IMPERIAL_FEET, IMPERIAL_METER -> context.getString(R.string.unit_mile);
|
||||
case NAUTICAL_IMPERIAL -> context.getString(R.string.unit_nautical_mile);
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -100,7 +100,7 @@ public class ContentProviderUtils {
|
||||
int uuidIndex = cursor.getColumnIndexOrThrow(TracksColumns.UUID);
|
||||
int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE);
|
||||
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
|
||||
int startTimeOffsetIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME_OFFSET);
|
||||
int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME);
|
||||
@@ -129,7 +129,7 @@ public class ContentProviderUtils {
|
||||
track.setDescription(cursor.getString(descriptionIndex));
|
||||
}
|
||||
if (!cursor.isNull(categoryIndex)) {
|
||||
track.setCategory(cursor.getString(categoryIndex));
|
||||
track.setActivityType(cursor.getString(categoryIndex));
|
||||
}
|
||||
|
||||
if (!cursor.isNull(startTimeIndex)) {
|
||||
@@ -290,7 +290,7 @@ public class ContentProviderUtils {
|
||||
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
|
||||
values.put(TracksColumns.NAME, track.getName());
|
||||
values.put(TracksColumns.DESCRIPTION, track.getDescription());
|
||||
values.put(TracksColumns.CATEGORY, track.getCategory());
|
||||
values.put(TracksColumns.ACTIVITY_TYPE, track.getActivityType());
|
||||
values.put(TracksColumns.STARTTIME_OFFSET, track.getZoneOffset().getTotalSeconds());
|
||||
if (trackStatistics.getStartTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime().toEpochMilli());
|
||||
@@ -403,7 +403,7 @@ public class ContentProviderUtils {
|
||||
public void deleteMarker(Context context, Marker.Id markerId) {
|
||||
final Marker marker = getMarker(markerId);
|
||||
deleteMarkerPhoto(context, marker);
|
||||
contentResolver.delete(MarkerColumns.CONTENT_URI, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.getId())});
|
||||
contentResolver.delete(MarkerColumns.CONTENT_URI, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.id())});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,7 +422,7 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
public Marker getMarker(@NonNull Marker.Id markerId) {
|
||||
try (Cursor cursor = getMarkerCursor(null, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.getId())}, MarkerColumns._ID, 1)) {
|
||||
try (Cursor cursor = getMarkerCursor(null, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.id())}, MarkerColumns._ID, 1)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return createMarker(cursor);
|
||||
}
|
||||
@@ -442,7 +442,7 @@ public class ContentProviderUtils {
|
||||
String[] selectionArgs;
|
||||
if (minMarkerId != null) {
|
||||
selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?";
|
||||
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minMarkerId.getId())};
|
||||
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minMarkerId.id())};
|
||||
} else {
|
||||
selection = MarkerColumns.TRACKID + "=?";
|
||||
selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||
@@ -495,7 +495,7 @@ public class ContentProviderUtils {
|
||||
if (!updateMarker.hasPhoto()) {
|
||||
deleteMarkerPhoto(context, savedMarker);
|
||||
}
|
||||
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.getId().getId())});
|
||||
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.getId().id())});
|
||||
return rows == 1;
|
||||
}
|
||||
|
||||
@@ -503,7 +503,7 @@ public class ContentProviderUtils {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
if (marker.getId() != null) {
|
||||
values.put(MarkerColumns._ID, marker.getId().getId());
|
||||
values.put(MarkerColumns._ID, marker.getId().id());
|
||||
}
|
||||
values.put(MarkerColumns.NAME, marker.getName());
|
||||
values.put(MarkerColumns.DESCRIPTION, marker.getDescription());
|
||||
|
||||
@@ -30,9 +30,9 @@ public class TrackSelection implements ContentProviderUtils.ContentProviderSelec
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrackSelection addCategory(String category) {
|
||||
if (!this.categories.contains(category)) {
|
||||
this.categories.add(category);
|
||||
public TrackSelection addActivityType(String activityType) {
|
||||
if (!this.categories.contains(activityType)) {
|
||||
this.categories.add(activityType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -53,7 +53,7 @@ public class TrackSelection implements ContentProviderUtils.ContentProviderSelec
|
||||
}
|
||||
if (!categories.isEmpty()) {
|
||||
selection += selection.isEmpty() ? "" : " AND ";
|
||||
selection += String.format(TracksColumns.CATEGORY + " IN (%s)", TextUtils.join(",", Collections.nCopies(categories.size(), "?")));
|
||||
selection += String.format(TracksColumns.ACTIVITY_TYPE + " IN (%s)", TextUtils.join(",", Collections.nCopies(categories.size(), "?")));
|
||||
}
|
||||
if (from != null && to != null) {
|
||||
selection += selection.isEmpty() ? "" : " AND ";
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record AtmosphericPressure(float value) {
|
||||
|
||||
public static AtmosphericPressure ofPA(float value_Pa) {
|
||||
return new AtmosphericPressure(value_Pa * 100);
|
||||
}
|
||||
|
||||
public static AtmosphericPressure ofHPA(float value_hPa) {
|
||||
return new AtmosphericPressure(value_hPa);
|
||||
}
|
||||
|
||||
public float getPA() {
|
||||
return value * 100;
|
||||
}
|
||||
|
||||
public float getHPA() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,8 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
public class BatteryLevel {
|
||||
public record BatteryLevel(int percentage) {
|
||||
|
||||
public static BatteryLevel of(int percentage) {
|
||||
return new BatteryLevel(percentage);
|
||||
}
|
||||
|
||||
private final int percentage;
|
||||
|
||||
private BatteryLevel(int percentage) {
|
||||
this.percentage = percentage;
|
||||
}
|
||||
|
||||
public int getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Cadence {
|
||||
public record Cadence(float value_rpm) {
|
||||
|
||||
public static Cadence of(float value, Duration duration) {
|
||||
if (duration.isZero()) {
|
||||
@@ -23,34 +23,7 @@ public class Cadence {
|
||||
return of(0.0f);
|
||||
}
|
||||
|
||||
private final float value_rpm;
|
||||
|
||||
private Cadence(float value) {
|
||||
this.value_rpm = value;
|
||||
}
|
||||
|
||||
public float getRPM() {
|
||||
return value_rpm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Cadence cadence = (Cadence) o;
|
||||
return Float.compare(cadence.value_rpm, value_rpm) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value_rpm);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cadence{" +
|
||||
"value=" + value_rpm + " rpm" +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,13 @@ package de.dennisguse.opentracks.data.models;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
public class Distance {
|
||||
public record Distance(double distance_m) {
|
||||
|
||||
public static Distance of(double distance_m) {
|
||||
return new Distance(distance_m);
|
||||
}
|
||||
|
||||
public static Distance of(Double distance_m) {
|
||||
if (distance_m == null) {
|
||||
return Distance.of(Double.NaN);
|
||||
@@ -20,11 +17,9 @@ public class Distance {
|
||||
return Distance.of((double) distance_m);
|
||||
}
|
||||
}
|
||||
|
||||
public static Distance of(String distance_m) {
|
||||
return of(Float.parseFloat(distance_m));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Distance ofOrNull(Double distance_m) {
|
||||
if (distance_m == null) {
|
||||
@@ -60,17 +55,11 @@ public class Distance {
|
||||
public static Distance one(UnitSystem unitSystem) {
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> Distance.ofKilometer(1);
|
||||
case IMPERIAL -> Distance.ofMile(1);
|
||||
case IMPERIAL_FEET, IMPERIAL_METER -> Distance.ofMile(1);
|
||||
case NAUTICAL_IMPERIAL -> Distance.ofNauticalMile(1);
|
||||
};
|
||||
}
|
||||
|
||||
private final double distance_m;
|
||||
|
||||
private Distance(double distance_m) {
|
||||
this.distance_m = distance_m;
|
||||
}
|
||||
|
||||
public Distance plus(@NonNull Distance distance) {
|
||||
return new Distance(distance_m + distance.distance_m);
|
||||
}
|
||||
@@ -130,39 +119,18 @@ public class Distance {
|
||||
public double toKM_Miles(UnitSystem unitSystem) {
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> toKM();
|
||||
case IMPERIAL -> toMI();
|
||||
case IMPERIAL_FEET, IMPERIAL_METER -> toMI();
|
||||
case NAUTICAL_IMPERIAL -> toNauticalMiles();
|
||||
};
|
||||
}
|
||||
|
||||
public double toM_FT(UnitSystem unitSystem) {
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> toM();
|
||||
case NAUTICAL_IMPERIAL, IMPERIAL -> toFT();
|
||||
case METRIC, IMPERIAL_METER -> toM();
|
||||
case NAUTICAL_IMPERIAL, IMPERIAL_FEET -> toFT();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Distance distance = (Distance) o;
|
||||
return Double.compare(distance.distance_m, distance_m) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(distance_m);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Distance{" +
|
||||
"distance_m=" + distance_m +
|
||||
'}';
|
||||
}
|
||||
|
||||
// multiplication factors for conversion
|
||||
private static final double KM_TO_M = 1000.0;
|
||||
private static final double M_TO_KM = 1 / KM_TO_M;
|
||||
|
||||
@@ -47,8 +47,9 @@ public class DistanceFormatter {
|
||||
if (distance.isInvalid()) {
|
||||
String valueUnknown = resources.getString(R.string.value_unknown);
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL ->
|
||||
case METRIC, IMPERIAL_METER ->
|
||||
new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
|
||||
case IMPERIAL_FEET, NAUTICAL_IMPERIAL ->
|
||||
new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
|
||||
};
|
||||
}
|
||||
@@ -61,13 +62,20 @@ public class DistanceFormatter {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
|
||||
}
|
||||
}
|
||||
case IMPERIAL -> {
|
||||
case IMPERIAL_FEET -> {
|
||||
if (distance.greaterThan(Distance.ofMile(threshold))) {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
|
||||
} else {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
||||
}
|
||||
}
|
||||
case IMPERIAL_METER -> {
|
||||
if (distance.greaterThan(Distance.ofMile(threshold))) {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
|
||||
} else {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
|
||||
}
|
||||
}
|
||||
case NAUTICAL_IMPERIAL -> {
|
||||
if (distance.greaterThan(Distance.ofNauticalMile(threshold))) {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
|
||||
|
||||
@@ -4,40 +4,13 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class HeartRate {
|
||||
public record HeartRate(float value) {
|
||||
|
||||
public static HeartRate of(float value) {
|
||||
return new HeartRate(value);
|
||||
}
|
||||
|
||||
private final float value;
|
||||
|
||||
private HeartRate(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public float getBPM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HeartRate heartRate = (HeartRate) o;
|
||||
return Float.compare(heartRate.value, value) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HeartRate{" +
|
||||
"value=" + value + " bpm" +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
@@ -267,31 +266,7 @@ public final class Marker {
|
||||
return photoUrl != null && !"".equals(photoUrl);
|
||||
}
|
||||
|
||||
public static class Id implements Parcelable {
|
||||
|
||||
private final long id;
|
||||
|
||||
public Id(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
//TOOD Limit visibility to TrackRecordingService / ContentProvider
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Id id1 = (Id) o;
|
||||
return id == id1.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
public record Id(long id) implements Parcelable {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -309,7 +284,7 @@ public final class Marker {
|
||||
parcel.writeLong(id);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Id> CREATOR = new Parcelable.Creator<>() {
|
||||
public static final Creator<Id> CREATOR = new Creator<>() {
|
||||
public Id createFromParcel(Parcel in) {
|
||||
return new Id(in.readLong());
|
||||
}
|
||||
|
||||
@@ -4,40 +4,13 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Power {
|
||||
public record Power(float value) {
|
||||
|
||||
public static Power of(float value) {
|
||||
return new Power(value);
|
||||
}
|
||||
|
||||
private final float value;
|
||||
|
||||
private Power(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public float getW() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Power power = (Power) o;
|
||||
return Float.compare(power.value, value) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Power{" +
|
||||
"value=" + value + " W" +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
public class Speed {
|
||||
public record Speed(double speed_mps) {
|
||||
|
||||
public static Speed of(Distance distance, Duration duration) {
|
||||
if (duration.isZero()) {
|
||||
@@ -47,12 +44,6 @@ public class Speed {
|
||||
return Speed.of(Math.abs(speed1.speed_mps - speed2.speed_mps));
|
||||
}
|
||||
|
||||
private final double speed_mps;
|
||||
|
||||
private Speed(double speed_mps) {
|
||||
this.speed_mps = speed_mps;
|
||||
}
|
||||
|
||||
public Speed mul(double factor) {
|
||||
return new Speed(factor * speed_mps);
|
||||
}
|
||||
@@ -117,29 +108,8 @@ public class Speed {
|
||||
public double to(UnitSystem unitSystem) {
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> toKMH();
|
||||
case IMPERIAL -> toMPH();
|
||||
case IMPERIAL_FEET, IMPERIAL_METER -> toMPH();
|
||||
case NAUTICAL_IMPERIAL -> toKnots();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Speed speed = (Speed) o;
|
||||
return Double.compare(speed.speed_mps, speed_mps) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(speed_mps);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Speed{" +
|
||||
"speed_mps=" + speed_mps +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class SpeedFormatter {
|
||||
int unitId = switch (unitSystem) {
|
||||
case METRIC ->
|
||||
reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
|
||||
case IMPERIAL ->
|
||||
case IMPERIAL_FEET, IMPERIAL_METER ->
|
||||
reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
||||
case NAUTICAL_IMPERIAL ->
|
||||
reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
|
||||
|
||||
@@ -44,7 +44,7 @@ public class Track {
|
||||
|
||||
private String name = "";
|
||||
private String description = "";
|
||||
private String category = "";
|
||||
private String activityType = "";
|
||||
|
||||
private String icon = "";
|
||||
|
||||
@@ -97,12 +97,12 @@ public class Track {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
public String getActivityType() {
|
||||
return activityType;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
public void setActivityType(String activityType) {
|
||||
this.activityType = activityType;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
|
||||
@@ -39,7 +39,7 @@ public interface TracksColumns extends BaseColumns {
|
||||
String UUID = "uuid"; // identifier to make tracks globally unique (prevent re-import)
|
||||
String NAME = "name"; // track name
|
||||
String DESCRIPTION = "description"; // track description
|
||||
String CATEGORY = "category"; // track activity type
|
||||
String ACTIVITY_TYPE = "category"; // track activity type
|
||||
String STARTTIME = "starttime"; // track start time
|
||||
String STARTTIME_OFFSET = "starttime_offset"; // in plus/minus in seconds
|
||||
String STOPTIME = "stoptime"; // track stop time
|
||||
@@ -63,7 +63,7 @@ public interface TracksColumns extends BaseColumns {
|
||||
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ NAME + " TEXT, "
|
||||
+ DESCRIPTION + " TEXT, "
|
||||
+ CATEGORY + " TEXT, "
|
||||
+ ACTIVITY_TYPE + " TEXT, "
|
||||
+ STARTTIME + " INTEGER, "
|
||||
+ STOPTIME + " INTEGER, "
|
||||
+ NUMPOINTS + " INTEGER, "
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ class ChooseActivityTypeImageAdapter extends BaseAdapter {
|
||||
}
|
||||
|
||||
if (position == selected) {
|
||||
imageView.setBackgroundColor(ContextCompat.getColor(parent.getContext(), R.color.opentracks_secondary_color));
|
||||
imageView.setBackgroundColor(ContextCompat.getColor(parent.getContext(), R.color.opentracks));
|
||||
} else {
|
||||
imageView.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_rate_key, key) && track != null) {
|
||||
updateUInecessary = true;
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(track.getActivityType());
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
@@ -155,7 +155,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
boolean prefsChanged = this.track == null || (!this.track.getCategory().equals(track.getCategory()));
|
||||
boolean prefsChanged = this.track == null || (!this.track.getActivityType().equals(track.getActivityType()));
|
||||
this.track = track;
|
||||
if (prefsChanged) {
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
|
||||
@@ -191,7 +191,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set activity type
|
||||
{
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), track.getCategory());
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), track.getActivityType());
|
||||
viewBinding.statsActivityTypeIcon.setImageDrawable(ContextCompat.getDrawable(getContext(), TrackIconUtils.getIconDrawable(trackIconValue)));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ public class TrackFilenameGenerator {
|
||||
|
||||
public static final String UUID_KEY = "{uuid}";
|
||||
public static final String TRACKNAME_KEY = "{name}";
|
||||
public static final String CATEGORY_KEY = "{category}";
|
||||
public static final String ACTIVITY_TYPE_KEY = "{category}";
|
||||
public static final String STARTTIME_TIME_KEY = "{time}";
|
||||
public static final String STARTTIME_DATE_KEY = "{date}";
|
||||
|
||||
public static String getAllOptions() {
|
||||
return Stream.of(UUID_KEY, TRACKNAME_KEY, CATEGORY_KEY, STARTTIME_TIME_KEY, STARTTIME_DATE_KEY)
|
||||
return Stream.of(UUID_KEY, TRACKNAME_KEY, ACTIVITY_TYPE_KEY, STARTTIME_TIME_KEY, STARTTIME_DATE_KEY)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class TrackFilenameGenerator {
|
||||
|
||||
values.put(UUID_KEY, track.getUuid().toString().substring(0, 8));
|
||||
values.put(TRACKNAME_KEY, track.getName());
|
||||
values.put(CATEGORY_KEY, track.getCategory());
|
||||
values.put(ACTIVITY_TYPE_KEY, track.getActivityType());
|
||||
values.put(STARTTIME_TIME_KEY, track.getStartTime().toLocalTime().toString());
|
||||
values.put(STARTTIME_DATE_KEY, track.getStartTime().toLocalDate().toString());
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
private enum ConflictResolutionStrategy {
|
||||
CONFLICT_NONE,
|
||||
CONFLICT_OVERWRITE,
|
||||
CONFLICT_SKIP;
|
||||
CONFLICT_SKIP
|
||||
}
|
||||
|
||||
private TrackFileFormat trackFileFormat;
|
||||
@@ -187,7 +187,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
nextExport(null);
|
||||
}
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.export_progress_message, directoryDisplayName));
|
||||
viewBinding.exportActivityToolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
|
||||
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
|
||||
public class ExportService extends JobIntentService {
|
||||
|
||||
-2
@@ -6,8 +6,6 @@ import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
|
||||
/**
|
||||
* Create a new ResultReceive to receive results.
|
||||
* Your {@link #onReceiveResult} method will be called from the thread running <var>handler</var> if given, or from an arbitrary thread if null.
|
||||
|
||||
@@ -268,7 +268,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<trk>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(track.getCategory()) + "</type>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(track.getActivityType()) + "</type>");
|
||||
|
||||
printWriter.println("<extensions>");
|
||||
printWriter.println("<topografix:color>c0c0c0</topografix:color>");
|
||||
|
||||
@@ -59,7 +59,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
private static final String TRACK_STYLE = "track";
|
||||
private static final String SCHEMA_ID = "schema";
|
||||
|
||||
public static final String EXTENDED_DATA_TYPE_CATEGORY = "type";
|
||||
public static final String EXTENDED_DATA_TYPE_ACTIVITYTYPE = "type";
|
||||
|
||||
public static final String EXTENDED_DATA_TYPE_SPEED = "speed";
|
||||
public static final String EXTENDED_DATA_TYPE_DISTANCE = "distance";
|
||||
@@ -311,7 +311,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
|
||||
|
||||
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
|
||||
writeCategory(track.getCategory());
|
||||
writeActivityType(track.getActivityType());
|
||||
printWriter.println("<MultiTrack>");
|
||||
printWriter.println("<altitudeMode>absolute</altitudeMode>");
|
||||
printWriter.println("<interpolate>1</interpolate>");
|
||||
@@ -426,19 +426,19 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
/**
|
||||
* Writes a placemark.
|
||||
*
|
||||
* @param name the name
|
||||
* @param category the category
|
||||
* @param description the description
|
||||
* @param location the location
|
||||
* @param name the name
|
||||
* @param activityType the activityType
|
||||
* @param description the description
|
||||
* @param location the location
|
||||
*/
|
||||
private void writePlacemark(String name, String category, String description, Location location, ZoneOffset zoneOffset) {
|
||||
private void writePlacemark(String name, String activityType, String description, Location location, ZoneOffset zoneOffset) {
|
||||
if (location != null) {
|
||||
printWriter.println("<Placemark>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, location) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + KMLTrackExporter.MARKER_STYLE + "</styleUrl>");
|
||||
writeCategory(category);
|
||||
writeActivityType(activityType);
|
||||
printWriter.println("<Point>");
|
||||
printWriter.println("<coordinates>" + getCoordinates(location, ",") + "</coordinates>");
|
||||
printWriter.println("</Point>");
|
||||
@@ -459,7 +459,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
printWriter.println("</Camera>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getLocation()) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + MARKER_STYLE + "</styleUrl>");
|
||||
writeCategory(marker.getCategory());
|
||||
writeActivityType(marker.getCategory());
|
||||
|
||||
if (exportPhotos) {
|
||||
printWriter.println("<Icon><href>" + KmzTrackExporter.buildKmzImageFilePath(marker) + "</href></Icon>");
|
||||
@@ -514,23 +514,15 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the category.
|
||||
*
|
||||
* @param category the category
|
||||
*/
|
||||
private void writeCategory(String category) {
|
||||
if (category == null || category.equals("")) {
|
||||
private void writeActivityType(String actiivtyType) {
|
||||
if (actiivtyType == null || actiivtyType.equals("")) {
|
||||
return;
|
||||
}
|
||||
printWriter.println("<ExtendedData>");
|
||||
printWriter.println("<Data name=\"" + EXTENDED_DATA_TYPE_CATEGORY + "\"><value>" + StringUtils.formatCData(category) + "</value></Data>");
|
||||
printWriter.println("<Data name=\"" + EXTENDED_DATA_TYPE_ACTIVITYTYPE + "\"><value>" + StringUtils.formatCData(actiivtyType) + "</value></Data>");
|
||||
printWriter.println("</ExtendedData>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the track style.
|
||||
*/
|
||||
private void writeTrackStyle() {
|
||||
printWriter.println("<Style id=\"" + TRACK_STYLE + "\">");
|
||||
printWriter.println("<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>");
|
||||
|
||||
@@ -141,6 +141,6 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
public static String buildKmzImageFilePath(Marker marker) {
|
||||
String ext = FileUtils.getExtension(marker.getPhotoUrl());
|
||||
ext = ext == null ? "" : "." + ext;
|
||||
return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(marker.getId().getId() + ext);
|
||||
return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(marker.getId().id() + ext);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
private String category;
|
||||
private String activityType;
|
||||
private String latitude;
|
||||
private String longitude;
|
||||
private String altitude;
|
||||
@@ -159,7 +159,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
case TAG_GPX -> onFileEnd();
|
||||
case TAG_MARKER -> onMarkerEnd();
|
||||
case TAG_TRACK -> {
|
||||
trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset);
|
||||
trackImporter.setTrack(context, name, uuid, description, activityType, null, zoneOffset);
|
||||
zoneOffset = null;
|
||||
}
|
||||
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||
@@ -176,7 +176,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
case TAG_TYPE -> {
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
activityType = content.trim();
|
||||
}
|
||||
}
|
||||
case TAG_TIME -> {
|
||||
@@ -385,7 +385,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
private void onMarkerStart(Attributes attributes) {
|
||||
name = null;
|
||||
description = null;
|
||||
category = null;
|
||||
activityType = null;
|
||||
photoUrl = null;
|
||||
latitude = attributes.getValue(ATTRIBUTE_LAT);
|
||||
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||
@@ -415,8 +415,8 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
if (description != null) {
|
||||
marker.setDescription(description);
|
||||
}
|
||||
if (category != null) {
|
||||
marker.setCategory(category);
|
||||
if (activityType != null) {
|
||||
marker.setCategory(activityType);
|
||||
}
|
||||
|
||||
if (photoUrl != null) {
|
||||
|
||||
@@ -112,7 +112,7 @@ public class ImportActivity extends FragmentActivity {
|
||||
});
|
||||
|
||||
//Works for a directory, but we might have received multiple files via SEND_MULTIPLE.
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
|
||||
viewBinding.importActivityToolbar.setTitle(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
|
||||
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
private static final String TAG_MULTI_TRACK = "MultiTrack";
|
||||
private static final String TAG_KML22_MULTI_TRACK = "gx:MultiTrack";
|
||||
|
||||
private static final String TAG_DATA_CATEGORY = "Data"; //used for Track.category
|
||||
private static final String TAG_DATA_ACTIVITYTYPE = "Data";
|
||||
|
||||
private static final String TAG_SIMPLE_ARRAY_DATA = "SimpleArrayData";
|
||||
private static final String TAG_KML22_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData";
|
||||
@@ -113,7 +113,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
private String icon;
|
||||
private String name;
|
||||
private String description;
|
||||
private String category;
|
||||
private String activityType;
|
||||
private String latitude;
|
||||
private String longitude;
|
||||
private String altitude;
|
||||
@@ -146,7 +146,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
onTrackSegmentStart();
|
||||
}
|
||||
case TAG_DATA_CATEGORY, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA ->
|
||||
case TAG_DATA_ACTIVITYTYPE, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA ->
|
||||
dataType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
}
|
||||
}
|
||||
@@ -165,15 +165,15 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
onMarkerEnd();
|
||||
case TAG_COORDINATES -> onMarkerLocationEnd();
|
||||
case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> {
|
||||
trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset);
|
||||
trackImporter.setTrack(context, name, uuid, description, activityType, icon, zoneOffset);
|
||||
zoneOffset = null;
|
||||
}
|
||||
case TAG_TRACK, TAG_KML22_TRACK -> onTrackSegmentEnd();
|
||||
case TAG_COORD, TAG_KML22_COORD -> onCoordEnded();
|
||||
case TAG_VALUE, TAG_KML22_VALUE -> {
|
||||
if (KMLTrackExporter.EXTENDED_DATA_TYPE_CATEGORY.equals(dataType)) {
|
||||
if (KMLTrackExporter.EXTENDED_DATA_TYPE_ACTIVITYTYPE.equals(dataType)) {
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
activityType = content.trim();
|
||||
}
|
||||
} else {
|
||||
onExtendedDataValueEnd();
|
||||
@@ -233,7 +233,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
name = null;
|
||||
icon = null;
|
||||
description = null;
|
||||
category = null;
|
||||
activityType = null;
|
||||
photoUrl = null;
|
||||
latitude = null;
|
||||
longitude = null;
|
||||
@@ -260,13 +260,13 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
Marker marker = new Marker(null, new TrackPoint(TrackPoint.Type.TRACKPOINT, location, whenList.get(0))); //TODO Creating marker without need
|
||||
marker.setName(name != null ? name : "");
|
||||
marker.setDescription(description != null ? description : "");
|
||||
marker.setCategory(category != null ? category : "");
|
||||
marker.setCategory(activityType != null ? activityType : "");
|
||||
marker.setPhotoUrl(photoUrl);
|
||||
markers.add(marker);
|
||||
|
||||
name = null;
|
||||
description = null;
|
||||
category = null;
|
||||
activityType = null;
|
||||
photoUrl = null;
|
||||
whenList.clear();
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class TrackImporter {
|
||||
this.markers.addAll(markers);
|
||||
}
|
||||
|
||||
void setTrack(Context context, String name, String uuid, String description, String category, String icon, @Nullable ZoneOffset zoneOffset) {
|
||||
void setTrack(Context context, String name, String uuid, String description, String activityType, String icon, @Nullable ZoneOffset zoneOffset) {
|
||||
track = new Track(zoneOffset != null ? zoneOffset : ZoneOffset.UTC);
|
||||
track.setName(name != null ? name : "");
|
||||
|
||||
@@ -103,11 +103,11 @@ public class TrackImporter {
|
||||
|
||||
track.setDescription(description != null ? description : "");
|
||||
|
||||
if (category != null) {
|
||||
track.setCategory(category);
|
||||
if (activityType != null) {
|
||||
track.setActivityType(activityType);
|
||||
|
||||
if (icon == null) {
|
||||
icon = TrackIconUtils.getIconValue(context, category);
|
||||
icon = TrackIconUtils.getIconValue(context, activityType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ public class StopRecording extends AbstractAPIActivity {
|
||||
protected void execute(TrackRecordingService service) {
|
||||
RecordingData recordingData = service.getRecordingDataObservable().getValue();
|
||||
Track.Id trackId = null;
|
||||
if (recordingData != null && recordingData.getTrack() != null) {
|
||||
trackId = recordingData.getTrack().getId();
|
||||
if (recordingData != null && recordingData.track() != null) {
|
||||
trackId = recordingData.track().getId();
|
||||
}
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
+5
-4
@@ -77,7 +77,7 @@ public abstract class AbstractBluetoothConnectionManager<DataType> {
|
||||
BluetoothGattService gattService = null;
|
||||
ServiceMeasurementUUID serviceMeasurement = null;
|
||||
for (ServiceMeasurementUUID s : serviceMeasurementUUIDs) {
|
||||
gattService = gatt.getService(s.getServiceUUID());
|
||||
gattService = gatt.getService(s.serviceUUID());
|
||||
if (gattService != null) {
|
||||
serviceMeasurement = s;
|
||||
break;
|
||||
@@ -89,9 +89,9 @@ public abstract class AbstractBluetoothConnectionManager<DataType> {
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.getMeasurementUUID());
|
||||
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.measurementUUID());
|
||||
if (characteristic == null) {
|
||||
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement.getServiceUUID() + " characteristicUUID=" + serviceMeasurement.getMeasurementUUID());
|
||||
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
|
||||
return;
|
||||
}
|
||||
gatt.setCharacteristicNotification(characteristic, true);
|
||||
@@ -113,7 +113,7 @@ public abstract class AbstractBluetoothConnectionManager<DataType> {
|
||||
UUID serviceUUID = characteristic.getService().getUuid();
|
||||
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress() + " with service " + serviceUUID + " and characteristics " + characteristic.getUuid());
|
||||
Optional<ServiceMeasurementUUID> serviceMeasurementUUID = serviceMeasurementUUIDs.stream()
|
||||
.filter(s -> s.getServiceUUID().equals(characteristic.getService().getUuid())).findFirst();
|
||||
.filter(s -> s.serviceUUID().equals(characteristic.getService().getUuid())).findFirst();
|
||||
if (serviceMeasurementUUID.isEmpty()) {
|
||||
Log.e(TAG, "Unknown service UUID; not supported?");
|
||||
return;
|
||||
@@ -189,6 +189,7 @@ public abstract class AbstractBluetoothConnectionManager<DataType> {
|
||||
*/
|
||||
protected abstract SensorData<DataType> parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, @NonNull BluetoothGattCharacteristic characteristic);
|
||||
|
||||
@Deprecated //TODO REMOVE
|
||||
interface SensorDataObserver {
|
||||
|
||||
void onChanged(SensorData<?> sensorData);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -12,56 +8,49 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
||||
|
||||
/**
|
||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||
*/
|
||||
public class AltitudeSumManager implements SensorConnector, SensorEventListener {
|
||||
public class AltitudeSumManager implements SensorConnector {
|
||||
|
||||
private static final String TAG = AltitudeSumManager.class.getSimpleName();
|
||||
|
||||
private boolean isConnected = false;
|
||||
private final BarometerInternal driver;
|
||||
|
||||
private float lastAcceptedPressureValue_hPa;
|
||||
private AtmosphericPressure lastAcceptedSensorValue;
|
||||
|
||||
private float lastSeenSensorValue_hPa;
|
||||
private AtmosphericPressure lastSeenSensorValue;
|
||||
|
||||
private Float altitudeGain_m;
|
||||
private Float altitudeLoss_m;
|
||||
|
||||
public AltitudeSumManager() {
|
||||
driver = new BarometerInternal();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public AltitudeSumManager(BarometerInternal mock) {
|
||||
this.driver = mock;
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
driver.connect(context, handler, this);
|
||||
|
||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
if (pressureSensor == null) {
|
||||
Log.w(TAG, "No pressure sensor available.");
|
||||
isConnected = false;
|
||||
} else {
|
||||
isConnected = sensorManager.registerListener(this, pressureSensor, (int) TimeUnit.SECONDS.toMicros(5), handler);
|
||||
}
|
||||
|
||||
lastAcceptedPressureValue_hPa = Float.NaN;
|
||||
lastAcceptedSensorValue = null;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
Log.d(TAG, "Stop");
|
||||
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sensorManager.unregisterListener(this);
|
||||
|
||||
isConnected = false;
|
||||
driver.disconnect(context);
|
||||
reset();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setConnected(boolean isConnected) {
|
||||
this.isConnected = isConnected;
|
||||
}
|
||||
|
||||
public void fill(@NonNull TrackPoint trackPoint) {
|
||||
trackPoint.setAltitudeGain(altitudeGain_m);
|
||||
trackPoint.setAltitudeLoss(altitudeLoss_m);
|
||||
@@ -69,7 +58,7 @@ public class AltitudeSumManager implements SensorConnector, SensorEventListener
|
||||
|
||||
@Nullable
|
||||
public Float getAltitudeGain_m() {
|
||||
return isConnected ? altitudeGain_m : null;
|
||||
return driver.isConnected() ? altitudeGain_m : null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -77,21 +66,9 @@ public class AltitudeSumManager implements SensorConnector, SensorEventListener
|
||||
this.altitudeGain_m = altitudeGain_m;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void addAltitudeGain_m(float altitudeGain_m) {
|
||||
this.altitudeGain_m = this.altitudeGain_m == null ? 0f : this.altitudeGain_m;
|
||||
this.altitudeGain_m += altitudeGain_m;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void addAltitudeLoss_m(Float altitudeLoss_m) {
|
||||
this.altitudeLoss_m = this.altitudeLoss_m == null ? 0f : this.altitudeLoss_m;
|
||||
this.altitudeLoss_m += altitudeLoss_m;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Float getAltitudeLoss_m() {
|
||||
return isConnected ? altitudeLoss_m : null;
|
||||
return driver.isConnected() ? altitudeLoss_m : null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -105,41 +82,26 @@ public class AltitudeSumManager implements SensorConnector, SensorEventListener
|
||||
altitudeLoss_m = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (!isConnected) {
|
||||
Log.w(TAG, "Not connected to sensor, cannot process data.");
|
||||
return;
|
||||
}
|
||||
onSensorValueChanged(event.values[0]);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onSensorValueChanged(float value_hPa) {
|
||||
if (Float.isNaN(lastAcceptedPressureValue_hPa)) {
|
||||
lastAcceptedPressureValue_hPa = value_hPa;
|
||||
lastSeenSensorValue_hPa = value_hPa;
|
||||
public void onSensorValueChanged(AtmosphericPressure currentSensorValue) {
|
||||
if (lastAcceptedSensorValue == null) {
|
||||
lastAcceptedSensorValue = currentSensorValue;
|
||||
lastSeenSensorValue = currentSensorValue;
|
||||
return;
|
||||
}
|
||||
|
||||
altitudeGain_m = altitudeGain_m != null ? altitudeGain_m : 0;
|
||||
altitudeLoss_m = altitudeLoss_m != null ? altitudeLoss_m : 0;
|
||||
|
||||
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedPressureValue_hPa, lastSeenSensorValue_hPa, value_hPa);
|
||||
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedSensorValue, lastSeenSensorValue, currentSensorValue);
|
||||
if (altitudeChange != null) {
|
||||
altitudeGain_m += altitudeChange.getAltitudeGain_m();
|
||||
|
||||
altitudeLoss_m += altitudeChange.getAltitudeLoss_m();
|
||||
|
||||
lastAcceptedPressureValue_hPa = altitudeChange.getCurrentSensorValue_hPa();
|
||||
lastAcceptedSensorValue = altitudeChange.currentSensorValue();
|
||||
}
|
||||
|
||||
lastSeenSensorValue_hPa = value_hPa;
|
||||
lastSeenSensorValue = currentSensorValue;
|
||||
|
||||
Log.v(TAG, "altitude gain: " + altitudeGain_m + ", altitude loss: " + altitudeLoss_m);
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ public class BluetoothConnectionManagerCyclingCadence extends AbstractBluetoothC
|
||||
if (serviceMeasurementUUID.equals(BluetoothUtils.CYCLING_POWER)) {
|
||||
SensorDataCyclingPower.Data data = BluetoothUtils.parseCyclingPower(address, sensorName, characteristic);
|
||||
if (data!= null) {
|
||||
return data.getCadence();
|
||||
return data.cadence();
|
||||
}
|
||||
} else if (serviceMeasurementUUID.equals(BluetoothUtils.CYCLING_SPEED_CADENCE)) {
|
||||
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
||||
|
||||
+1
-1
@@ -22,6 +22,6 @@ public class BluetoothConnectionManagerCyclingPower extends AbstractBluetoothCon
|
||||
protected SensorDataCyclingPower parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
SensorDataCyclingPower.Data cyclingPower = BluetoothUtils.parseCyclingPower(address, sensorName, characteristic);
|
||||
|
||||
return cyclingPower != null ? cyclingPower.getPower() : null;
|
||||
return cyclingPower != null ? cyclingPower.power() : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,17 +24,12 @@ import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@@ -61,20 +56,15 @@ public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBl
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private final Context context;
|
||||
private final Handler handler;
|
||||
private final SensorManager.SensorDataChangedObserver observer;
|
||||
private boolean started = false;
|
||||
|
||||
private Distance preferenceWheelCircumference;
|
||||
|
||||
private final BluetoothConnectionManagerHeartRate heartRate = new BluetoothConnectionManagerHeartRate(this);
|
||||
private final BluetoothConnectionManagerCyclingCadence cyclingCadence = new BluetoothConnectionManagerCyclingCadence(this);
|
||||
private final BluetoothConnectionManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManagerCyclingDistanceSpeed(this);
|
||||
private final BluetoothConnectionManagerCyclingPower cyclingPower = new BluetoothConnectionManagerCyclingPower(this);
|
||||
private final BluetoothConnectionRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothConnectionRunningSpeedAndCadence(this);
|
||||
|
||||
private final SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
|
||||
private final SensorManager.SensorDataSetChangeObserver observer;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
@@ -95,9 +85,6 @@ public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBl
|
||||
|
||||
connect(cyclingSpeed, address);
|
||||
}
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
|
||||
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress();
|
||||
@@ -114,7 +101,7 @@ public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBl
|
||||
}
|
||||
};
|
||||
|
||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorManager.SensorDataSetChangeObserver observer) {
|
||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @Nullable SensorManager.SensorDataChangedObserver observer) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
this.observer = observer;
|
||||
@@ -137,8 +124,6 @@ public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBl
|
||||
cyclingPower.disconnect();
|
||||
runningSpeedAndCadence.disconnect();
|
||||
|
||||
sensorDataSet.clear();
|
||||
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
started = false;
|
||||
}
|
||||
@@ -178,53 +163,14 @@ public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBl
|
||||
}
|
||||
}
|
||||
|
||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return new SensorDataSet(sensorDataSet);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
sensorDataSet.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onChanged(SensorData<?> sensorData) {
|
||||
if (sensorData instanceof SensorDataCyclingCadence) {
|
||||
SensorDataCyclingCadence previous = sensorDataSet.getCyclingCadence();
|
||||
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
|
||||
|
||||
if (sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: cadence data repeated.");
|
||||
return;
|
||||
}
|
||||
((SensorDataCyclingCadence) sensorData).compute(previous);
|
||||
}
|
||||
if (sensorData instanceof SensorDataCyclingDistanceSpeed) {
|
||||
SensorDataCyclingDistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
|
||||
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
|
||||
if (sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: cycling speed data repeated.");
|
||||
return;
|
||||
}
|
||||
((SensorDataCyclingDistanceSpeed) sensorData).compute(previous, preferenceWheelCircumference);
|
||||
}
|
||||
if (sensorData instanceof SensorDataRunning) {
|
||||
SensorDataRunning previous = sensorDataSet.getRunningDistanceSpeedCadence();
|
||||
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
|
||||
if (sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: running speed data repeated.");
|
||||
return;
|
||||
}
|
||||
((SensorDataRunning) sensorData).compute(previous);
|
||||
}
|
||||
|
||||
sensorDataSet.set(sensorData);
|
||||
observer.onChange(new SensorDataSet(sensorDataSet));
|
||||
observer.onChange(sensorData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnecting(SensorData<?> sensorData) {
|
||||
sensorDataSet.remove(sensorData);
|
||||
observer.onDisconnect(sensorData);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.data.models.BatteryLevel;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
@@ -60,13 +61,19 @@ public class BluetoothUtils {
|
||||
);
|
||||
|
||||
// Used for device discovery in preferences
|
||||
public static final List<ServiceMeasurementUUID> HEART_RATE_SUPPORTING_DEVICES = Collections.unmodifiableList(Arrays.asList(
|
||||
public static final List<ServiceMeasurementUUID> HEART_RATE_SUPPORTING_DEVICES = List.of(
|
||||
HEARTRATE,
|
||||
//Devices that support HEART_RATE_SERVICE_UUID, but do not announce HEART_RATE_SERVICE_UUID in there BLE announcement messages (during device discovery).
|
||||
new ServiceMeasurementUUID(
|
||||
UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"), //Miband3
|
||||
HEARTRATE.getMeasurementUUID()
|
||||
))
|
||||
HEARTRATE.measurementUUID()
|
||||
)
|
||||
);
|
||||
|
||||
private static final UUID ENVIRONMENTAL_SENSING_SERVICE = new UUID(0x181A00001000L, 0x800000805f9b34fbL);
|
||||
public static final ServiceMeasurementUUID BAROMETRIC_PRESSURE = new ServiceMeasurementUUID(
|
||||
ENVIRONMENTAL_SENSING_SERVICE,
|
||||
new UUID(0x2A6D00001000L, 0x800000805f9b34fbL)
|
||||
);
|
||||
|
||||
public static final ServiceMeasurementUUID CYCLING_POWER = new ServiceMeasurementUUID(
|
||||
@@ -137,6 +144,17 @@ public class BluetoothUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static AtmosphericPressure parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] raw = characteristic.getValue();
|
||||
|
||||
if (raw.length < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer pressure = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0);
|
||||
return AtmosphericPressure.ofPA(pressure / 10f);
|
||||
}
|
||||
|
||||
public static SensorDataCyclingPower.Data parseCyclingPower(String address, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.cycling_power_measurement.xml
|
||||
int valueLength = characteristic.getValue().length;
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.hardware.SensorManager;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
|
||||
public class PressureSensorUtils {
|
||||
|
||||
//Everything above is considered a meaningful change in altitude.
|
||||
@@ -16,24 +18,7 @@ public class PressureSensorUtils {
|
||||
private PressureSensorUtils() {
|
||||
}
|
||||
|
||||
public static class AltitudeChange {
|
||||
|
||||
private final float currentSensorValue_hPa;
|
||||
|
||||
private final float altitudeChange_m;
|
||||
|
||||
public AltitudeChange(float currentSensorValue_hPa, float altitudeChange_m) {
|
||||
this.currentSensorValue_hPa = currentSensorValue_hPa;
|
||||
this.altitudeChange_m = altitudeChange_m;
|
||||
}
|
||||
|
||||
public float getCurrentSensorValue_hPa() {
|
||||
return currentSensorValue_hPa;
|
||||
}
|
||||
|
||||
public float getAltitudeChange_m() {
|
||||
return altitudeChange_m;
|
||||
}
|
||||
public record AltitudeChange(AtmosphericPressure currentSensorValue, float altitudeChange_m) {
|
||||
|
||||
public float getAltitudeGain_m() {
|
||||
return altitudeChange_m > 0 ? altitudeChange_m : 0;
|
||||
@@ -47,16 +32,16 @@ public class PressureSensorUtils {
|
||||
/**
|
||||
* Applies exponential smoothing to sensor value before computation.
|
||||
*/
|
||||
public static AltitudeChange computeChangesWithSmoothing_m(float lastAcceptedSensorValue_hPa, float lastSeenSensorValue_hPa, float currentSensorValue_hPa) {
|
||||
float nextSensorValue_hPa = EXPONENTIAL_SMOOTHING * currentSensorValue_hPa + (1 - EXPONENTIAL_SMOOTHING) * lastSeenSensorValue_hPa;
|
||||
public static AltitudeChange computeChangesWithSmoothing_m(AtmosphericPressure lastAcceptedSensorValue, AtmosphericPressure lastSeenSensorValue, AtmosphericPressure currentSensorValue) {
|
||||
AtmosphericPressure nextSensorValue = AtmosphericPressure.ofHPA(EXPONENTIAL_SMOOTHING * currentSensorValue.getHPA() + (1 - EXPONENTIAL_SMOOTHING) * lastSeenSensorValue.getHPA());
|
||||
|
||||
return computeChanges(lastAcceptedSensorValue_hPa, nextSensorValue_hPa);
|
||||
return computeChanges(lastAcceptedSensorValue, nextSensorValue);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static AltitudeChange computeChanges(float lastAcceptedSensorValue_hPa, float currentSensorValue_hPa) {
|
||||
float lastSensorValue_m = SensorManager.getAltitude(p0, lastAcceptedSensorValue_hPa);
|
||||
float currentSensorValue_m = SensorManager.getAltitude(p0, currentSensorValue_hPa);
|
||||
public static AltitudeChange computeChanges(AtmosphericPressure lastAcceptedSensorValue, AtmosphericPressure currentSensorValue) {
|
||||
float lastSensorValue_m = SensorManager.getAltitude(p0, lastAcceptedSensorValue.getHPA());
|
||||
float currentSensorValue_m = SensorManager.getAltitude(p0, currentSensorValue.getHPA());
|
||||
|
||||
float altitudeChange_m = currentSensorValue_m - lastSensorValue_m;
|
||||
if (Math.abs(altitudeChange_m) < ALTITUDE_CHANGE_DIFF_M) {
|
||||
@@ -64,8 +49,8 @@ public class PressureSensorUtils {
|
||||
}
|
||||
|
||||
// Limit altitudeC change by ALTITUDE_CHANGE_DIFF and computes pressure value accordingly.
|
||||
AltitudeChange altitudeChange = new AltitudeChange(currentSensorValue_hPa, altitudeChange_m);
|
||||
if (altitudeChange.getAltitudeChange_m() > 0) {
|
||||
AltitudeChange altitudeChange = new AltitudeChange(currentSensorValue, altitudeChange_m);
|
||||
if (altitudeChange.altitudeChange_m() > 0) {
|
||||
return new AltitudeChange(getBarometricPressure(lastSensorValue_m + ALTITUDE_CHANGE_DIFF_M), ALTITUDE_CHANGE_DIFF_M);
|
||||
} else {
|
||||
return new AltitudeChange(getBarometricPressure(lastSensorValue_m - ALTITUDE_CHANGE_DIFF_M), -1 * ALTITUDE_CHANGE_DIFF_M);
|
||||
@@ -78,7 +63,7 @@ public class PressureSensorUtils {
|
||||
* {\color{White} p(h)} = p_0 \cdot \left( 1 - \frac{0{,}0065 \frac{\mathrm K}{\mathrm m} \cdot h}{T_0\ } \right)^{5{,}255}
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static float getBarometricPressure(float altitude_m) {
|
||||
return (float) (p0 * Math.pow(1.0 - 0.0065 * altitude_m / 288.15, 5.255f));
|
||||
public static AtmosphericPressure getBarometricPressure(float altitude_m) {
|
||||
return AtmosphericPressure.ofHPA((float) (p0 * Math.pow(1.0 - 0.0065 * altitude_m / 288.15, 5.255f)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,23 +9,53 @@ import androidx.annotation.VisibleForTesting;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.GPSManager;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
|
||||
public class SensorManager {
|
||||
|
||||
private static final String TAG = SensorManager.class.getSimpleName();
|
||||
|
||||
//TODO Should be final and not be visible for testing
|
||||
@VisibleForTesting
|
||||
public SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
|
||||
private final TrackPointCreator observer;
|
||||
|
||||
private final SensorDataChangedObserver listener = new SensorDataChangedObserver() {
|
||||
|
||||
@Override
|
||||
public void onChange(SensorData<?> sensorData) {
|
||||
sensorDataSet.set(sensorData);
|
||||
observer.onChange(new SensorDataSet(sensorDataSet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(SensorData<?> sensorData) {
|
||||
sensorDataSet.remove(sensorData);
|
||||
observer.onChange(new SensorDataSet(sensorDataSet));
|
||||
}
|
||||
};
|
||||
|
||||
private BluetoothRemoteSensorManager bluetoothSensorManager;
|
||||
|
||||
private AltitudeSumManager altitudeSumManager;
|
||||
|
||||
public SensorManager(Context context, Handler handler, SensorDataSetChangeObserver observer) {
|
||||
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, observer);
|
||||
altitudeSumManager = new AltitudeSumManager();
|
||||
private GPSManager gpsManager;
|
||||
|
||||
public SensorManager(TrackPointCreator observer) {
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
bluetoothSensorManager.start(context, handler);
|
||||
gpsManager = new GPSManager(observer); //TODO Pass listener
|
||||
gpsManager.start(context, handler);
|
||||
|
||||
altitudeSumManager = new AltitudeSumManager();
|
||||
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener);
|
||||
|
||||
altitudeSumManager.start(context, handler);
|
||||
bluetoothSensorManager.start(context, handler);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
@@ -38,11 +68,19 @@ public class SensorManager {
|
||||
altitudeSumManager.stop(context);
|
||||
altitudeSumManager = null;
|
||||
}
|
||||
|
||||
if (gpsManager != null) {
|
||||
gpsManager.stop(context);
|
||||
gpsManager = null;
|
||||
}
|
||||
|
||||
sensorDataSet.clear();
|
||||
}
|
||||
|
||||
public SensorDataSet fill(TrackPoint trackPoint) {
|
||||
altitudeSumManager.fill(trackPoint);
|
||||
return bluetoothSensorManager.fill(trackPoint);
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return new SensorDataSet(sensorDataSet);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@@ -50,7 +88,7 @@ public class SensorManager {
|
||||
Log.d(TAG, "No recording running and no reset necessary.");
|
||||
return;
|
||||
}
|
||||
bluetoothSensorManager.reset();
|
||||
sensorDataSet.reset();
|
||||
altitudeSumManager.reset();
|
||||
}
|
||||
|
||||
@@ -60,10 +98,8 @@ public class SensorManager {
|
||||
return bluetoothSensorManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setBluetoothSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
this.bluetoothSensorManager = remoteSensorManager;
|
||||
public GPSManager getGpsManager() {
|
||||
return gpsManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -78,8 +114,9 @@ public class SensorManager {
|
||||
this.altitudeSumManager = altitudeSumManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public interface SensorDataSetChangeObserver {
|
||||
void onChange(SensorDataSet sensorDataSet);
|
||||
public interface SensorDataChangedObserver {
|
||||
void onChange(SensorData<?> sensorData);
|
||||
|
||||
void onDisconnect(SensorData<?> sensorData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,43 +5,5 @@ import androidx.annotation.NonNull;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ServiceMeasurementUUID {
|
||||
|
||||
private final UUID serviceUUID;
|
||||
private final UUID measurementUUID;
|
||||
|
||||
public ServiceMeasurementUUID(UUID serviceUUID, UUID measurementUUID) {
|
||||
this.serviceUUID = serviceUUID;
|
||||
this.measurementUUID = measurementUUID;
|
||||
}
|
||||
|
||||
public UUID getServiceUUID() {
|
||||
return serviceUUID;
|
||||
}
|
||||
|
||||
public UUID getMeasurementUUID() {
|
||||
return measurementUUID;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServiceMeasurementUUID{" +
|
||||
"serviceUUID=" + serviceUUID +
|
||||
", measurementUUID=" + measurementUUID +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ServiceMeasurementUUID that = (ServiceMeasurementUUID) o;
|
||||
return Objects.equals(serviceUUID, that.serviceUUID) && Objects.equals(measurementUUID, that.measurementUUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(serviceUUID, measurementUUID);
|
||||
}
|
||||
public record ServiceMeasurementUUID(UUID serviceUUID, UUID measurementUUID) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
|
||||
|
||||
public class BarometerInternal {
|
||||
|
||||
private static final String TAG = BarometerInternal.class.getSimpleName();
|
||||
|
||||
private static final int SAMPLING_PERIOD = (int) TimeUnit.SECONDS.toMicros(5);
|
||||
|
||||
private AltitudeSumManager observer;
|
||||
|
||||
private final SensorEventListener listener = new SensorEventListener() {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "Not connected to sensor, cannot process data.");
|
||||
return;
|
||||
}
|
||||
|
||||
observer.onSensorValueChanged(AtmosphericPressure.ofHPA(event.values[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
|
||||
}
|
||||
};
|
||||
|
||||
public void connect(Context context, Handler handler, AltitudeSumManager observer) {
|
||||
this.observer = observer;
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
if (pressureSensor == null) {
|
||||
Log.w(TAG, "No pressure sensor available.");
|
||||
this.observer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sensorManager.registerListener(listener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||
this.observer = observer;
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect(context);
|
||||
}
|
||||
|
||||
public void disconnect(Context context) {
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sensorManager.unregisterListener(listener);
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return observer != null;
|
||||
}
|
||||
}
|
||||
+13
-24
@@ -28,30 +28,19 @@ public class SensorDataCyclingPower extends SensorData<Power> {
|
||||
return super.toString() + " data=" + value;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
private final SensorDataCyclingPower power;
|
||||
private final SensorDataCyclingCadence cadence;
|
||||
public record Data(SensorDataCyclingPower power, SensorDataCyclingCadence cadence) {
|
||||
public Data(SensorDataCyclingPower power, @Nullable SensorDataCyclingCadence cadence) {
|
||||
this.power = power;
|
||||
this.cadence = cadence;
|
||||
}
|
||||
|
||||
public Data(SensorDataCyclingPower power, @Nullable SensorDataCyclingCadence cadence) {
|
||||
this.power = power;
|
||||
this.cadence = cadence;
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"power=" + power +
|
||||
", cadence=" + cadence +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public SensorDataCyclingPower getPower() {
|
||||
return power;
|
||||
}
|
||||
|
||||
public SensorDataCyclingCadence getCadence() {
|
||||
return cadence;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"power=" + power +
|
||||
", cadence=" + cadence +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +68,8 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
|
||||
Distance overallDistance = null;
|
||||
if (previous != null && previous.hasTotalDistance()) {
|
||||
overallDistance = this.totalDistance.minus(previous.totalDistance);
|
||||
if (previous.hasValue() && previous.getValue().getDistance() != null) {
|
||||
overallDistance = overallDistance.plus(previous.getValue().getDistance());
|
||||
if (previous.hasValue() && previous.getValue().distance() != null) {
|
||||
overallDistance = overallDistance.plus(previous.getValue().distance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,41 +84,6 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
private final Speed speed;
|
||||
private final Cadence cadence;
|
||||
|
||||
@Nullable
|
||||
private final Distance distance;
|
||||
|
||||
public Data(Speed speed, Cadence cadence, @Nullable Distance distance) {
|
||||
this.speed = speed;
|
||||
this.cadence = cadence;
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public Speed getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public Cadence getCadence() {
|
||||
return cadence;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Distance getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"speed=" + speed +
|
||||
", cadence=" + cadence +
|
||||
", distance=" + distance +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
public record Data(Speed speed, Cadence cadence, @Nullable Distance distance) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public final class SensorDataSet {
|
||||
|
||||
private static final String TAG = SensorDataSet.class.getSimpleName();
|
||||
|
||||
private SensorDataHeartRate heartRate;
|
||||
|
||||
private SensorDataCyclingCadence cyclingCadence;
|
||||
@@ -57,7 +63,7 @@ public final class SensorDataSet {
|
||||
return new Pair<>(cyclingDistanceSpeed.getValue().getSpeed(), cyclingDistanceSpeed.getSensorNameOrAddress());
|
||||
}
|
||||
|
||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue() && runningDistanceSpeedCadence.getValue().getSpeed() != null) {
|
||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue() && runningDistanceSpeedCadence.getValue().speed() != null) {
|
||||
return new Pair<>(runningDistanceSpeedCadence.getSpeed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
||||
}
|
||||
|
||||
@@ -118,7 +124,7 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue()) {
|
||||
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().getDistance());
|
||||
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().distance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,28 +146,61 @@ public final class SensorDataSet {
|
||||
+ (runningDistanceSpeedCadence != null ? " " + runningDistanceSpeedCadence : "");
|
||||
}
|
||||
|
||||
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
|
||||
private void set(@NonNull SensorData<?> type, @Nullable SensorData<?> sensorData) {
|
||||
if (type instanceof SensorDataHeartRate) {
|
||||
this.heartRate = (SensorDataHeartRate) data;
|
||||
this.heartRate = (SensorDataHeartRate) sensorData;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type instanceof SensorDataCyclingCadence) {
|
||||
this.cyclingCadence = (SensorDataCyclingCadence) data;
|
||||
SensorDataCyclingCadence previous = getCyclingCadence();
|
||||
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
|
||||
|
||||
if (sensorData != null && sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: cadence data repeated.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.cyclingCadence = (SensorDataCyclingCadence) sensorData;
|
||||
if (this.cyclingCadence != null) {
|
||||
this.cyclingCadence.compute(previous);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type instanceof SensorDataCyclingDistanceSpeed) {
|
||||
this.cyclingDistanceSpeed = (SensorDataCyclingDistanceSpeed) data;
|
||||
SensorDataCyclingDistanceSpeed previous = getCyclingDistanceSpeed();
|
||||
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
|
||||
if (sensorData != null && sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: cycling speed data repeated.");
|
||||
return;
|
||||
}
|
||||
Distance preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(); //TODO Fetch once and then listen for changes.
|
||||
|
||||
this.cyclingDistanceSpeed = (SensorDataCyclingDistanceSpeed) sensorData;
|
||||
if (this.cyclingDistanceSpeed != null) {
|
||||
this.cyclingDistanceSpeed.compute(previous, preferenceWheelCircumference);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type instanceof SensorDataCyclingPower) {
|
||||
this.cyclingPower = (SensorDataCyclingPower) data;
|
||||
this.cyclingPower = (SensorDataCyclingPower) sensorData;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type instanceof SensorDataRunning) {
|
||||
this.runningDistanceSpeedCadence = (SensorDataRunning) data;
|
||||
SensorDataRunning previous = getRunningDistanceSpeedCadence();
|
||||
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
|
||||
if (sensorData != null && sensorData.equals(previous)) {
|
||||
Log.d(TAG, "onChanged: running speed data repeated.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.runningDistanceSpeedCadence = (SensorDataRunning) sensorData;
|
||||
if (this.runningDistanceSpeedCadence != null) {
|
||||
this.runningDistanceSpeedCadence.compute(previous);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,39 +2,21 @@ package de.dennisguse.opentracks.services;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
public class RecordingData {
|
||||
|
||||
private final Track track;
|
||||
|
||||
private final TrackPoint latestTrackPoint;
|
||||
|
||||
private final SensorDataSet sensorDataSet;
|
||||
|
||||
/**
|
||||
* {@link Track} and {@link TrackPoint} must be immutable (i.e., their content does not change).
|
||||
*/
|
||||
public RecordingData(Track track, TrackPoint lastTrackPoint, SensorDataSet sensorDataSet) {
|
||||
this.track = track;
|
||||
this.latestTrackPoint = lastTrackPoint;
|
||||
this.sensorDataSet = sensorDataSet;
|
||||
}
|
||||
|
||||
public Track getTrack() {
|
||||
return track;
|
||||
}
|
||||
/**
|
||||
* {@link Track} and {@link TrackPoint} must be immutable (i.e., their content does not change).
|
||||
*/
|
||||
public record RecordingData(Track track, TrackPoint latestTrackPoint, SensorDataSet sensorDataSet) {
|
||||
|
||||
public String getTrackCategory() {
|
||||
if (track == null) {
|
||||
return "";
|
||||
}
|
||||
return track.getCategory();
|
||||
return track.getActivityType();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -45,35 +27,4 @@ public class RecordingData {
|
||||
|
||||
return track.getTrackStatistics();
|
||||
}
|
||||
|
||||
public TrackPoint getLatestTrackPoint() {
|
||||
return latestTrackPoint;
|
||||
}
|
||||
|
||||
public SensorDataSet getSensorDataSet() {
|
||||
return sensorDataSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
RecordingData that = (RecordingData) o;
|
||||
return Objects.equals(track, that.track) && Objects.equals(latestTrackPoint, that.latestTrackPoint) && Objects.equals(sensorDataSet, that.sensorDataSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(track, latestTrackPoint, sensorDataSet);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RecordingData{" +
|
||||
"track=" + track +
|
||||
", latestTrackPoint=" + latestTrackPoint +
|
||||
", sensorDataSet=" + sensorDataSet +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,7 @@ import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
|
||||
public class RecordingStatus {
|
||||
private final Track.Id trackId;
|
||||
|
||||
@VisibleForTesting
|
||||
RecordingStatus(Track.Id trackId) {
|
||||
this.trackId = trackId;
|
||||
}
|
||||
public record RecordingStatus(Track.Id trackId) {
|
||||
|
||||
public Track.Id getTrackId() {
|
||||
return trackId;
|
||||
@@ -34,25 +28,4 @@ public class RecordingStatus {
|
||||
public RecordingStatus stop() {
|
||||
return TrackRecordingService.STATUS_DEFAULT;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RecordingStatus{" +
|
||||
"trackId=" + trackId +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
RecordingStatus that = (RecordingStatus) o;
|
||||
return Objects.equals(trackId, that.trackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(trackId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChang
|
||||
onNewTrackPoint(segmentStartTrackPoint);
|
||||
|
||||
String category = PreferencesUtils.getDefaultActivity();
|
||||
track.setCategory(category);
|
||||
track.setActivityType(category);
|
||||
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
//TODO Pass TrackPoint
|
||||
|
||||
+1
-24
@@ -9,19 +9,15 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
@@ -89,6 +85,7 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(context);
|
||||
// The decision if a trackpoint is not accurate enough should happen in the sensor subsystem.
|
||||
if (trackPoint.hasHorizontalAccuracy()) {
|
||||
formattedAccuracy = formatter.formatDistance(trackPoint.getHorizontalAccuracy());
|
||||
|
||||
@@ -107,26 +104,6 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
|
||||
notificationBuilder.setOnlyAlertOnce(true);
|
||||
}
|
||||
|
||||
Notification setRecording(Context context, @NonNull Track.Id trackId) {
|
||||
Intent intent = IntentUtils.newIntent(context, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE;
|
||||
}
|
||||
|
||||
PendingIntent pendingIntent = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(intent)
|
||||
.getPendingIntent(0, pendingIntentFlags);
|
||||
|
||||
updateContent(context.getString(R.string.gps_starting));
|
||||
|
||||
notificationBuilder.setContentIntent(pendingIntent);
|
||||
updateNotification();
|
||||
|
||||
return getNotification();
|
||||
}
|
||||
|
||||
Notification setGPSonlyStarted(Context context) {
|
||||
Intent intent = IntentUtils.newIntent(context, TrackListActivity.class);
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ public class VoiceAnnouncement {
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(track.getId());
|
||||
}
|
||||
|
||||
Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track.getCategory()), lastInterval, sensorStatistics);
|
||||
Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track.getActivityType()), lastInterval, sensorStatistics);
|
||||
|
||||
if (announcement.length() > 0) {
|
||||
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ class VoiceAnnouncementUtils {
|
||||
unitDistanceTTS = "kilometer";
|
||||
unitSpeedTTS = "kilometer per hour";
|
||||
}
|
||||
case IMPERIAL -> {
|
||||
case IMPERIAL_FEET -> {
|
||||
perUnitStringId = R.string.voice_per_mile;
|
||||
distanceId = R.plurals.voiceDistanceMiles;
|
||||
speedId = R.plurals.voiceSpeedMilesPerHour;
|
||||
|
||||
@@ -15,7 +15,6 @@ import androidx.core.location.LocationManagerCompat;
|
||||
import androidx.core.location.LocationRequestCompat;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
@@ -26,18 +25,18 @@ import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public class GPSManager implements SensorConnector, LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public class GPSManager implements SensorConnector, LocationListenerCompat, GpsStatusManager.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private final String TAG = GPSManager.class.getSimpleName();
|
||||
|
||||
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||
private static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||
|
||||
private final TrackPointCreator trackPointCreator;
|
||||
private Context context;
|
||||
private Handler handler;
|
||||
|
||||
private LocationManager locationManager;
|
||||
private GpsStatus gpsStatus;
|
||||
private GpsStatusManager gpsStatusManager;
|
||||
private Duration gpsInterval;
|
||||
private Distance thresholdHorizontalAccuracy;
|
||||
|
||||
@@ -50,10 +49,10 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
this.handler = handler;
|
||||
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(this);
|
||||
gpsStatus = new GpsStatus(context, this);
|
||||
gpsStatusManager = new GpsStatusManager(context, this, handler);
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
registerLocationListener();
|
||||
gpsStatus.start();
|
||||
gpsStatusManager.start();
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
@@ -72,9 +71,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
handler = null;
|
||||
}
|
||||
|
||||
if (gpsStatus != null) {
|
||||
gpsStatus.stop();
|
||||
gpsStatus = null;
|
||||
if (gpsStatusManager != null) {
|
||||
gpsStatusManager.stop();
|
||||
gpsStatusManager = null;
|
||||
}
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
@@ -88,8 +87,8 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
|
||||
gpsInterval = PreferencesUtils.getMinRecordingInterval();
|
||||
|
||||
if (gpsStatus != null) {
|
||||
gpsStatus.onMinRecordingIntervalChanged(gpsInterval);
|
||||
if (gpsStatusManager != null) {
|
||||
gpsStatusManager.onMinRecordingIntervalChanged(gpsInterval);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(R.string.recording_gps_accuracy_key, key)) {
|
||||
@@ -98,9 +97,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
||||
registerListener = true;
|
||||
|
||||
if (gpsStatus != null) {
|
||||
if (gpsStatusManager != null) {
|
||||
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval();
|
||||
gpsStatus.onRecordingDistanceChanged(gpsMinDistance);
|
||||
gpsStatusManager.onRecordingDistanceChanged(gpsMinDistance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,10 +120,10 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
return;
|
||||
}
|
||||
|
||||
if (gpsStatus != null) {
|
||||
if (gpsStatusManager != null) {
|
||||
// Send each update to the status; please note that this TrackPoint is not stored.
|
||||
TrackPoint trackPoint = new TrackPoint(location, Instant.ofEpochMilli(location.getTime()));
|
||||
gpsStatus.onLocationChanged(trackPoint);
|
||||
TrackPoint trackPoint = new TrackPoint(location, trackPointCreator.createNow());
|
||||
gpsStatusManager.onNewTrackPoint(trackPoint);
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
@@ -146,15 +145,15 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(@NonNull String provider) {
|
||||
if (gpsStatus != null) {
|
||||
gpsStatus.onGpsEnabled();
|
||||
if (gpsStatusManager != null) {
|
||||
gpsStatusManager.onGpsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(@NonNull String provider) {
|
||||
if (gpsStatus != null) {
|
||||
gpsStatus.onGpsDisabled();
|
||||
if (gpsStatusManager != null) {
|
||||
gpsStatusManager.onGpsDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +188,7 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGpsStatusChanged(GpsStatusValue prevStatus, GpsStatusValue currentStatus) {
|
||||
public void onGpsStatusChanged(GpsStatusValue currentStatus) {
|
||||
trackPointCreator.sendGpsStatus(currentStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* This class handle GPS status according to received locations` and some thresholds.
|
||||
*/
|
||||
//TODO should handle sharedpreference changes
|
||||
class GpsStatus {
|
||||
|
||||
private static final String TAG = GpsStatus.class.getSimpleName();
|
||||
|
||||
// The duration that GpsStatus waits from minimal interval to consider GPS lost.
|
||||
private static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(30);
|
||||
|
||||
private Distance thresholdHorizontalAccuracy;
|
||||
// Threshold for time without points.
|
||||
private Duration signalLostThreshold;
|
||||
|
||||
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
|
||||
private GpsStatusListener client;
|
||||
private final Context context;
|
||||
|
||||
@Nullable
|
||||
private TrackPoint lastTrackPoint = null;
|
||||
|
||||
@Nullable
|
||||
// The last valid (not null) location. Null value means that there have not been any location yet.
|
||||
private TrackPoint lastValidTrackPoint = null;
|
||||
|
||||
// Flag to prevent GpsStatus checks two or more locations at the same time.
|
||||
private boolean checking = false;
|
||||
|
||||
private class GpsStatusRunner implements Runnable {
|
||||
private boolean stopped = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (gpsStatus != null && !stopped) {
|
||||
onLocationChanged(null);
|
||||
gpsStatusHandler.postDelayed(gpsStatusRunner, getIntervalThreshold().toMillis());
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
stopped = true;
|
||||
sendStatus(gpsStatus, GpsStatusValue.GPS_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
private final Handler gpsStatusHandler;
|
||||
private GpsStatusRunner gpsStatusRunner = null;
|
||||
|
||||
public GpsStatus(Context context, GpsStatusListener client) {
|
||||
this.client = client;
|
||||
this.context = context;
|
||||
|
||||
thresholdHorizontalAccuracy = PreferencesUtils.getRecordingDistanceInterval();
|
||||
|
||||
Duration minRecordingInterval = PreferencesUtils.getMinRecordingInterval();
|
||||
signalLostThreshold = SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval);
|
||||
|
||||
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.
|
||||
*/
|
||||
public void stop() {
|
||||
client.onGpsStatusChanged(gpsStatus, GpsStatusValue.GPS_NONE);
|
||||
client = null;
|
||||
if (gpsStatusRunner != null) {
|
||||
gpsStatusRunner.stop();
|
||||
gpsStatusRunner = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to change the bad threshold from outside.
|
||||
*
|
||||
* @param value New preference value to signalBadThreshold.
|
||||
*/
|
||||
public void onRecordingDistanceChanged(@NonNull Distance value) {
|
||||
thresholdHorizontalAccuracy = value;
|
||||
}
|
||||
|
||||
public void onMinRecordingIntervalChanged(Duration value) {
|
||||
signalLostThreshold = SIGNAL_LOST_THRESHOLD.plus(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be called from the client every time a new trackPoint is received.
|
||||
* Receive new trackPoint and calculate the new status if needed.
|
||||
* It look for GPS changes in lastLocation if it's not null. If it's null then look for in lastValidLocation if any.
|
||||
*/
|
||||
public void onLocationChanged(final TrackPoint trackPoint) {
|
||||
if (checking) {
|
||||
return;
|
||||
}
|
||||
|
||||
checking = true;
|
||||
if (lastTrackPoint != null) {
|
||||
checkStatusFromLastLocation();
|
||||
} else if (lastValidTrackPoint != null) {
|
||||
checkStatusFromLastValidLocation();
|
||||
}
|
||||
|
||||
if (trackPoint != null) {
|
||||
lastValidTrackPoint = trackPoint;
|
||||
}
|
||||
lastTrackPoint = trackPoint;
|
||||
checking = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if lastLocation has new GPS status looking up time and accuracy.
|
||||
* It depends of signalLostThreshold and signalBadThreshold.
|
||||
* If there is any change then it does the change.
|
||||
* Also, it'll run the runnable if signal is bad or stop it if the signal is lost.
|
||||
*/
|
||||
private void checkStatusFromLastLocation() {
|
||||
if (Duration.between(lastTrackPoint.getTime(), Instant.now()).compareTo(signalLostThreshold) > 0 && gpsStatus != GpsStatusValue.GPS_SIGNAL_LOST) {
|
||||
// Too much time without receiving signal -> signal lost.
|
||||
GpsStatusValue oldStatus = gpsStatus;
|
||||
gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST;
|
||||
sendStatus(oldStatus, gpsStatus);
|
||||
stopStatusRunner();
|
||||
} else if (lastTrackPoint.fulfillsAccuracy(thresholdHorizontalAccuracy) && gpsStatus != GpsStatusValue.GPS_SIGNAL_BAD) {
|
||||
// Too little accuracy -> bad signal.
|
||||
GpsStatusValue oldStatus = gpsStatus;
|
||||
gpsStatus = GpsStatusValue.GPS_SIGNAL_BAD;
|
||||
sendStatus(oldStatus, gpsStatus);
|
||||
startStatusRunner();
|
||||
} else if (lastTrackPoint.fulfillsAccuracy(thresholdHorizontalAccuracy) && gpsStatus != GpsStatusValue.GPS_SIGNAL_FIX) {
|
||||
// Gps okay.
|
||||
GpsStatusValue oldStatus = gpsStatus;
|
||||
gpsStatus = GpsStatusValue.GPS_SIGNAL_FIX;
|
||||
sendStatus(oldStatus, gpsStatus);
|
||||
startStatusRunner();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if lastValidLocation has a new GPS status looking up time.
|
||||
* It depends on signalLostThreshold.
|
||||
* If there is any change then it does the change.
|
||||
*/
|
||||
private void checkStatusFromLastValidLocation() {
|
||||
Duration elapsed = Duration.between(lastValidTrackPoint.getTime(), Instant.now());
|
||||
if (signalLostThreshold.minus(elapsed).isNegative()) {
|
||||
// Too much time without locations -> lost signal? (wait signalLostThreshold from last valid location).
|
||||
GpsStatusValue oldStatus = gpsStatus;
|
||||
gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST;
|
||||
sendStatus(oldStatus, gpsStatus);
|
||||
stopStatusRunner();
|
||||
lastValidTrackPoint = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be called from the client every time the GPS sensor is enabled.
|
||||
* Anyway, it checks that GPS is enabled because the client assumes that if it's on then GPS is enabled but user can disable GPS by hand.
|
||||
*/
|
||||
public void onGpsEnabled() {
|
||||
if (gpsStatus != GpsStatusValue.GPS_ENABLED) {
|
||||
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
GpsStatusValue oldStatus = gpsStatus;
|
||||
gpsStatus = GpsStatusValue.GPS_ENABLED;
|
||||
sendStatus(oldStatus, gpsStatus);
|
||||
startStatusRunner();
|
||||
} else {
|
||||
onGpsDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be called from service every time the GPS sensor is disabled.
|
||||
*/
|
||||
public void onGpsDisabled() {
|
||||
if (gpsStatus != GpsStatusValue.GPS_DISABLED) {
|
||||
GpsStatusValue oldStatus = gpsStatus;
|
||||
gpsStatus = GpsStatusValue.GPS_DISABLED;
|
||||
sendStatus(oldStatus, gpsStatus);
|
||||
lastTrackPoint = null;
|
||||
lastValidTrackPoint = null;
|
||||
stopStatusRunner();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendStatus(GpsStatusValue prev, GpsStatusValue current) {
|
||||
if (client != null) {
|
||||
client.onGpsStatusChanged(prev, current);
|
||||
}
|
||||
}
|
||||
|
||||
private void startStatusRunner() {
|
||||
if (gpsStatusRunner == null) {
|
||||
gpsStatusRunner = new GpsStatusRunner();
|
||||
gpsStatusRunner.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopStatusRunner() {
|
||||
if (gpsStatusRunner != null) {
|
||||
gpsStatusRunner.stop();
|
||||
gpsStatusRunner = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Duration getIntervalThreshold() {
|
||||
return signalLostThreshold;
|
||||
}
|
||||
|
||||
public GpsStatusValue getGpsStatus() {
|
||||
return gpsStatus;
|
||||
}
|
||||
|
||||
public interface GpsStatusListener {
|
||||
void onGpsStatusChanged(GpsStatusValue prevStatus, GpsStatusValue currentStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* This class handle GPS status according to received locations` and some thresholds.
|
||||
*/
|
||||
class GpsStatusManager {
|
||||
|
||||
private static final String TAG = GpsStatusManager.class.getSimpleName();
|
||||
|
||||
// The duration that GpsStatus waits from minimal interval to consider GPS lost.
|
||||
@VisibleForTesting
|
||||
public static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(30);
|
||||
|
||||
private Distance horizontalAccuracyThreshold;
|
||||
// Threshold for time without points.
|
||||
private Duration signalLostThreshold;
|
||||
|
||||
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
|
||||
private GpsStatusListener client;
|
||||
private final Context context;
|
||||
|
||||
@Nullable
|
||||
private TrackPoint lastTrackPoint = null;
|
||||
|
||||
// Flag to prevent GpsStatus checks two or more locations at the same time.
|
||||
private boolean checking = false;
|
||||
|
||||
private Handler handler;
|
||||
|
||||
public final Runnable gpsStatusTimer = () -> {
|
||||
determineGpsStatusByTime(Instant.now()); //TODO Get now via TrackPointCreator?
|
||||
};
|
||||
|
||||
|
||||
public GpsStatusManager(Context context, GpsStatusListener client, Handler handler) {
|
||||
this.client = client;
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
|
||||
onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval());
|
||||
onMinRecordingIntervalChanged(PreferencesUtils.getMinRecordingInterval());
|
||||
}
|
||||
|
||||
public void start() {
|
||||
client.onGpsStatusChanged(GpsStatusValue.GPS_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* The client that uses GpsStatus has to call this method to stop the Runnable if needed.
|
||||
*/
|
||||
public void stop() {
|
||||
client.onGpsStatusChanged(GpsStatusValue.GPS_NONE);
|
||||
client = null;
|
||||
handler = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to change the bad threshold from outside.
|
||||
*
|
||||
* @param value New preference value to signalBadThreshold.
|
||||
*/
|
||||
public void onRecordingDistanceChanged(@NonNull Distance value) {
|
||||
horizontalAccuracyThreshold = value;
|
||||
}
|
||||
|
||||
public void onMinRecordingIntervalChanged(Duration value) {
|
||||
signalLostThreshold = SIGNAL_LOST_THRESHOLD.plus(value); //TODO Reschedule gpsStatusTimer?
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be called from the client every time a new trackPoint is received.
|
||||
* Receive new trackPoint and calculate the new status if needed.
|
||||
* It look for GPS changes in lastLocation if it's not null. If it's null then look for in lastValidLocation if any.
|
||||
*/
|
||||
//TODO Remove checking; should be synchronized if this is a problem.
|
||||
public void onNewTrackPoint(@NonNull final TrackPoint trackPoint) {
|
||||
if (checking) {
|
||||
return;
|
||||
}
|
||||
|
||||
checking = true;
|
||||
lastTrackPoint = trackPoint;
|
||||
|
||||
determineGpsStatusOnTrackpoint(trackPoint);
|
||||
|
||||
checking = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if lastLocation has new GPS status looking up time and accuracy.
|
||||
* It depends of signalLostThreshold and signalBadThreshold.
|
||||
* If there is any change then it does the change.
|
||||
* Also, it'll run the runnable if signal is bad or stop it if the signal is lost.
|
||||
*/
|
||||
//TODO use MonotonicClock instead of Instant.now()
|
||||
@VisibleForTesting
|
||||
void determineGpsStatusOnTrackpoint(@NonNull TrackPoint lastTrackPoint) {
|
||||
if (lastTrackPoint.fulfillsAccuracy(horizontalAccuracyThreshold)) {
|
||||
if (gpsStatus != GpsStatusValue.GPS_SIGNAL_FIX) {
|
||||
setGpsStatus(GpsStatusValue.GPS_SIGNAL_FIX);
|
||||
scheduleTimer(); //TODO
|
||||
}
|
||||
} else {
|
||||
// GPS signal is to weak; TODO we might need a time-based threshold here as well (i.e., warn after Duration)
|
||||
if (gpsStatus != GpsStatusValue.GPS_SIGNAL_BAD) {
|
||||
setGpsStatus(GpsStatusValue.GPS_SIGNAL_BAD);
|
||||
scheduleTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void determineGpsStatusByTime(Instant now) {
|
||||
if (lastTrackPoint == null) {
|
||||
return;
|
||||
}
|
||||
if (signalLostThreshold.minus(Duration.between(lastTrackPoint.getTime(), now)).isNegative()) {
|
||||
// Too much time without receiving signal -> signal lost.
|
||||
if (gpsStatus != GpsStatusValue.GPS_SIGNAL_LOST) {
|
||||
setGpsStatus(GpsStatusValue.GPS_SIGNAL_LOST);
|
||||
}
|
||||
return;
|
||||
}
|
||||
scheduleTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be called from the client every time the GPS sensor is enabled.
|
||||
* Anyway, it checks that GPS is enabled because the client assumes that if it's on then GPS is enabled but user can disable GPS by hand.
|
||||
*/
|
||||
public void onGpsEnabled() {
|
||||
if (gpsStatus == GpsStatusValue.GPS_ENABLED) {
|
||||
return;
|
||||
}
|
||||
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
setGpsStatus(GpsStatusValue.GPS_ENABLED);
|
||||
scheduleTimer();
|
||||
} else {
|
||||
onGpsDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method must be called from service every time the GPS sensor is disabled.
|
||||
*/
|
||||
public void onGpsDisabled() {
|
||||
if (gpsStatus == GpsStatusValue.GPS_DISABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
setGpsStatus(GpsStatusValue.GPS_DISABLED);
|
||||
lastTrackPoint = null;
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
private void setGpsStatus(GpsStatusValue current) {
|
||||
gpsStatus = current;
|
||||
if (client != null) {
|
||||
client.onGpsStatusChanged(current);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleTimer() {
|
||||
handler.removeCallbacks(gpsStatusTimer);
|
||||
handler.postDelayed(gpsStatusTimer, signalLostThreshold.toMillis());
|
||||
}
|
||||
|
||||
private void stopTimer() {
|
||||
handler.removeCallbacks(gpsStatusTimer);
|
||||
}
|
||||
|
||||
public interface GpsStatusListener {
|
||||
void onGpsStatusChanged(GpsStatusValue currentStatus);
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,12 @@ import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
|
||||
*/
|
||||
public class TrackPointCreator implements SensorManager.SensorDataSetChangeObserver {
|
||||
public class TrackPointCreator {
|
||||
|
||||
private static final String TAG = TrackPointCreator.class.getSimpleName();
|
||||
|
||||
@@ -32,26 +33,21 @@ public class TrackPointCreator implements SensorManager.SensorDataSetChangeObser
|
||||
|
||||
@NonNull
|
||||
private Clock clock = new MonotonicClock();
|
||||
|
||||
private final GPSManager gpsManager;
|
||||
private SensorManager sensorManager;
|
||||
|
||||
public TrackPointCreator(Callback service, Context context, Handler handler) {
|
||||
this.service = service;
|
||||
this.gpsManager = new GPSManager(this);
|
||||
this.sensorManager = new SensorManager(context, handler, this);
|
||||
this.sensorManager = new SensorManager(this);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TrackPointCreator(GPSManager gpsManager, Callback service) {
|
||||
TrackPointCreator(Callback service) {
|
||||
this.service = service;
|
||||
this.gpsManager = gpsManager;
|
||||
}
|
||||
|
||||
public synchronized void start(@NonNull Context context, @NonNull Handler handler) {
|
||||
this.context = context;
|
||||
|
||||
gpsManager.start(context, handler);
|
||||
sensorManager.start(context, handler);
|
||||
}
|
||||
|
||||
@@ -73,8 +69,7 @@ public class TrackPointCreator implements SensorManager.SensorDataSetChangeObser
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
gpsManager.stop(context);
|
||||
|
||||
sensorManager.stop(context);
|
||||
this.context = null;
|
||||
}
|
||||
|
||||
@@ -85,7 +80,6 @@ public class TrackPointCreator implements SensorManager.SensorDataSetChangeObser
|
||||
/**
|
||||
* Got a new TrackPoint from Bluetooth only; contains no GPS location.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void onChange(@NonNull SensorDataSet unused) {
|
||||
onNewTrackPoint(new TrackPoint(TrackPoint.Type.SENSORPOINT, createNow()));
|
||||
}
|
||||
@@ -94,7 +88,7 @@ public class TrackPointCreator implements SensorManager.SensorDataSetChangeObser
|
||||
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
addSensorData(trackPoint);
|
||||
|
||||
boolean stored = service.newTrackPoint(trackPoint, gpsManager.getThresholdHorizontalAccuracy());
|
||||
boolean stored = service.newTrackPoint(trackPoint, PreferencesUtils.getThresholdHorizontalAccuracy()); //TODO Cache preference for performance
|
||||
if (stored) {
|
||||
reset();
|
||||
}
|
||||
@@ -142,21 +136,16 @@ public class TrackPointCreator implements SensorManager.SensorDataSetChangeObser
|
||||
return sensorManager;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setSensorManager(SensorManager sensorManager) {
|
||||
this.sensorManager = sensorManager;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setClock(@NonNull String time) {
|
||||
this.clock = Clock.fixed(Instant.parse(time), ZoneId.of("CET"));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setClock(@NonNull Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public GPSManager getGpsHandler() {
|
||||
return gpsManager;
|
||||
}
|
||||
|
||||
void sendGpsStatus(GpsStatusValue gpsStatusValue) {
|
||||
service.newGpsStatus(gpsStatusValue);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ public class DefaultsSettingsFragment extends PreferenceFragmentCompat implement
|
||||
|
||||
int entriesId = switch (unitSystem) {
|
||||
case METRIC -> R.array.stats_rate_metric_options;
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> R.array.stats_rate_imperial_options;
|
||||
case IMPERIAL_FEET, IMPERIAL_METER, NAUTICAL_IMPERIAL ->
|
||||
R.array.stats_rate_imperial_options;
|
||||
};
|
||||
|
||||
String[] entries = getResources().getStringArray(entriesId);
|
||||
|
||||
@@ -228,7 +228,7 @@ public class PreferencesUtils {
|
||||
if (!Locale.US.equals(Locale.getDefault())) {
|
||||
setUnit(UnitSystem.METRIC);
|
||||
} else {
|
||||
setUnit(UnitSystem.IMPERIAL);
|
||||
setUnit(UnitSystem.IMPERIAL_FEET);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,7 +455,7 @@ public class PreferencesUtils {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
}
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||
case IMPERIAL_FEET, NAUTICAL_IMPERIAL -> {
|
||||
if (value == recordingDistanceIntervalDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else {
|
||||
@@ -499,7 +499,7 @@ public class PreferencesUtils {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
}
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||
case IMPERIAL_FEET, NAUTICAL_IMPERIAL -> {
|
||||
if (value == maxRecordingDistanceDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else {
|
||||
@@ -576,7 +576,7 @@ public class PreferencesUtils {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
}
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||
case IMPERIAL_FEET, NAUTICAL_IMPERIAL -> {
|
||||
if (value == recordingGPSAccuracyDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else if (value == recordingGPSAccuracyExcellent) {
|
||||
@@ -618,7 +618,7 @@ public class PreferencesUtils {
|
||||
entries[i] = resources.getString(R.string.value_float_kilometer_hour, value);
|
||||
}
|
||||
}
|
||||
case IMPERIAL -> {
|
||||
case IMPERIAL_FEET -> {
|
||||
double valueMPH = Speed.ofKMH(value).toMPH();
|
||||
if (value == idleSpeedDefault) {
|
||||
entries[i] = resources.getString(R.string.value_float_mile_hour_recommended, valueMPH);
|
||||
|
||||
@@ -40,7 +40,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
fragment = new MainSettingsFragment();
|
||||
}
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.menu_settings));
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, fragment).commit();
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(profile);
|
||||
// viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(profile); TODO
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
|
||||
ItemTouchHelper itemTouchhelper = new ItemTouchHelper(recyclerViewSwipeDeleteCallback);
|
||||
itemTouchhelper.attachToRecyclerView(recyclerView);
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.custom_layout_list_title));
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import de.dennisguse.opentracks.R;
|
||||
|
||||
public enum UnitSystem {
|
||||
METRIC(R.string.stats_units_metric),
|
||||
IMPERIAL(R.string.stats_units_imperial),
|
||||
|
||||
IMPERIAL_FEET(R.string.stats_units_imperial_feet),
|
||||
IMPERIAL_METER(R.string.stats_units_imperial_meter),
|
||||
|
||||
NAUTICAL_IMPERIAL(R.string.stats_units_nautical); // Nautical miles with feet
|
||||
|
||||
private final int preferenceIdentifier;
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
final Bundle b = new Bundle(1);
|
||||
b.putString(ARG_KEY, preferenceKey);
|
||||
b.putParcelableArrayList(ARG_BLE_SERVICE_UUIDS, new ArrayList<>(sensorUUIDs.stream()
|
||||
.map(ServiceMeasurementUUID::getServiceUUID)
|
||||
.map(ServiceMeasurementUUID::serviceUUID)
|
||||
.map(ParcelUuid::new)
|
||||
.collect(Collectors.toList())));
|
||||
fragment.setArguments(b);
|
||||
|
||||
@@ -72,7 +72,7 @@ public class DescriptionGenerator {
|
||||
builder.append(paragraphSeparator);
|
||||
|
||||
writeString(track.getName(), builder, R.string.generic_name_line, lineBreak);
|
||||
writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak);
|
||||
writeString(track.getActivityType(), builder, R.string.description_activity_type, lineBreak);
|
||||
writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak);
|
||||
builder.append(generateTrackStatisticsDescription(track.getTrackStatistics(), html));
|
||||
|
||||
@@ -211,7 +211,7 @@ public class DescriptionGenerator {
|
||||
@VisibleForTesting
|
||||
void writePace(Speed speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
Pair<String, String> paceInMetrics = SpeedFormatter.Builder().setUnit(UnitSystem.METRIC).setReportSpeedOrPace(false).build(context).getSpeedParts(speed);
|
||||
Pair<String, String> paceInImperial = SpeedFormatter.Builder().setUnit(UnitSystem.IMPERIAL).setReportSpeedOrPace(false).build(context).getSpeedParts(speed);
|
||||
Pair<String, String> paceInImperial = SpeedFormatter.Builder().setUnit(UnitSystem.IMPERIAL_FEET).setReportSpeedOrPace(false).build(context).getSpeedParts(speed);
|
||||
|
||||
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
|
||||
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user