Files
OpenTracks/moje_zmiany_opentracks.patch
T
karol 7c6141f98c feat: prioritize Bluetooth sensor data over GPS
- Restructured sensor logic to give absolute priority to Bluetooth sensors, largely ignoring GPS data for distance and speed calculations.
- Implemented downstream UI expansions, custom recording layouts, and new settings to reflect and control the new sensor-first flow.
- Added MediaNotificationListenerService and integrated media playback tracking within statistics.
- Modified VoiceAnnouncementUtils to harmonize announcements with the updated architecture.
- Added 'About This Fork' section to the README.

Note: These modifications were co-created with Antigravity AI.
2026-09-11 14:10:42 +02:00

1433 lines
74 KiB
Diff

diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java
index d0e56ff4d..7bf360409 100644
--- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java
+++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java
@@ -67,6 +67,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
private Snackbar snackbar;
+ private Snackbar sensorWarningSnackbar;
// The following are setFrequency in onCreate
private ContentProviderUtils contentProviderUtils;
@@ -86,6 +87,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
service.getGpsStatusObservable()
.observe(TrackRecordingActivity.this, this::onGpsStatusChanged);
+ service.getSensorDisconnectedWarningObservable()
+ .observe(TrackRecordingActivity.this, this::onSensorDisconnectedWarningChanged);
+
if (!service.isRecording()) {
finish();
return;
@@ -408,4 +412,27 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
});
snackbar.show();
}
+
+ private void onSensorDisconnectedWarningChanged(boolean show) {
+ if (show) {
+ if (sensorWarningSnackbar == null) {
+ sensorWarningSnackbar = Snackbar
+ .make(viewBinding.trackRecordingCoordinatorLayout,
+ getString(R.string.sensor_disconnected_warning),
+ Snackbar.LENGTH_INDEFINITE)
+ .setAction(getString(R.string.sensor_disconnected_switch_action), v -> {
+ if (trackRecordingServiceConnection != null && trackRecordingServiceConnection.getTrackRecordingService() != null) {
+ trackRecordingServiceConnection.getTrackRecordingService().setOverrideSensorWithGPS(true);
+ }
+ });
+ }
+ if (!sensorWarningSnackbar.isShown()) {
+ sensorWarningSnackbar.show();
+ }
+ } else {
+ if (sensorWarningSnackbar != null && sensorWarningSnackbar.isShown()) {
+ sensorWarningSnackbar.dismiss();
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/de/dennisguse/opentracks/data/statistics/SegmentStatisticUpdater.java b/src/main/java/de/dennisguse/opentracks/data/statistics/SegmentStatisticUpdater.java
index 500d5ce1a..90f60179c 100644
--- a/src/main/java/de/dennisguse/opentracks/data/statistics/SegmentStatisticUpdater.java
+++ b/src/main/java/de/dennisguse/opentracks/data/statistics/SegmentStatisticUpdater.java
@@ -151,12 +151,14 @@ public class SegmentStatisticUpdater {
}
public void addMovingTime(TrackPoint trackPoint, TrackPoint lastTrackPoint) {
- Duration movingDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
+ addMovingTime(Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()));
+ }
- if (movingDuration.isNegative()) {
- throw new RuntimeException("Moving time cannot be negative: " + lastTrackPoint.getTime() + " is after " + trackPoint.getTime());
+ public void addMovingTime(Duration duration) {
+ if (duration.isNegative()) {
+ throw new RuntimeException("Moving time cannot be negative");
}
- this.movingDuration = this.movingDuration.plus(movingDuration);
+ movingDuration = movingDuration.plus(duration);
}
private Speed getAverageMovingSpeed() {
diff --git a/src/main/java/de/dennisguse/opentracks/data/statistics/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/data/statistics/TrackStatisticsUpdater.java
index 46ad43431..55fe47374 100644
--- a/src/main/java/de/dennisguse/opentracks/data/statistics/TrackStatisticsUpdater.java
+++ b/src/main/java/de/dennisguse/opentracks/data/statistics/TrackStatisticsUpdater.java
@@ -54,6 +54,7 @@ public class TrackStatisticsUpdater {
private TrackPoint lastTrackPoint;
private boolean isIdle;
+ private boolean forceZeroDistance;
public TrackStatisticsUpdater(@NonNull TrackPoint trackPoint) {
addTrackPoint(trackPoint);
@@ -78,6 +79,7 @@ public class TrackStatisticsUpdater {
this.lastTrackPoint = toCopy.lastTrackPoint;
this.isIdle = toCopy.isIdle;
+ this.forceZeroDistance = toCopy.forceZeroDistance;
resetAverageHeartRate();
addTrackPoint(tmp);
@@ -94,6 +96,10 @@ public class TrackStatisticsUpdater {
return isIdle;
}
+ public void setForceZeroDistance(boolean forceZeroDistance) {
+ this.forceZeroDistance = forceZeroDistance;
+ }
+
public Statistics getCurrentSegment() {
return currentSegment.getStatistics();
}
@@ -147,22 +153,15 @@ public class TrackStatisticsUpdater {
Distance movingDistance = null;
if (trackPoint.sensorDistance() != null) {
movingDistance = trackPoint.sensorDistance();
+ } else if (forceZeroDistance) {
+ movingDistance = Distance.ZERO;
} else if (lastTrackPoint != null
&& lastTrackPoint.position().hasLocation()
&& trackPoint.position().hasLocation()) {
// GPS-based distance/speed
movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
}
- if (movingDistance != null) {
- currentSegment.addTotalDistance(movingDistance);
- }
-
- if (!isIdle
- && !trackPoint.isSegmentManualStart()
- && lastTrackPoint != null) {
- currentSegment.addMovingTime(trackPoint, lastTrackPoint);
- }
-
+ boolean isIdleTransition = false;
if (trackPoint.isIdleTriggered()) {
isIdle = true;
} else if (isIdle) {
@@ -170,6 +169,26 @@ public class TrackStatisticsUpdater {
if (movingDistance != null
&& movingDistance.greaterOrEqualThan(PreferencesUtils.getRecordingDistanceInterval())) {
isIdle = false;
+ isIdleTransition = true;
+ }
+ }
+
+ if (movingDistance != null && !isIdle) {
+ currentSegment.addTotalDistance(movingDistance);
+ }
+
+ if (!isIdle
+ && !trackPoint.isSegmentManualStart()
+ && lastTrackPoint != null) {
+ if (isIdleTransition) {
+ if (trackPoint.position().hasSpeed() && trackPoint.position().speed().toMPS() > 0) {
+ double timeSeconds = movingDistance.toM() / trackPoint.position().speed().toMPS();
+ currentSegment.addMovingTime(Duration.ofMillis((long)(timeSeconds * 1000)));
+ } else {
+ currentSegment.addMovingTime(Duration.ofMillis((long)(movingDistance.toM() * 1000)));
+ }
+ } else {
+ currentSegment.addMovingTime(trackPoint, lastTrackPoint);
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/publicapi/DataProvider.java b/src/main/java/de/dennisguse/opentracks/publicapi/DataProvider.java
index 1003dcbd9..eab156963 100644
--- a/src/main/java/de/dennisguse/opentracks/publicapi/DataProvider.java
+++ b/src/main/java/de/dennisguse/opentracks/publicapi/DataProvider.java
@@ -189,6 +189,17 @@ public class DataProvider {
Log.i(TAG, "Starting dashboard activity with generic intent (package=" + targetPackage + ", class=" + targetClass + ")");
}
+ // Explicitly grant URI permissions to all packages that can resolve this intent.
+ // This works around issues where ClipData multiple URI grants fail on some Android versions.
+ android.content.pm.PackageManager packageManager = context.getPackageManager();
+ java.util.List<android.content.pm.ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, android.content.pm.PackageManager.MATCH_DEFAULT_ONLY);
+ for (android.content.pm.ResolveInfo resolveInfo : resolveInfos) {
+ String packageName = resolveInfo.activityInfo.packageName;
+ for (Uri uri : uris) {
+ context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ }
+
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java b/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java
index 31433932d..0a7c6de8c 100644
--- a/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java
+++ b/src/main/java/de/dennisguse/opentracks/sensors/SensorHandler.java
@@ -115,7 +115,8 @@ public abstract class SensorHandler<AggregatorInput, AggregatorOutput> implement
return;
}
- SensorHandler.this.aggregator.resetAggregated();
+ SensorHandler.this.aggregator = null;
+ sensorManager.onChange();
}
@Override
@@ -130,5 +131,6 @@ public abstract class SensorHandler<AggregatorInput, AggregatorOutput> implement
@Override
public void onDisconnected() {
SensorHandler.this.aggregator = null;
+ sensorManager.onChange();
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java b/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java
index b2a9ba1bc..7429b048a 100644
--- a/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java
+++ b/src/main/java/de/dennisguse/opentracks/sensors/SensorManager.java
@@ -239,6 +239,9 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
}
public SensorDataSet getSensorDataSet(Instant now) {
+ if (!isStarted()) {
+ return new SensorDataSet(new SensorData<>(Position.of(now), ""), null, null, null, null, null, null, null);
+ }
//We always need a Position with now
SensorData<Position> gpsSensorData = gpsHandler.getSensorData(now);
if (gpsSensorData != null && gpsSensorData.data() != null) {
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java
index f10fcace9..42ead636b 100644
--- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java
+++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java
@@ -5,6 +5,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import java.time.Duration;
+import java.time.Instant;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.sensors.UintUtils;
@@ -18,17 +19,20 @@ public class AggregatorCyclingCadence extends Aggregator<CyclingCadenceBluetooth
super(sensorAddress, sensorName);
}
+ private Instant lastValidUpdate;
+
@Override
protected void computeValue(@NonNull Raw<CyclingCadenceBluetooth.CyclingCadenceMeasurement> current) {
if (current.value() instanceof CyclingCadenceBluetooth.DirectCadenceData directCadenceData) {
output = directCadenceData.cadence();
+ lastValidUpdate = current.time();
return;
}
- computeRawCadence((CyclingCadenceBluetooth.CrankData) current.value());
+ computeRawCadence((CyclingCadenceBluetooth.CrankData) current.value(), current.time());
}
- private void computeRawCadence(@NonNull CyclingCadenceBluetooth.CrankData currentRaw) {
+ private void computeRawCadence(@NonNull CyclingCadenceBluetooth.CrankData currentRaw, Instant now) {
CyclingCadenceBluetooth.CrankData previousRaw = getPreviousRaw();
if (previousRaw == null) return;
@@ -36,6 +40,9 @@ public class AggregatorCyclingCadence extends Aggregator<CyclingCadenceBluetooth
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
if (timeDiff.isZero()) {
+ if (lastValidUpdate != null && Duration.between(lastValidUpdate, now).compareTo(getMaxSensorAge()) > 0) {
+ resetOutdated();
+ }
return;
}
if (timeDiff.isNegative()) {
@@ -52,9 +59,15 @@ public class AggregatorCyclingCadence extends Aggregator<CyclingCadenceBluetooth
}
long crankDiff = UintUtils.diff(currentRaw.crankRevolutionsCount(), previousRaw.crankRevolutionsCount(), UintUtils.UINT32_MAX);
+ lastValidUpdate = now;
output = Cadence.of(crankDiff, timeDiff);
}
+ @Override
+ protected void resetOutdated() {
+ output = Cadence.of(0f);
+ }
+
private CyclingCadenceBluetooth.CrankData getPreviousRaw() {
if (previous == null) {
return null;
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingDistanceSpeed.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingDistanceSpeed.java
index db7b65d7c..d80381f46 100644
--- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingDistanceSpeed.java
+++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingDistanceSpeed.java
@@ -5,6 +5,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import java.time.Duration;
+import java.time.Instant;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
@@ -21,9 +22,12 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<CyclingDistanceSp
super(sensorAddress, sensorName);
}
+ private Instant lastValidUpdate;
+
@Override
protected void computeValue(@NonNull Raw<CyclingDistanceSpeedBluetooth.WheelData> current) {
if (previous == null) {
+ lastValidUpdate = current.time();
return;
}
@@ -31,6 +35,9 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<CyclingDistanceSp
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
if (timeDiff.isZero()) {
+ if (lastValidUpdate != null && Duration.between(lastValidUpdate, current.time()).compareTo(getMaxSensorAge()) > 0) {
+ resetOutdated();
+ }
return;
}
if (timeDiff.isNegative()) {
@@ -52,6 +59,7 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<CyclingDistanceSp
distanceOverall = distance.plus(output.distanceOverall);
}
Speed speed_mps = Speed.of(distance, timeDiff);
+ lastValidUpdate = current.time();
output = new Data(distance, distanceOverall, speed_mps);
}
diff --git a/src/main/java/de/dennisguse/opentracks/services/RecordingData.java b/src/main/java/de/dennisguse/opentracks/services/RecordingData.java
index b85449e9f..34b409688 100644
--- a/src/main/java/de/dennisguse/opentracks/services/RecordingData.java
+++ b/src/main/java/de/dennisguse/opentracks/services/RecordingData.java
@@ -16,9 +16,13 @@ public record RecordingData(
*/
TrackPoint latestTrackPoint,
SensorDataSet sensorDataSet,
- Statistics currentSegment) {
+ Statistics currentSegment,
+ String autoPauseStatus,
+ String idleTimer,
+ String walkingTimer,
+ String markerTimer) {
- public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null, null);
+ public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null, null, "", "", "", "");
public Statistics trackStatistics() {
return track.statistics();
diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
index 3a4197aee..1abb943f8 100644
--- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
+++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
@@ -36,10 +36,12 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
private static final AltitudeCorrectionManager ALTITUDE_CORRECTION_MANAGER = new AltitudeCorrectionManager();
private final Runnable ON_IDLE = this::onIdle;
+ private final Runnable ON_LONG_BREAK = this::onLongBreak;
private final ContentProviderUtils contentProviderUtils;
private final Context context;
private final IdleObserver idleObserver;
+ private final SensorDisconnectObserver sensorDisconnectObserver;
private final Handler handler;
@@ -59,9 +61,25 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
private TrackPoint lastStoredTrackPoint;
private TrackPoint lastStoredTrackPointWithLocation;
- TrackRecordingManager(Context context, TrackPointCreator trackPointCreator, IdleObserver idleObserver, Handler handler) {
+ private boolean hasSensorBeenConnected = false;
+ private boolean isSensorDisconnectedWarningActive = false;
+
+ private boolean autoPauseOnCadence0;
+ private de.dennisguse.opentracks.data.models.Speed autoPauseCadence0Speed;
+ private boolean hasCadenceSensorBeenConnected = false;
+ private String autoPauseStatus = "N/A";
+
+ private java.time.Instant idleTimeoutScheduledAt;
+ private java.time.Instant walkingStartedAt;
+ private java.time.Instant idleStartedAt;
+ private boolean longBreakRecorded = false;
+
+ private boolean gpsOverriden = false;
+
+ TrackRecordingManager(Context context, TrackPointCreator trackPointCreator, IdleObserver idleObserver, SensorDisconnectObserver sensorDisconnectObserver, Handler handler) {
this.context = context;
this.idleObserver = idleObserver;
+ this.sensorDisconnectObserver = sensorDisconnectObserver;
this.trackPointCreator = trackPointCreator;
this.handler = handler;
contentProviderUtils = new ContentProviderUtils(context);
@@ -97,6 +115,23 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
onNewTrackPoint(segmentStartTrackPoint);
+ handler.post(() -> {
+ if (trackId != null) {
+ onIdle();
+ }
+ });
+ String address = de.dennisguse.opentracks.settings.PreferencesUtils.getString(de.dennisguse.opentracks.R.string.settings_sensor_bluetooth_cycling_speed_key, de.dennisguse.opentracks.sensors.driver.SensorType.NONE.getPreferenceValue());
+ if (!de.dennisguse.opentracks.sensors.driver.SensorType.NONE.getPreferenceValue().equals(address)) {
+ handler.postDelayed(() -> {
+ if (trackId != null && !hasSensorBeenConnected && !isSensorDisconnectedWarningActive) {
+ isSensorDisconnectedWarningActive = true;
+ if (sensorDisconnectObserver != null) {
+ sensorDisconnectObserver.onSensorDisconnectedWarning(true);
+ }
+ }
+ }, 10000);
+ }
+
return trackId;
}
@@ -116,6 +151,23 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
reset();
+ handler.post(() -> {
+ if (this.trackId != null) {
+ onIdle();
+ }
+ });
+ String address = de.dennisguse.opentracks.settings.PreferencesUtils.getString(de.dennisguse.opentracks.R.string.settings_sensor_bluetooth_cycling_speed_key, de.dennisguse.opentracks.sensors.driver.SensorType.NONE.getPreferenceValue());
+ if (!de.dennisguse.opentracks.sensors.driver.SensorType.NONE.getPreferenceValue().equals(address)) {
+ handler.postDelayed(() -> {
+ if (trackId != null && !hasSensorBeenConnected && !isSensorDisconnectedWarningActive) {
+ isSensorDisconnectedWarningActive = true;
+ if (sensorDisconnectObserver != null) {
+ sensorDisconnectObserver.onSensorDisconnectedWarning(true);
+ }
+ }
+ }, 10000);
+ }
+
return true;
}
@@ -125,6 +177,12 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
trackId = null;
trackStatisticsUpdater = null;
+
+ idleTimeoutScheduledAt = null;
+ walkingStartedAt = null;
+ idleStartedAt = null;
+ longBreakRecorded = false;
+ handler.removeCallbacks(ON_LONG_BREAK);
reset();
}
@@ -144,18 +202,69 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
TrackBuilder trackBuilder = new TrackBuilder(track);
trackBuilder.setStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
-
- return new RecordingData(trackBuilder.getTrack(), TrackPointWithEGM2008, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
+
+ java.time.Instant now = java.time.Instant.now();
+ String idleTimer = "";
+ if (idleTimeoutScheduledAt != null && !"IDLE".equals(autoPauseStatus) && !"WALKING".equals(autoPauseStatus)) {
+ java.time.Duration d = java.time.Duration.between(now, idleTimeoutScheduledAt);
+ if (!d.isNegative()) idleTimer = de.dennisguse.opentracks.util.StringUtils.formatElapsedTime(d);
+ }
+
+ String walkingTimer = "";
+ if (walkingStartedAt != null && "WALKING".equals(autoPauseStatus)) {
+ java.time.Duration d = java.time.Duration.between(walkingStartedAt, now);
+ if (!d.isNegative()) walkingTimer = de.dennisguse.opentracks.util.StringUtils.formatElapsedTime(d);
+ }
+
+ String markerTimer = "";
+ if (idleStartedAt != null && "IDLE".equals(autoPauseStatus)) {
+ java.time.Duration d = java.time.Duration.between(idleStartedAt, now);
+ java.time.Duration markerDuration = de.dennisguse.opentracks.settings.PreferencesUtils.getMarkerIdleDuration();
+ if (longBreakRecorded || d.compareTo(markerDuration) >= 0) {
+ d = markerDuration;
+ }
+ if (!d.isNegative()) markerTimer = de.dennisguse.opentracks.util.StringUtils.formatElapsedTime(d);
+ }
+
+ return new RecordingData(trackBuilder.getTrack(), TrackPointWithEGM2008, current.second, tmpTrackStatisticsUpdater.getCurrentSegment(), autoPauseStatus, idleTimer, walkingTimer, markerTimer);
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public synchronized void onIdle() {
Log.d(TAG, "Becoming idle");
- onNewTrackPoint(trackPointCreator.createIdle());
+ TrackPoint idlePoint = trackPointCreator.createIdle();
+
+ boolean preventIdle = de.dennisguse.opentracks.settings.PreferencesUtils.shouldPreventIdleIfCadence();
+ if (preventIdle && hasCadenceSensorBeenConnected) {
+ if (idlePoint.cadence() != null && idlePoint.cadence().value_rpm() > 0) {
+ scheduleNewIdleTimeout();
+ return;
+ }
+ }
+
+ if (!"IDLE".equals(autoPauseStatus)) {
+ idleStartedAt = java.time.Instant.now();
+ longBreakRecorded = false;
+ java.time.Duration markerDuration = de.dennisguse.opentracks.settings.PreferencesUtils.getMarkerIdleDuration();
+ handler.removeCallbacks(ON_LONG_BREAK);
+ handler.postDelayed(ON_LONG_BREAK, markerDuration.toMillis());
+ }
+ autoPauseStatus = "IDLE";
+
+ onNewTrackPoint(idlePoint);
idleObserver.onIdle();
}
+ private synchronized void onLongBreak() {
+ if ("IDLE".equals(autoPauseStatus) && idleStartedAt != null && !longBreakRecorded) {
+ longBreakRecorded = true;
+ TrackPoint trackPoint = trackPointCreator.createSegmentStartManual();
+ insertTrackPoint(trackPoint, true);
+ idleObserver.onLongBreak();
+ }
+ }
+
/**
* @return TrackPoint was stored?
*/
@@ -167,9 +276,36 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
lastTrackPointUIWithAltitude = trackPoint;
}
+ boolean sensorHasDistance = trackPoint.sensorDistance() != null;
+ if (sensorHasDistance) {
+ hasSensorBeenConnected = true;
+ if (isSensorDisconnectedWarningActive) {
+ isSensorDisconnectedWarningActive = false;
+ sensorDisconnectObserver.onSensorDisconnectedWarning(false);
+ sensorDisconnectObserver.onSensorConnected();
+ }
+ } else if (hasSensorBeenConnected) {
+ if (!isSensorDisconnectedWarningActive) {
+ isSensorDisconnectedWarningActive = true;
+ sensorDisconnectObserver.onSensorDisconnectedWarning(true);
+ }
+ }
+
+ if (trackStatisticsUpdater != null) {
+ String address = de.dennisguse.opentracks.settings.PreferencesUtils.getString(de.dennisguse.opentracks.R.string.settings_sensor_bluetooth_cycling_speed_key, de.dennisguse.opentracks.sensors.driver.SensorType.NONE.getPreferenceValue());
+ boolean isSensorExpected = !de.dennisguse.opentracks.sensors.driver.SensorType.NONE.getPreferenceValue().equals(address);
+ trackStatisticsUpdater.setForceZeroDistance(hasSensorBeenConnected || (isSensorExpected && !gpsOverriden));
+ }
+
if (trackPoint.type() == TrackPoint.Type.IDLE) {
+ longBreakRecorded = false;
insertTrackPoint(trackPoint, true);
handler.removeCallbacks(ON_IDLE);
+
+ long durationMs = PreferencesUtils.getMarkerIdleDuration().toMillis();
+ if (durationMs > 0) {
+ handler.postDelayed(ON_LONG_BREAK, durationMs);
+ }
return true;
}
//Storing trackPoint
@@ -200,11 +336,29 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
return true;
}
+ if (trackPoint.cadence() != null) {
+ hasCadenceSensorBeenConnected = true;
+ }
+
+ boolean isWalkingWithBike = false;
+ if (autoPauseOnCadence0 && hasCadenceSensorBeenConnected && autoPauseCadence0Speed != null) {
+ if (trackPoint.cadence() == null || trackPoint.cadence().getRPM() == 0) {
+ if (trackPoint.position().hasSpeed() && trackPoint.position().speed().lessThan(autoPauseCadence0Speed)) {
+ isWalkingWithBike = true;
+ }
+ }
+ }
+
Distance distanceToLastStoredTrackPoint;
- if (trackPoint.position().hasLocation() && !lastStoredTrackPoint.position().hasLocation()) {
- distanceToLastStoredTrackPoint = trackPoint.distanceToPreviousFromLocation(lastStoredTrackPointWithLocation);
+ if (isWalkingWithBike) {
+ distanceToLastStoredTrackPoint = Distance.ZERO;
+ autoPauseStatus = "WALKING";
} else {
- distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint);
+ if (trackPoint.position().hasLocation() && !lastStoredTrackPoint.position().hasLocation()) {
+ distanceToLastStoredTrackPoint = trackPoint.distanceToPreviousFromLocation(lastStoredTrackPointWithLocation);
+ } else {
+ distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint);
+ }
}
if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) {
@@ -212,6 +366,20 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
insertTrackPoint(trackPoint, true);
scheduleNewIdleTimeout();
+ if (!isWalkingWithBike) {
+ if ("IDLE".equals(autoPauseStatus)) {
+ handler.removeCallbacks(ON_LONG_BREAK);
+ }
+ autoPauseStatus = "REC";
+ } else {
+ if (!"WALKING".equals(autoPauseStatus)) {
+ walkingStartedAt = java.time.Instant.now();
+ if ("IDLE".equals(autoPauseStatus)) {
+ handler.removeCallbacks(ON_LONG_BREAK);
+ }
+ }
+ autoPauseStatus = "WALKING";
+ }
return true;
}
@@ -219,6 +387,20 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
insertTrackPoint(trackPoint, false);
scheduleNewIdleTimeout();
+ if (!isWalkingWithBike) {
+ if ("IDLE".equals(autoPauseStatus)) {
+ handler.removeCallbacks(ON_LONG_BREAK);
+ }
+ autoPauseStatus = "REC";
+ } else {
+ if (!"WALKING".equals(autoPauseStatus)) {
+ walkingStartedAt = java.time.Instant.now();
+ if ("IDLE".equals(autoPauseStatus)) {
+ handler.removeCallbacks(ON_LONG_BREAK);
+ }
+ }
+ autoPauseStatus = "WALKING";
+ }
return true;
}
@@ -235,6 +417,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
}
handler.removeCallbacks(ON_IDLE);
handler.postDelayed(ON_IDLE, idleDuration.toMillis());
+ idleTimeoutScheduledAt = java.time.Instant.now().plus(idleDuration);
}
Statistics getTrackStatistics() {
@@ -284,6 +467,13 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
lastStoredTrackPoint = null;
lastStoredTrackPointWithLocation = null;
+
+ hasSensorBeenConnected = false;
+ isSensorDisconnectedWarningActive = false;
+ hasCadenceSensorBeenConnected = false;
+ if (sensorDisconnectObserver != null) {
+ sensorDisconnectObserver.onSensorDisconnectedWarning(false);
+ }
}
@Override
@@ -297,13 +487,39 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
if (PreferencesUtils.isKey(R.string.idle_duration_key, key)) {
idleDuration = PreferencesUtils.getIdleDurationTimeout();
}
+ if (PreferencesUtils.isKey(R.string.settings_recording_auto_pause_on_cadence_0_key, key)) {
+ autoPauseOnCadence0 = PreferencesUtils.shouldAutoPauseOnCadence0();
+ }
+ if (PreferencesUtils.isKey(R.string.settings_recording_auto_pause_on_cadence_0_speed_key, key)) {
+ autoPauseCadence0Speed = PreferencesUtils.getAutoPauseCadence0Speed();
+ }
}
public TrackPoint getLastStoredTrackPointWithLocation() {
return lastStoredTrackPointWithLocation;
}
+ public void setOverrideSensorWithGPS(boolean override) {
+ if (override) {
+ hasSensorBeenConnected = false;
+ gpsOverriden = true;
+ if (isSensorDisconnectedWarningActive) {
+ isSensorDisconnectedWarningActive = false;
+ sensorDisconnectObserver.onSensorDisconnectedWarning(false);
+ }
+ if (trackStatisticsUpdater != null) {
+ trackStatisticsUpdater.setForceZeroDistance(false);
+ }
+ }
+ }
+
public interface IdleObserver {
void onIdle();
+ void onLongBreak();
+ }
+
+ public interface SensorDisconnectObserver {
+ void onSensorDisconnectedWarning(boolean show);
+ void onSensorConnected();
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
index 8ca4c5a4a..c28f3b224 100644
--- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
+++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
@@ -44,7 +44,7 @@ import de.dennisguse.opentracks.services.announcement.VoiceAnnouncementManager;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
-public class TrackRecordingService extends Service implements TrackPointCreator.Callback, SharedPreferences.OnSharedPreferenceChangeListener, TrackRecordingManager.IdleObserver {
+public class TrackRecordingService extends Service implements TrackPointCreator.Callback, SharedPreferences.OnSharedPreferenceChangeListener, TrackRecordingManager.IdleObserver, TrackRecordingManager.SensorDisconnectObserver {
private static final String TAG = TrackRecordingService.class.getSimpleName();
@@ -84,6 +84,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private RecordingStatus recordingStatus;
private MutableLiveData<RecordingStatus> recordingStatusObservable;
private MutableLiveData<GpsStatusValue> gpsStatusObservable;
+ private MutableLiveData<Boolean> sensorDisconnectedWarningObservable;
private MutableLiveData<RecordingData> recordingDataObservable;
private Handler handler;
@@ -104,10 +105,11 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
recordingStatusObservable = new MutableLiveData<>();
updateRecordingStatus(STATUS_DEFAULT);
gpsStatusObservable = new MutableLiveData<>(STATUS_GPS_DEFAULT);
+ sensorDisconnectedWarningObservable = new MutableLiveData<>(false);
recordingDataObservable = new MutableLiveData<>(RecordingData.NOT_RECORDING);
trackPointCreator = new TrackPointCreator(this);
- trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this, handler);
+ trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this, this, handler);
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
notificationManager = new TrackRecordingServiceNotificationManager(this);
@@ -138,6 +140,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
recordingStatusObservable = null;
gpsStatusObservable = null;
+ sensorDisconnectedWarningObservable = null;
recordingDataObservable = null;
Log.d(TAG, "Destroyed");
@@ -315,6 +318,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
return gpsStatusObservable;
}
+ public LiveData<Boolean> getSensorDisconnectedWarningObservable() {
+ return sensorDisconnectedWarningObservable;
+ }
+
public LiveData<RecordingData> getRecordingDataObservable() {
return recordingDataObservable;
}
@@ -337,6 +344,32 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
voiceAnnouncementManager.announceIdle();
}
+ @Override
+ public void onLongBreak() {
+ voiceAnnouncementManager.announceLongBreak();
+ }
+
+ @Override
+ public void onSensorDisconnectedWarning(boolean show) {
+ if (show) {
+ voiceAnnouncementManager.announceSensorDisconnected();
+ }
+ if (sensorDisconnectedWarningObservable != null) {
+ sensorDisconnectedWarningObservable.postValue(show);
+ }
+ }
+
+ @Override
+ public void onSensorConnected() {
+ voiceAnnouncementManager.announceSensorConnected();
+ }
+
+ public void setOverrideSensorWithGPS(boolean override) {
+ if (trackRecordingManager != null) {
+ trackRecordingManager.setOverrideSensorWithGPS(override);
+ }
+ }
+
@VisibleForTesting
public void stopUpdateRecordingData() {
handler.removeCallbacks(updateRecordingData);
diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java
index af2e5b651..1c3988884 100644
--- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java
+++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java
@@ -119,6 +119,30 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
voiceAnnouncement.speak(VoiceAnnouncementUtils.createIdle(context));
}
+ public void announceLongBreak() {
+ if (shouldNotAnnounce() || !PreferencesUtils.shouldVoiceAnnouncementLongBreak()) {
+ return;
+ }
+
+ voiceAnnouncement.speak(VoiceAnnouncementUtils.createLongBreak(context));
+ }
+
+ public void announceSensorDisconnected() {
+ if (shouldNotAnnounce() || !PreferencesUtils.shouldVoiceAnnouncementSensorDisconnected()) {
+ return;
+ }
+
+ voiceAnnouncement.speak(VoiceAnnouncementUtils.createSensorDisconnected(context));
+ }
+
+ public void announceSensorConnected() {
+ if (shouldNotAnnounce() || !PreferencesUtils.shouldVoiceAnnouncementSensorConnected()) {
+ return;
+ }
+
+ voiceAnnouncement.speak(VoiceAnnouncementUtils.createSensorConnected(context));
+ }
+
public void announceStatisticsIfNeeded(@NonNull RecordingData data) {
if (shouldNotAnnounce()) {
return;
diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java
index bbce637e9..3fe16ce73 100644
--- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java
@@ -50,6 +50,21 @@ class VoiceAnnouncementUtils {
.append(context.getString(R.string.voiceIdle));
}
+ static Spannable createLongBreak(Context context) {
+ return new SpannableStringBuilder()
+ .append(context.getString(R.string.voiceLongBreak));
+ }
+
+ static Spannable createSensorDisconnected(Context context) {
+ return new SpannableStringBuilder()
+ .append(context.getString(R.string.voiceSensorDisconnected));
+ }
+
+ static Spannable createSensorConnected(Context context) {
+ return new SpannableStringBuilder()
+ .append(context.getString(R.string.voiceSensorConnected));
+ }
+
static Spannable createStatistics(Context context, Track track, SensorDataSet sensorDataSet, UnitSystem unitSystem, boolean isReportSpeed, @Nullable Statistics currentInterval, @Nullable SensorStatistics sensorStatistics) {
Statistics trackStatistics = track.statistics();
diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java
index 696379cf0..8e449c65b 100644
--- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java
+++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java
@@ -101,8 +101,11 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
position = position.withCoordinates(lastStoredTrackPointWithLocation.position());
}
- if (lastTrackPointUISpeed != null)
+ if (sensorDataSet.speed() != null) {
+ position = position.with(sensorDataSet.speed().data());
+ } else if (lastTrackPointUISpeed != null) {
position = position.with(lastTrackPointUISpeed.position().speed());
+ }
if (lastTrackPointUIAltitude != null)
position = position.with(lastTrackPointUIAltitude.position().altitude());
diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
index 2ef449f90..3ef618272 100644
--- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
@@ -38,11 +38,13 @@ import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
+import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
+import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.HeartRateZones;
import de.dennisguse.opentracks.data.models.Track;
@@ -249,6 +251,10 @@ public class PreferencesUtils {
setString(R.string.stats_units_key, unitSystem.getPreferenceId());
}
+ public static Duration getMarkerIdleDuration() {
+ return Duration.ofMinutes(getInt(R.string.marker_idle_duration_key, 3));
+ }
+
//TODO Check if actually needed or can be superseeded by a flexible default in getUnit()
public static void applyDefaultUnit() {
if (getString(R.string.stats_units_key, "").isEmpty()) {
@@ -342,6 +348,20 @@ public class PreferencesUtils {
return getBoolean(R.string.voice_announce_idle_key, true);
}
+ public static boolean shouldVoiceAnnouncementLongBreak() {
+ return getBoolean(R.string.voice_announce_long_break_key, true);
+ }
+
+
+
+ public static boolean shouldVoiceAnnouncementSensorDisconnected() {
+ return getBoolean(R.string.voice_announce_sensor_disconnected_key, true);
+ }
+
+ public static boolean shouldVoiceAnnouncementSensorConnected() {
+ return getBoolean(R.string.voice_announce_sensor_connected_key, true);
+ }
+
public static boolean shouldVoiceAnnounceTime() {
return getBoolean(R.string.voice_announce_time_key, true);
}
@@ -682,6 +702,51 @@ public class PreferencesUtils {
return entries;
}
+ static String[] getMarkerIdleDurationEntries() {
+ String[] entryValues = resources.getStringArray(R.array.marker_idle_duration_values);
+ String[] entries = new String[entryValues.length];
+
+ final int defaultDuration = Integer.parseInt(resources.getString(R.string.marker_idle_duration_default));
+
+ for (int i = 0; i < entryValues.length; i++) {
+ int value = Integer.parseInt(entryValues[i]);
+
+ if (value == defaultDuration) {
+ entries[i] = resources.getString(R.string.value_integer_minute_recommended, value);
+ } else {
+ entries[i] = resources.getString(R.string.value_integer_minute, value);
+ }
+ }
+
+ return entries;
+ }
+
+ public static boolean shouldAutoPauseOnCadence0() {
+ return getBoolean(R.string.settings_recording_auto_pause_on_cadence_0_key, false);
+ }
+
+ public static boolean shouldPreventIdleIfCadence() {
+ return getBoolean(R.string.settings_recording_prevent_idle_if_cadence_key, true);
+ }
+
+ public static Speed getAutoPauseCadence0Speed() {
+ final int DEFAULT = Integer.parseInt(resources.getString(R.string.auto_pause_cadence_0_speed_default));
+ int value = getInt(R.string.settings_recording_auto_pause_on_cadence_0_speed_key, DEFAULT);
+ return Speed.ofKMH(value);
+ }
+
+ static String[] getAutoPauseCadence0SpeedEntries() {
+ String[] entryValues = resources.getStringArray(R.array.auto_pause_cadence_0_speed_values);
+ String[] entries = new String[entryValues.length];
+
+ for (int i = 0; i < entryValues.length; i++) {
+ int value = Integer.parseInt(entryValues[i]);
+ entries[i] = value + " km/h";
+ }
+
+ return entries;
+ }
+
public static boolean shouldInstantExportToFile() {
final boolean INSTANT_POST_WORKOUT_EXPORT_DEFAULT = resources.getBoolean(R.bool.post_workout_export_enabled_default);
return getBoolean(R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT) && isDefaultExportDirectoryUri();
@@ -839,7 +904,14 @@ public class PreferencesUtils {
new DataField(resources.getString(R.string.stats_custom_layout_cadence_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_power_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_clock_key), false, false, false),
- new DataField(resources.getString(R.string.stats_custom_layout_temperature_key), false, false, false)
+ new DataField(resources.getString(R.string.stats_custom_layout_temperature_key), false, false, false),
+ new DataField(resources.getString(R.string.stats_custom_layout_auto_pause_status_key), false, false, false),
+ new DataField(resources.getString(R.string.stats_custom_layout_sensor_status_hr_key), false, false, false),
+ new DataField(resources.getString(R.string.stats_custom_layout_sensor_status_cadence_key), false, false, false),
+ new DataField(resources.getString(R.string.stats_custom_layout_sensor_status_speed_key), false, false, false),
+ new DataField(resources.getString(R.string.stats_custom_layout_debug_idle_timer_key), false, false, false),
+ new DataField(resources.getString(R.string.stats_custom_layout_debug_walking_timer_key), false, false, false),
+ new DataField(resources.getString(R.string.stats_custom_layout_debug_marker_timer_key), false, false, false)
);
RecordingLayout defaultLayout = new RecordingLayout(resources.getString(R.string.stats_custom_layout_default_layout), 2);
@@ -855,7 +927,7 @@ public class PreferencesUtils {
String selectedLayout = getSelectedLayoutName();
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
- List<RecordingLayout> recordingLayouts = RecordingLayoutIO.fromCsv(csvCustomLayouts, resources);
+ List<RecordingLayout> recordingLayouts = getAllCustomLayouts();
RecordingLayout fallback = !recordingLayouts.isEmpty() ? recordingLayouts.get(0) : getDefaultLayout();
@@ -901,7 +973,18 @@ public class PreferencesUtils {
public static List<RecordingLayout> getAllCustomLayouts() {
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
- return RecordingLayoutIO.fromCsv(csvCustomLayouts, resources);
+ List<RecordingLayout> layouts = RecordingLayoutIO.fromCsv(csvCustomLayouts, resources);
+ List<DataField> defaultFields = getDefaultLayout().getFields();
+
+ for (RecordingLayout layout : layouts) {
+ for (DataField df : defaultFields) {
+ boolean hasField = layout.getFields().stream().anyMatch(f -> f.getKey().equals(df.getKey()));
+ if (!hasField) {
+ layout.addField(new DataField(df.getKey(), false, false, df.isWide()));
+ }
+ }
+ }
+ return layouts;
}
public static void resetCustomLayoutPreferences() {
diff --git a/src/main/java/de/dennisguse/opentracks/settings/RecordingSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/RecordingSettingsFragment.java
index 7408de78f..71ac834de 100644
--- a/src/main/java/de/dennisguse/opentracks/settings/RecordingSettingsFragment.java
+++ b/src/main/java/de/dennisguse/opentracks/settings/RecordingSettingsFragment.java
@@ -57,5 +57,15 @@ public class RecordingSettingsFragment extends PreferenceFragmentCompat {
ListPreference idleDuration = findPreference(getString(R.string.idle_duration_key));
idleDuration.setEntries(PreferencesUtils.getIdleDurationEntries());
+
+ ListPreference autoPauseCadence0Speed = findPreference(getString(R.string.settings_recording_auto_pause_on_cadence_0_speed_key));
+ if (autoPauseCadence0Speed != null) {
+ autoPauseCadence0Speed.setEntries(PreferencesUtils.getAutoPauseCadence0SpeedEntries());
+ }
+
+ ListPreference markerIdleDuration = findPreference(getString(R.string.marker_idle_duration_key));
+ if (markerIdleDuration != null) {
+ markerIdleDuration.setEntries(PreferencesUtils.getMarkerIdleDurationEntries());
+ }
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutIO.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutIO.java
index 0ebb5f0f8..7b8c31958 100644
--- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutIO.java
+++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutIO.java
@@ -40,6 +40,13 @@ public class RecordingLayoutIO {
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
return recordingLayout;
}
+
+ String key = fieldParts[0];
+ if (key.equals(resources.getString(R.string.stats_custom_layout_sensor_status_key))) {
+ // Ignore deprecated sensor_status key to safely remove it from user's custom layout
+ continue;
+ }
+
recordingLayout.addField(fromCSV(fieldParts, resources));
}
return recordingLayout;
diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java
index 3d62ca203..841324b84 100644
--- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java
+++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java
@@ -77,7 +77,7 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<Recycl
Speed.ZERO
);
- demoData = new RecordingData(track, new TrackPoint(TrackPoint.Type.TRACKPOINT, position), null, statistics);
+ demoData = new RecordingData(track, new TrackPoint(TrackPoint.Type.TRACKPOINT, position), null, statistics, "", "", "", "");
}
private RecordingLayout recordingLayout;
diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java b/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java
index 8193dba41..ee08ed501 100644
--- a/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java
+++ b/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java
@@ -288,4 +288,84 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
getBinding().statsDescriptionMain.setText(R.string.stats_coordinates);
}
}
+
+ public static class AutoPauseStatus extends GenericStatisticsViewHolder {
+
+ @Override
+ public void onChanged(UnitSystem unitSystem, RecordingData data) {
+ String value = data.autoPauseStatus();
+ if (value == null || value.isEmpty()) {
+ value = "N/A";
+ }
+ getBinding().statsValue.setText(value);
+ getBinding().statsUnit.setText("");
+ getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_auto_pause_status));
+ }
+ }
+
+ public static class SensorStatusHR extends GenericStatisticsViewHolder {
+ @Override
+ public void onChanged(UnitSystem unitSystem, RecordingData data) {
+ String status = "Brak";
+ if (data.sensorDataSet() != null && data.sensorDataSet().heartRate() != null) {
+ status = "OK";
+ }
+ getBinding().statsValue.setText(status);
+ getBinding().statsUnit.setText("");
+ getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_sensor_status_hr));
+ }
+ }
+
+ public static class SensorStatusCadence extends GenericStatisticsViewHolder {
+ @Override
+ public void onChanged(UnitSystem unitSystem, RecordingData data) {
+ String status = "Brak";
+ if (data.sensorDataSet() != null && data.sensorDataSet().cadence() != null) {
+ status = "OK";
+ }
+ getBinding().statsValue.setText(status);
+ getBinding().statsUnit.setText("");
+ getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_sensor_status_cadence));
+ }
+ }
+
+ public static class SensorStatusSpeed extends GenericStatisticsViewHolder {
+ @Override
+ public void onChanged(UnitSystem unitSystem, RecordingData data) {
+ String status = "Brak";
+ if (data.sensorDataSet() != null && data.sensorDataSet().speed() != null) {
+ status = "OK";
+ }
+ getBinding().statsValue.setText(status);
+ getBinding().statsUnit.setText("");
+ getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_sensor_status_speed));
+ }
+ }
+
+ public static class DebugIdleTimer extends GenericStatisticsViewHolder {
+ @Override
+ public void onChanged(UnitSystem unitSystem, RecordingData data) {
+ getBinding().statsValue.setText(data.idleTimer().isEmpty() ? "---" : data.idleTimer());
+ getBinding().statsUnit.setText("");
+ getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_debug_idle_timer));
+ }
+ }
+
+ public static class DebugWalkingTimer extends GenericStatisticsViewHolder {
+ @Override
+ public void onChanged(UnitSystem unitSystem, RecordingData data) {
+ getBinding().statsValue.setText(data.walkingTimer().isEmpty() ? "---" : data.walkingTimer());
+ getBinding().statsUnit.setText("");
+ getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_debug_walking_timer));
+ }
+ }
+
+ public static class DebugMarkerTimer extends GenericStatisticsViewHolder {
+ @Override
+ public void onChanged(UnitSystem unitSystem, RecordingData data) {
+ getBinding().statsValue.setText(data.markerTimer().isEmpty() ? "---" : data.markerTimer());
+ getBinding().statsUnit.setText("");
+ getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_debug_marker_timer));
+ }
+ }
}
diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java b/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java
index 822a745e0..f3f4e5c8f 100644
--- a/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java
+++ b/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java
@@ -38,6 +38,13 @@ public class Mapping {
m.put(context.getString(R.string.stats_custom_layout_cadence_key), SensorStatisticsViewHolder.SensorCadence::new);
m.put(context.getString(R.string.stats_custom_layout_power_key), SensorStatisticsViewHolder.SensorPower::new);
m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new);
+ m.put(context.getString(R.string.stats_custom_layout_auto_pause_status_key), GenericStatisticsViewHolder.AutoPauseStatus::new);
+ m.put(context.getString(R.string.stats_custom_layout_sensor_status_hr_key), GenericStatisticsViewHolder.SensorStatusHR::new);
+ m.put(context.getString(R.string.stats_custom_layout_sensor_status_cadence_key), GenericStatisticsViewHolder.SensorStatusCadence::new);
+ m.put(context.getString(R.string.stats_custom_layout_sensor_status_speed_key), GenericStatisticsViewHolder.SensorStatusSpeed::new);
+ m.put(context.getString(R.string.stats_custom_layout_debug_idle_timer_key), GenericStatisticsViewHolder.DebugIdleTimer::new);
+ m.put(context.getString(R.string.stats_custom_layout_debug_walking_timer_key), GenericStatisticsViewHolder.DebugWalkingTimer::new);
+ m.put(context.getString(R.string.stats_custom_layout_debug_marker_timer_key), GenericStatisticsViewHolder.DebugMarkerTimer::new);
return m;
}
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 4c5a554b4..5765d6a12 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -197,6 +197,7 @@ limitations under the License.
<string name="settings_recording_max_recording_distance_summary">Nowy segment jest tworzony, jeśli odległość między dwiema lokalizacjami jest większa niż %1$s</string>
<string name="settings_recording_max_recording_distance_title">Maksymalna odległość</string>
<string name="settings_recording_min_recording_distance_title">Rejestrowanie odcinka</string>
+ <string name="settings_recording_marker_idle_duration_title">Minimalny czas długiej przerwy</string>
<string name="settings_recording_min_recording_interval_title">Rejestrowanie interwału</string>
<string name="settings_recording_min_required_accuracy_title">Dokładność GPS-u</string>
<string name="settings_recording_track_name_date_iso_8601_option">Data (ISO 8601)</string>
@@ -281,6 +282,7 @@ limitations under the License.
<string name="value_integer_meter_recommended">%1$d m (zalecane)</string>
<string name="value_integer_mile_hour">%1$d mil/h</string>
<string name="value_integer_minute">%1$d min</string>
+ <string name="value_integer_minute_recommended">%1$d min (zalecane)</string>
<string name="value_integer_second">%1$d s</string>
<string name="value_none">Brak</string>
<string name="value_off">Wyłącz</string>
@@ -443,6 +445,8 @@ limitations under the License.
<string name="settings_announcements_totaltime_frequency">Przedział czasu</string>
<string name="stats_sensors_heart_rate">Tętno</string>
<string name="stats_clock">Zegar</string>
+ <string name="stats_auto_pause_status">Status auto-pauzy</string>
+ <string name="stats_sensor_status">Status czujników</string>
<string name="settings_announcements_distance_frequency">Długość okrążenia</string>
<string name="settings_layout_reset_confirm_title">Zresetować twoje układy\?</string>
<string name="settings_cycling_sensor">Kolarstwo</string>
@@ -523,6 +527,10 @@ limitations under the License.
<string name="settings_announcements_lap_speed_pace">Tempo/prędkość okrążenia</string>
<string name="track_delete_number_of_tracks">%1$d ścieżki</string>
<string name="settings_announcements_idle_title">Przerwa</string>
+ <string name="settings_announcements_long_break_title">Długa przerwa (filiżanka)</string>
+ <string name="settings_announcements_sensor_category_title">Czujniki</string>
+ <string name="settings_announcements_sensor_disconnected_title">Odłączono czujnik</string>
+ <string name="settings_announcements_sensor_connected_title">Podłączono czujnik</string>
<string name="settings_recording_title">Nagrywanie</string>
<string name="settings_theme_switch_restart">Zmiana motywu może wymagać ręcznego restartu.</string>
<string name="settings_ui_dynamic_colors_summary">Użyj ręcznie zdefiniowanych Dynamicznych Kolorów (tylko Android 12+; wymaga ręcznego restartu)</string>
@@ -585,4 +593,15 @@ limitations under the License.
<string name="show_on_dashboard">API panelu OpenTracks</string>
<string name="marker_detail_title">%1$d z %2$d: %3$s</string>
<string name="voice_on_device_speaker_title">Użyj wbudowanego głośnika</string>
+ <string name="sensor_disconnected_warning">Czujnik rozłączony. Kontynuować z GPS?</string>
+ <string name="sensor_disconnected_switch_action">Przełącz</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_title">Zatrzymaj przy prowadzeniu roweru</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_summary">Uruchom autopauzę jeżeli kadencja wynosi 0 a prędkość wskazuje na chód</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_speed_title">Maksymalna prędkość prowadzenia</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_speed_summary">Prędkość poniżej której wykrywane jest prowadzenie roweru (gdy kadencja to 0)</string>
+ <string name="settings_recording_prevent_idle_if_cadence_title">Zapobiegaj pauzie podczas pedałowania</string>
+ <string name="settings_recording_prevent_idle_if_cadence_summary">Nie włączaj trybu postoju jeżeli pedałujesz, nawet przy braku ruchu</string>
+ <string name="voiceLongBreak">Rozpoczęto długą przerwę.</string>
+ <string name="voiceSensorDisconnected">Odłączono czujnik.</string>
+ <string name="voiceSensorConnected">Podłączono czujnik.</string>
</resources>
diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml
index 5d9c2038e..3c1323a95 100644
--- a/src/main/res/values/settings.xml
+++ b/src/main/res/values/settings.xml
@@ -128,6 +128,14 @@
<string name="idle_duration_key" translatable="false">idleSpeedDuration</string>
<string name="idle_duration_default" translatable="false">10</string>
<string name="idle_duration_off" translatable="false">0</string>
+ <string-array name="auto_pause_cadence_0_speed_values">
+ <item>4</item>
+ <item>6</item>
+ <item>8</item>
+ <item>10</item>
+ <item>12</item>
+ </string-array>
+
<string-array name="idle_duration_values">
<item>@string/idle_duration_off</item>
<item>5</item>
@@ -139,6 +147,17 @@
<item>120</item>
</string-array>
+ <string name="marker_idle_duration_default" translatable="false">3</string>
+ <string-array name="marker_idle_duration_values">
+ <item>1</item>
+ <item>2</item>
+ <item>@string/marker_idle_duration_default</item>
+ <item>5</item>
+ <item>10</item>
+ <item>15</item>
+ <item>30</item>
+ </string-array>
+
<string name="stats_rate_key" translatable="false">statsRate</string>
<string name="stats_rate_default" translatable="false">@string/stats_rate_speed_or_pace_default</string>
<string-array name="stats_rate_metric_options">
@@ -243,6 +262,12 @@
<bool name="voice_on_device_speaker_default" translatable="false">true</bool>
<string name="voice_announce_idle_key" translatable="false">voice_announce_idle_key</string>
<bool name="voice_announce_idle_default" translatable="false">false</bool>
+ <string name="voice_announce_long_break_key" translatable="false">voice_announce_long_break_key</string>
+ <bool name="voice_announce_long_break_default" translatable="false">true</bool>
+ <string name="voice_announce_sensor_disconnected_key" translatable="false">voice_announce_sensor_disconnected_key</string>
+ <bool name="voice_announce_sensor_disconnected_default" translatable="false">true</bool>
+ <string name="voice_announce_sensor_connected_key" translatable="false">voice_announce_sensor_connected_key</string>
+ <bool name="voice_announce_sensor_connected_default" translatable="false">true</bool>
<string name="voice_announce_total_distance_key" translatable="false">voiceAnnounceTotalDistance</string>
<bool name="voice_announce_total_distance_default" translatable="false">true</bool>
@@ -379,6 +404,14 @@
<string name="stats_custom_layout_average_cadence_key" translatable="false">average_cadence</string>
<string name="stats_custom_layout_power_key" translatable="false">power</string>
<string name="stats_custom_layout_clock_key" translatable="false">time</string>
+ <string name="stats_custom_layout_auto_pause_status_key" translatable="false">auto_pause_status</string>
+ <string name="stats_custom_layout_sensor_status_key" translatable="false">sensor_status</string>
+ <string name="stats_custom_layout_sensor_status_hr_key" translatable="false">sensor_status_hr</string>
+ <string name="stats_custom_layout_sensor_status_cadence_key" translatable="false">sensor_status_cadence</string>
+ <string name="stats_custom_layout_sensor_status_speed_key" translatable="false">sensor_status_speed</string>
+ <string name="stats_custom_layout_debug_idle_timer_key" translatable="false">debug_idle_timer</string>
+ <string name="stats_custom_layout_debug_walking_timer_key" translatable="false">debug_walking_timer</string>
+ <string name="stats_custom_layout_debug_marker_timer_key" translatable="false">debug_marker_timer</string>
<!-- Stats empty values -->
<string name="stats_empty_value_integer" translatable="false">0</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index c36234e09..dc51c72ef 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -331,6 +331,10 @@ limitations under the License.
<string name="settings_sensors_summary">Cycling Sensors, Wheel Circumference, Running Sensors</string>
<string name="settings_announcements_idle_category_title">Idle Announcements</string>
<string name="settings_announcements_idle_title">Idle</string>
+ <string name="settings_announcements_long_break_title">Long break</string>
+ <string name="settings_announcements_sensor_category_title">Sensors</string>
+ <string name="settings_announcements_sensor_disconnected_title">Sensor disconnected</string>
+ <string name="settings_announcements_sensor_connected_title">Sensor connected</string>
<string name="settings_announcements_statistics_title">Statistics Announcements</string>
<string name="settings_announcements_summary">Time, Distance, Voice Speed</string>
<string name="settings_import_export_title">Import and Export</string>
@@ -369,7 +373,9 @@ limitations under the License.
<string name="settings_recording_max_recording_distance_summary">A new segment is created if the distance between two locations is greater than %1$s</string>
<string name="settings_recording_max_recording_distance_title">Max distance</string>
<string name="settings_recording_min_recording_distance_title">Recording distance interval</string>
- <string name="settings_recording_min_recording_interval_title">Recording time interval</string>
+ <string name="marker_idle_duration_key" translatable="false">marker_idle_duration</string>
+ <string name="settings_recording_marker_idle_duration_title">Marker idle duration</string>
+ <string name="settings_recording_min_recording_interval_key" translatable="false">recording_min_recording_interval_key</string>
<string name="settings_recording_min_required_accuracy_summary">Location is ignored if its accuracy is greater than %1$s</string>
<string name="settings_recording_min_required_accuracy_title">GPS accuracy</string>
<string name="settings_recording_track_name_date_iso_8601_option">Date (ISO 8601)</string>
@@ -478,6 +484,14 @@ limitations under the License.
<string name="stats_sensors_cadence">Cadence</string>
<string name="stats_sensors_power">Power</string>
<string name="stats_clock">Clock</string>
+ <string name="stats_auto_pause_status">Auto-Pause Status</string>
+ <string name="stats_sensor_status_hr">Sensor HR</string>
+ <string name="stats_sensor_status_cadence">Sensor Cadence</string>
+ <string name="stats_sensor_status_speed">Sensor Speed</string>
+ <string name="stats_debug_idle_timer">Idle Timer</string>
+ <string name="stats_debug_walking_timer">Walking Timer</string>
+ <string name="stats_debug_marker_timer">Marker Timer</string>
+
<!-- Track -->
<string name="track_name_format">Track %1$d</string>
<string name="track_recording_notification_accuracy">Location accuracy: %1$s</string>
@@ -536,6 +550,7 @@ limitations under the License.
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (recommended)</string>
<string name="value_float_knots_recommended">%1$.1f knots (recommended)</string>
<string name="value_integer_minute">%1$d min</string>
+ <string name="value_integer_minute_recommended">%1$d min (recommended)</string>
<string name="value_integer_second">%1$d s</string>
<string name="value_none">None</string>
<string name="value_off">Off</string>
@@ -543,6 +558,9 @@ limitations under the License.
<string name="value_smallest_recommended">Smallest (recommended)</string>
<string name="value_int_seconds">%1$d s (recommended)</string>
<string name="voiceIdle">Becoming idle.</string>
+ <string name="voiceLongBreak">Long break recorded.</string>
+ <string name="voiceSensorDisconnected">Sensor disconnected.</string>
+ <string name="voiceSensorConnected">Sensor connected.</string>
<string name="voiceHoursPlural">
{n, plural,
=1 {1 hour}
@@ -705,4 +723,16 @@ limitations under the License.
<string name="settings_announcements_lap_power">Lap power</string>
<string name="power_x_watt">Power {x, plural, =1 {1 watt} other {# watts} }</string>
<string name="power">Power</string>
+ <string name="settings_recording_prevent_idle_if_cadence_key" translatable="false">preventIdleIfCadence</string>
+ <string name="settings_recording_prevent_idle_if_cadence_title">Prevent auto-pause if pedaling</string>
+ <string name="settings_recording_prevent_idle_if_cadence_summary">Do not enter idle mode while pedaling even if not moving</string>
+ <string name="sensor_disconnected_warning">Sensor disconnected. Continue with GPS?</string>
+ <string name="sensor_disconnected_switch_action">Switch</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_key" translatable="false">autoPauseCadence0</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_title">Auto-pause when carrying bike</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_summary">Pause recording if cadence is 0 and speed is low</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_speed_key" translatable="false">autoPauseCadence0Speed</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_speed_title">Auto-pause max speed</string>
+ <string name="settings_recording_auto_pause_on_cadence_0_speed_summary">Speed below which walking with bike is detected (if cadence is 0)</string>
+ <string name="auto_pause_cadence_0_speed_default" translatable="false">8</string>
</resources>
diff --git a/src/main/res/xml/settings_announcements.xml b/src/main/res/xml/settings_announcements.xml
index 3adf5fa29..919270f62 100644
--- a/src/main/res/xml/settings_announcements.xml
+++ b/src/main/res/xml/settings_announcements.xml
@@ -22,6 +22,21 @@
android:defaultValue="@bool/voice_announce_idle_default"
android:key="@string/voice_announce_idle_key"
android:title="@string/settings_announcements_idle_title" />
+ <SwitchPreferenceCompat
+ android:defaultValue="@bool/voice_announce_long_break_default"
+ android:key="@string/voice_announce_long_break_key"
+ android:title="@string/settings_announcements_long_break_title" />
+ </PreferenceCategory>
+
+ <PreferenceCategory android:title="@string/settings_announcements_sensor_category_title">
+ <SwitchPreferenceCompat
+ android:defaultValue="@bool/voice_announce_sensor_disconnected_default"
+ android:key="@string/voice_announce_sensor_disconnected_key"
+ android:title="@string/settings_announcements_sensor_disconnected_title" />
+ <SwitchPreferenceCompat
+ android:defaultValue="@bool/voice_announce_sensor_connected_default"
+ android:key="@string/voice_announce_sensor_connected_key"
+ android:title="@string/settings_announcements_sensor_connected_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_announcements_statistics_title">
diff --git a/src/main/res/xml/settings_recording.xml b/src/main/res/xml/settings_recording.xml
index 2a16d3041..28c4d003f 100644
--- a/src/main/res/xml/settings_recording.xml
+++ b/src/main/res/xml/settings_recording.xml
@@ -4,6 +4,25 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/settings_recording_title">
+ <SwitchPreferenceCompat
+ android:defaultValue="false"
+ android:key="@string/settings_recording_auto_pause_on_cadence_0_key"
+ android:title="@string/settings_recording_auto_pause_on_cadence_0_title"
+ android:summary="@string/settings_recording_auto_pause_on_cadence_0_summary" />
+
+ <SwitchPreferenceCompat
+ android:defaultValue="true"
+ android:key="@string/settings_recording_prevent_idle_if_cadence_key"
+ android:title="@string/settings_recording_prevent_idle_if_cadence_title"
+ android:summary="@string/settings_recording_prevent_idle_if_cadence_summary" />
+
+ <ListPreference
+ android:defaultValue="@string/auto_pause_cadence_0_speed_default"
+ android:entryValues="@array/auto_pause_cadence_0_speed_values"
+ android:key="@string/settings_recording_auto_pause_on_cadence_0_speed_key"
+ android:title="@string/settings_recording_auto_pause_on_cadence_0_speed_title"
+ app:useSimpleSummaryProvider="true" />
+
<ListPreference
android:defaultValue="@string/recording_distance_interval_default"
android:entryValues="@array/recording_distance_interval_values"
@@ -19,8 +38,17 @@
<ListPreference
android:defaultValue="@string/idle_duration_default"
android:entryValues="@array/idle_duration_values"
+ android:entries="@array/idle_duration_values"
android:key="@string/idle_duration_key"
android:title="@string/settings_recording_idle_timeout_title"
app:useSimpleSummaryProvider="true" />
+ <ListPreference
+ android:defaultValue="@string/marker_idle_duration_default"
+ android:entryValues="@array/marker_idle_duration_values"
+ android:entries="@array/marker_idle_duration_values"
+ android:key="@string/marker_idle_duration_key"
+ android:title="@string/settings_recording_marker_idle_duration_title"
+ app:useSimpleSummaryProvider="true" />
+
</PreferenceScreen>
\ No newline at end of file