Compare commits

...

46 Commits

Author SHA1 Message Date
Dennis Guse 8932578b90 Release v3.2.7 2019-11-25 22:03:40 +01:00
Dennis Guse 631ac9881d Wrap help into PreferenceCategory. 2019-11-25 22:03:02 +01:00
Dennis Guse 78334ed797 Fixed Spanish project description (short); was still German. 2019-11-24 21:12:07 +01:00
Dennis Guse b06da89654 Show chart for distance as well as time.
Also removed configuration option.
2019-11-24 19:55:00 +01:00
Dennis Guse d6c2e78993 Cleanup. 2019-11-24 18:48:31 +01:00
Dennis Guse 67d1ac551d Show GPS accuracy while recording in the notification of the TrackRecordingService and notify if accuracy goes beyond configured threshold. 2019-11-24 18:44:04 +01:00
Dennis Guse dcb6be717d Cleanup. 2019-11-23 22:46:39 +01:00
Dennis Guse f2c04eec1a Add help activity.
Can be accessed from Settings.
2019-11-23 21:43:07 +01:00
Dennis Guse a08311cf43 Temporary bugfix: disable landscape mode for TrackListActivity. 2019-11-22 07:44:01 +01:00
Dennis Guse 1e28f3aa86 Add TTSUtils.
(required by previous commit)
2019-11-22 07:43:19 +01:00
Dennis Guse 426af41f2c Move TTS stream definition into one place. 2019-11-20 19:27:41 +01:00
Dennis Guse fd08a219f9 Add Spanish store description. 2019-11-20 19:13:21 +01:00
Dennis Guse 25b6aec0e9 Release v3.2.6 2019-11-19 22:09:10 +01:00
Dennis Guse 3e40209d15 Refactoring. 2019-11-19 08:00:12 +01:00
Dennis Guse 54ff94f905 Settings: renamed Bluetooth sensor to "Heart rate". 2019-11-18 17:16:50 +01:00
Dennis Guse 7a4faf6b0d Fixed SettingsActivity. 2019-11-18 17:13:42 +01:00
Dennis Guse 8922b00a5f README: add screenshots. 2019-11-17 18:45:29 +01:00
Dennis Guse 702efa8693 Removed shortcut to Android's Bluetooth settings. 2019-11-17 18:25:01 +01:00
Dennis Guse e7a8c897a3 Settings: scan for Bluetooth sensors rather than using bonded devices.
Bonded devices do no provide information what services (e.g., heart rate) they support.
2019-11-17 18:24:02 +01:00
Dennis Guse c59d1d87bd Bluetooth: move getting adapter to BluetoothUtils. 2019-11-17 18:23:08 +01:00
Dennis Guse a706efb8b3 Bugfix. 2019-11-14 18:30:57 +01:00
Dennis Guse e391e83a00 Build: show version and tag in about page. 2019-11-14 18:04:50 +01:00
Dennis Guse 2940bf20d8 Settings: remove all defaults from code. 2019-11-12 21:33:04 +01:00
Dennis Guse 081db5a58b Settings: remove default setting for default activity. 2019-11-12 21:33:04 +01:00
Dennis Guse b34805272f Bugfix: not connecting to heart rate sensor - need to start BluetoothRemoteSensorManager.
Introduced in bc5ab0d7e5
2019-11-09 00:02:20 +01:00
Dennis Guse 98443dba99 Cleanup. 2019-11-08 22:45:34 +01:00
Dennis Guse dd6e733d8c Release v3.2.5 2019-11-08 22:31:49 +01:00
Dennis Guse 92d3439c3b Re-enable SummaryProvider for default_activity. 2019-11-08 07:58:30 +01:00
Dennis Guse bd155cd600 Use SummaryProvider.
Fixed #46.
2019-11-08 07:36:45 +01:00
Dennis Guse e14e53c0ae Fix hack to update summary of statsRate if statsUnit change. 2019-11-07 23:32:28 +01:00
Dennis Guse 77a6a63174 Re-initialize PreferenceFragment, when sharedpreferences were reset. 2019-11-07 22:24:15 +01:00
Dennis Guse 40f2463f09 Fail safety: if settings cannot loaded (invalid data in sharedpreferences), reset it and try again. 2019-11-07 19:15:17 +01:00
Dennis Guse e5d1048c8d Bugfix reset settings: really clear sharedPreferences before settings default values. 2019-11-07 19:15:17 +01:00
Dennis Guse bf3b529fb2 Add link to Transifex for localization.
Fixes #27.
2019-11-07 19:15:17 +01:00
Dennis Guse d0fde46027 Android does not allow referencing integer in string-arrays: store everything as string and parse it later (also for tests). 2019-11-06 18:40:58 +01:00
Dennis Guse 0f348dff10 Android does not allow referencing integer in string-arrays: store everything as string and parse it later. 2019-11-06 18:16:40 +01:00
Dennis Guse d3f5080e73 Fixed issues introduced in 353c958ae0 and 2a60aa8f59. 2019-11-06 07:42:15 +01:00
Dennis Guse a453c8ec1e TrackDetailActivity: disable activity type changer in view mode.
Fix #3.
2019-11-05 22:45:29 +01:00
Dennis Guse 2a60aa8f59 Settings: removed some default definitions from code into xml. 2019-11-05 22:02:23 +01:00
Dennis Guse 41e7870509 Settings: removed some default definitions from code into xml. 2019-11-05 21:13:51 +01:00
Dennis Guse 57f66b77ab Reorganized settings (sharedpreferences and keys); xml only. 2019-11-05 21:13:51 +01:00
Dennis Guse 53572406e9 Mark foreground service to use location and connection to other devices, so it can run properly on Android 10+ when app is in the background.
Might fix #61.
2019-11-05 21:13:51 +01:00
Dennis Guse a288255ab1 Added issue and feature templates. 2019-11-05 19:33:57 +01:00
Dennis Guse 353c958ae0 Moved accessing sharedPreferences into PreferencesUtils. 2019-11-03 01:44:46 +01:00
Dennis Guse bc5ab0d7e5 Removed unnecessary RemoteSensorManagerFactory.
TrackRecordingService is only using BluetoothRemoteSensorManager and thus factory pattern is not required.
2019-11-03 00:21:51 +01:00
Dennis Guse 9d915b5640 Cleanup. 2019-11-03 00:13:14 +01:00
75 changed files with 1891 additions and 959 deletions
+26
View File
@@ -0,0 +1,26 @@
---
name: Bug report
about: Report issues of Opentracks features
title: ''
labels: potential bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
If applicable:
* add screenshots
* add logcat output
**Technical information**
- Device: [e.g. Nexus 7 (2013)]
- OS: [e.g. LineageOS 15.1]
- Version [e.g. v3.2.4]
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for OpenTracks
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="showDecorations" value="true" />
</component>
</project>
+7 -3
View File
@@ -7,6 +7,10 @@
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
4. Create changelog (`_version code_.txt`)
5. Create commit with all changes
6. Push commits
7. Tag newly create commit with _version name_ (e.g., v3.2.1)
8. Push the tagged commit to public repository
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
7. Push commits and tags to public repository
8. Add changelog to tag (Github releases)
## Translations (localization)
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
+8
View File
@@ -30,6 +30,14 @@ To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://pl
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
</a>
## Screenshots
<div>
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
</div>
## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
+7 -7
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:3.5.2'
}
}
@@ -35,10 +35,14 @@ def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--dirty'
commandLine 'git', 'describe', '--tags'
standardOutput = stdout
}
return stdout.toString().trim()
exec {
commandLine 'git', 'log', '--format="%h"', '-n 1'
standardOutput = stdout
}
return stdout.toString().replace('\n', '-').replace('"', '').trim()
}
catch (ignored) {
return null
@@ -75,10 +79,6 @@ android {
applicationVariants.all { variant ->
variant.resValue "string", "applicationId", variant.applicationId
}
lintOptions {
disable "AppCompatResource" // TODO Disable complaint to use androidx's SearchView as it is null.
}
}
dependencies {
@@ -1,4 +1,4 @@
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
Funktionen & Features:
@@ -1 +1 @@
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
@@ -0,0 +1,15 @@
v3.2.5: OpenTracks
Changes:
- Disable activity change
- Add permission for Android 10: recording location data
Bugfix:
- Fix reset settings
Developer:
- Preferences
- if Settings cannot be loaded reset all preferences
- moved access to preferences to PreferencesUtil
- moved default of preferences from code to xml
- use SummaryProvider
@@ -0,0 +1,8 @@
v3.2.6: OpenTracks
Changes:
- Heart rate monitor: scan for available devices
- Remove shortcut to Bluetooth settings
Developer:
- Preferences: moved default of preferences from code to xml
@@ -0,0 +1,10 @@
v3.2.7: OpenTracks
Changes:
- Notification show GPS accuracy
- Show heads-up notification if GPS accuracy drops beyond threshold
- Always show chart by time as well as by distance
- Added help (in Settings)
Bugfix:
- TrackDetailActivity: disabled non-working landscape mode (temporary)
@@ -0,0 +1,8 @@
OpenTracks es un tracker deportivo que respeta tu privacidad.
Funciones:
* Tracker deportivo: haz un seguimiento de tus actividades al aire libre
* Marca lugares de interés, incluso agrega fotos a los marcadores
* Pulsómetro (compatible con dispositivos Bluetooth LE)
* Exporta tus rutas como KML o GPX
* Completamente offline, no necesitas internet
@@ -0,0 +1 @@
OpenTracks es un tracker deportivo que respeta tu privacidad.
@@ -0,0 +1 @@
OpenTracks
@@ -46,8 +46,10 @@ public class ChartFragmentTest {
@Before
public void setUp() {
chartFragment = new ChartFragment();
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext()));
boolean chartByDistance = false;
chartFragment = new ChartFragment(chartByDistance) {
};
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
}
@@ -27,7 +27,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -95,7 +94,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -124,7 +123,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -156,7 +155,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -211,7 +210,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
@@ -27,7 +27,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -79,7 +78,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -110,7 +109,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -0,0 +1,54 @@
package de.dennisguse.opentracks.services;
import android.app.NotificationManager;
import android.content.Context;
import android.location.Location;
import androidx.core.app.NotificationCompat;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TrackRecordingServiceNotificationManagerTest {
private Context context = ApplicationProvider.getApplicationContext();
@Mock
private Location locationMock;
@Mock
private NotificationCompat.Builder notificationCompatBuilder;
@Mock
private NotificationManager notificationManager;
@Test
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
when(locationMock.hasAccuracy()).thenReturn(true);
when(locationMock.getAccuracy()).thenReturn(999f);
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
// when
subject.updateLocation(context, locationMock, 100);
subject.updateLocation(context, locationMock, 100);
subject.updateLocation(context, locationMock, 1000);
subject.updateLocation(context, locationMock, 100);
// then
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
verify(notificationCompatBuilder, times(2)).setOnlyAlertOnce(false);
}
}
@@ -135,7 +135,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis(), true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
@@ -154,7 +154,7 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testResumeAfterReboot_simulateReboot() throws Exception {
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
@@ -163,7 +163,7 @@ public class TrackRecordingServiceTest {
Assert.assertTrue(service.isRecording());
Assert.assertEquals(id, service.getRecordingTrackId());
mServiceRule.unbindService();
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
// Start the service in "resume" mode (simulates the on-reboot action).
@@ -184,7 +184,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis(), false);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
@@ -207,7 +207,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
@@ -230,7 +230,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis(), true);
// Set the number of attempts to max.
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
@@ -297,7 +297,7 @@ public class TrackRecordingServiceTest {
long newTrackId = service.startNewTrack();
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
Assert.assertEquals(trackId, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId());
}
@@ -310,7 +310,7 @@ public class TrackRecordingServiceTest {
// Ending the current track when there is no recording should not result in any error.
service.endCurrentTrack();
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertFalse(PreferencesUtils.isRecording(context));
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@@ -103,7 +103,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT);
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
fullRecordingSession();
}
@@ -125,7 +125,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
fullRecordingSession();
}
@@ -139,7 +139,8 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
fullRecordingSession();
}
@@ -153,7 +154,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT);
PreferencesUtils.setInt(context, R.string.split_frequency_key, Integer.parseInt(context.getResources().getString(R.string.split_frequency_default)));
fullRecordingSession();
}
@@ -174,7 +175,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
fullRecordingSession();
}
@@ -188,7 +189,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
fullRecordingSession();
}
@@ -202,7 +203,8 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
fullRecordingSession();
}
@@ -225,7 +227,7 @@ public class TrackRecordingServiceTestLooper {
Track track = providerUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(id, service.getRecordingTrackId());
// Insert a few points, markers and statistics.
+12 -5
View File
@@ -16,8 +16,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks"
android:installLocation="auto"
android:versionCode="3101"
android:versionName="v3.2.4"> <!-- For FDroid. -->
android:versionCode="3149"
android:versionName="v3.2.7"> <!-- For FDroid. -->
<!-- Permission to initialize services -->
<application
android:name=".Startup"
@@ -32,6 +32,10 @@ limitations under the License.
<activity android:name=".DeleteActivity" />
<activity
android:name=".HelpActivity"
android:label="@string/menu_help" />
<activity
android:name=".io.file.importer.ImportActivity"
android:theme="@style/ThemeCustomTransparent">
@@ -50,10 +54,10 @@ limitations under the License.
</intent-filter>
</activity>
<activity android:name=".MarkerEditActivity" />
<activity android:name=".MarkerDetailActivity" />
<activity android:name=".MarkerEditActivity" />
<activity
android:name=".MarkerListActivity"
android:label="@string/menu_markers">
@@ -79,7 +83,9 @@ limitations under the License.
<activity android:name=".TrackEditActivity" />
<activity android:name=".TrackListActivity">
<activity
android:name=".TrackListActivity"
android:screenOrientation="userPortrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -124,6 +130,7 @@ limitations under the License.
android:name=".services.TrackRecordingService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location|connectedDevice"
android:icon="@drawable/ic_logo_color_24dp"
android:label="@string/recording_service" />
</application>
@@ -17,11 +17,12 @@
package de.dennisguse.opentracks;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import de.dennisguse.opentracks.util.TTSUtils;
/**
* @author Jimmy Shih
*/
@@ -32,7 +33,7 @@ public abstract class AbstractActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
// Set volume control stream for text to speech
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
setVolumeControlStream(TTSUtils.getTTSStream());
setContentView(getLayoutResId());
@@ -65,7 +65,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
if (trackIds.length == 1 && trackIds[0] == -1L) {
stopRecording = true;
} else {
long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (long trackId : trackIds) {
if (trackId == recordingTrackId) {
stopRecording = true;
@@ -107,7 +107,7 @@ public class ChartView extends View {
private int effectiveWidth = 0;
private int effectiveHeight = 0;
private boolean chartByDistance = true;
private boolean chartByDistance;
private boolean metricUnits = true;
private boolean reportSpeed = true;
private boolean showPointer = false;
@@ -117,8 +117,9 @@ public class ChartView extends View {
*
* @param context the context
*/
public ChartView(Context context) {
public ChartView(Context context, boolean chartByDistance) {
super(context);
this.chartByDistance = chartByDistance;
series[ELEVATION_SERIES] = new ChartValueSeries(context,
Integer.MIN_VALUE,
@@ -0,0 +1,9 @@
package de.dennisguse.opentracks;
public class HelpActivity extends AbstractActivity {
@Override
protected int getLayoutResId() {
return R.layout.help;
}
}
@@ -69,7 +69,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private SharedPreferences sharedPreferences;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
private boolean recordingTrackPaused;
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected.
@@ -79,11 +79,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
// Note that the key can be null
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key);
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
}
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
}
if (key != null) {
runOnUiThread(new Runnable() {
@@ -128,6 +128,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
@@ -101,7 +101,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
private boolean recordingTrackPaused;
// Callback when an item is selected in the contextual action mode
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@@ -143,14 +143,14 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) {
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this);
}
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key);
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(SearchListActivity.this);
}
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
}
if (key != null) {
runOnUiThread(new Runnable() {
@@ -168,6 +168,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
@@ -24,6 +24,7 @@ import android.widget.TabHost;
import android.widget.TabHost.TabContentFactory;
import android.widget.TabWidget;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentPagerAdapter;
@@ -79,6 +80,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
}
@Override
@NonNull
public Fragment getItem(int position) {
TabInfo info = tabInfos.get(position);
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
@@ -45,7 +45,8 @@ import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
import de.dennisguse.opentracks.fragments.ChartTimeFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.StatsFragment;
@@ -54,6 +55,7 @@ import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
@@ -93,8 +95,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
// Preferences
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
private boolean showOnLockScreen = PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN;
private boolean recordingTrackPaused;
private final Runnable bindChangedCallback = new Runnable() {
@Override
@@ -121,20 +122,18 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key);
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
}
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
}
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key))) {
showOnLockScreen = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key, PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN);
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
setLockscreenPolicy();
}
@@ -184,6 +183,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
@@ -205,8 +206,11 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab));
tabsAdapter.addTab(chartTabSpec, ChartFragment.class, null);
TabSpec chartTimeTabSpec = tabHost.newTabSpec(ChartTimeFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_time));
tabsAdapter.addTab(chartTimeTabSpec, ChartTimeFragment.class, null);
TabSpec chartDistanceTabSpec = tabHost.newTabSpec(ChartDistanceFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_distance));
tabsAdapter.addTab(chartDistanceTabSpec, ChartDistanceFragment.class, null);
if (savedInstanceState != null) {
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
@@ -227,12 +231,14 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
}
private void setLockscreenPolicy() {
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen);
} else if (showOnLockScreen) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
} else {
if (showOnLockScreen)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
}
@@ -272,7 +278,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
}
@Override
protected void onSaveInstanceState(Bundle outState) {
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
if (photoUri != null) {
@@ -410,10 +416,16 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
/**
* Gets the track id.
*/
// TODO Seems to be no used.
@Deprecated
public long getTrackId() {
return trackId;
}
private void setBluetoothHeartRateSensorName(String sensorName) {
StatsUtils.setBluetoothHeartRateSensorName(this, sensorName, isRecording());
}
/**
* Handles the data in the intent.
*/
@@ -125,7 +125,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
}
};
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
private boolean recordingTrackPaused;
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
@@ -135,22 +135,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.stats_units_key))) {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
}
if (key == null || key.equals(
PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(
TrackListActivity.this, R.string.recording_track_id_key);
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
trackRecordingServiceConnection.startAndBind();
}
}
if (key == null || key.equals(PreferencesUtils.getKey(
TrackListActivity.this, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this,
R.string.recording_track_paused_key,
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
}
if (key != null) {
runOnUiThread(new Runnable() {
@@ -226,12 +221,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private final OnClickListener recordListener = new OnClickListener() {
public void onClick(View v) {
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
if (!PreferencesUtils.isRecording(recordingTrackId)) {
// Not recording -> Recording
updateMenuItems(false, true);
startRecording();
} else {
if (recordingTrackPaused) {
} else if (recordingTrackPaused) {
// Paused -> Resume
updateMenuItems(false, true);
trackRecordingServiceConnection.resumeTrack();
@@ -242,7 +236,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
trackRecordingServiceConnection.pauseTrack();
trackController.update(true, true);
}
}
}
};
@@ -250,6 +243,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
@@ -28,11 +28,6 @@ public class SensorDataSetLocation extends Location {
private SensorDataSet sensorDataSet = null;
/**
* The id of this location from the provider.
*/
private int id = -1;
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
super(location);
this.sensorDataSet = sensorDataSet;
@@ -50,17 +45,8 @@ public class SensorDataSetLocation extends Location {
this.sensorDataSet = sensorDataSet;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void reset() {
super.reset();
sensorDataSet = null;
id = -1;
}
}
@@ -254,13 +254,13 @@ public class TrackDataHub implements DataSourceListener {
runInHandlerThread(new Runnable() {
@Override
public void run() {
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
@@ -270,7 +270,7 @@ public class TrackDataHub implements DataSourceListener {
}
}
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
if (PreferencesUtils.isKey(context, R.string.stats_rate_key, key)) {
reportSpeed = PreferencesUtils.isReportSpeed(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
@@ -280,8 +280,8 @@ public class TrackDataHub implements DataSourceListener {
}
}
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
@@ -290,8 +290,8 @@ public class TrackDataHub implements DataSourceListener {
}
}
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) {
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
@@ -4,7 +4,7 @@ public final class SensorDataSet {
public static final float DATA_UNAVAILABLE = Float.NaN;
//TODO It might be necessary to consider: sensor if sensor was connected as well.
//TODO It might be necessary to consider: if sensor was connected as well.
public String sensorName;
public String sensorAddress;
private float heartRate;
@@ -0,0 +1,10 @@
package de.dennisguse.opentracks.fragments;
public class ChartDistanceFragment extends ChartFragment {
public static final String CHART_FRAGMENT_TAG = ChartDistanceFragment.class.getSimpleName();
public ChartDistanceFragment() {
super(true);
}
}
@@ -25,6 +25,7 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.ZoomControls;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -54,9 +55,9 @@ import de.dennisguse.opentracks.util.UnitConversions;
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class ChartFragment extends Fragment implements TrackDataListener {
public abstract class ChartFragment extends Fragment implements TrackDataListener {
public static final String CHART_FRAGMENT_TAG = "chartFragment";
private static final String STATE_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
private final ArrayList<double[]> pendingPoints = new ArrayList<>();
@@ -68,10 +69,10 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private boolean metricUnits = true;
private boolean reportSpeed = true;
private int recordingDistanceInterval = PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT;
private int recordingDistanceInterval;
// Modes of operation
private boolean chartByDistance = true;
private boolean chartByDistance;
private boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
// UI elements
@@ -95,14 +96,18 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
};
public ChartFragment(boolean chartByDistance) {
this.chartByDistance = chartByDistance;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
*/
chartView = new ChartView(getActivity());
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
// Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
chartView = new ChartView(getActivity(), chartByDistance);
}
@Override
@@ -127,7 +132,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override
public void onStart() {
super.onStart();
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
ViewGroup layout = getView().findViewById(R.id.chart_view_layout);
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layout.addView(chartView, layoutParams);
}
@@ -149,10 +154,24 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override
public void onStop() {
super.onStop();
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
ViewGroup layout = getView().findViewById(R.id.chart_view_layout);
layout.removeView(chartView);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
chartByDistance = savedInstanceState.getBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY);
}
}
@Override
public void onTrackUpdated(Track track) {
if (isResumed()) {
@@ -263,8 +282,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
reportSpeed = speed;
chartView.setReportSpeed(reportSpeed);
boolean chartShowSpeed = PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
runOnUiThread(new Runnable() {
@@ -303,30 +321,24 @@ public class ChartFragment extends Fragment implements TrackDataListener {
*/
private void checkChartSettings() {
boolean needUpdate = false;
if (chartByDistance != PreferencesUtils.isChartByDistance(getActivity())) {
chartByDistance = !chartByDistance;
chartView.setChartByDistance(chartByDistance);
reloadTrackDataHub();
needUpdate = true;
}
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.shouldChartShowElevation(getActivity()))) {
needUpdate = true;
}
boolean chartShowSpeed = PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_power_key, PreferencesUtils.CHART_SHOW_POWER_DEFAULT))) {
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.shouldChartShowPower(getActivity()))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_cadence_key, PreferencesUtils.CHART_SHOW_CADENCE_DEFAULT))) {
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.shouldChartShowCadence(getActivity()))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_heart_rate_key, PreferencesUtils.CHART_SHOW_HEART_RATE_DEFAULT))) {
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.shouldChartShowHeartRate(getActivity()))) {
needUpdate = true;
}
if (needUpdate) {
@@ -0,0 +1,10 @@
package de.dennisguse.opentracks.fragments;
public class ChartTimeFragment extends ChartFragment {
public static final String CHART_FRAGMENT_TAG = ChartTimeFragment.class.getSimpleName();
public ChartTimeFragment() {
super(false);
}
}
@@ -1,6 +1,5 @@
package de.dennisguse.opentracks.fragments;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
@@ -79,19 +78,6 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment implements
return view;
}
/**
* TODO: Only used by obsolete Androidx preferences. Remove when onAttach(Context) is called.
*/
@Override
public void onAttach(@NonNull Activity activity) {
super.onAttach(activity);
try {
chooseActivityTypeCaller = (ChooseActivityTypeCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity + " must implement " + ChooseActivityTypeCaller.class.getSimpleName());
}
}
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
@@ -45,7 +45,6 @@ import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -67,7 +66,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private Location lastLocation = null;
private TripStatistics lastTripStatistics = null;
private SensorDataSet sensorDataSet = null;
private final Runnable updateUIeachSecond = new Runnable() {
public void run() {
@@ -82,7 +80,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
}
};
private String category = "";
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT;
private int recordingGpsAccuracy;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -92,6 +90,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
recordingGpsAccuracy = Integer.parseInt(getContext().getResources().getString(R.string.recording_gps_accuracy_default));
handlerUpdateUI = new Handler();
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
@@ -304,9 +305,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
*/
private void updateSensorDataUI() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
SensorDataSet sensorDataSet = null;
if (trackRecordingService == null) {
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
sensorDataSet = null;
} else {
//TODO sensorState = trackRecordingService.getSensorState();
sensorDataSet = trackRecordingService.getSensorData();
@@ -27,7 +27,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
@@ -100,7 +99,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
try {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
boolean isRecording = PreferencesUtils.isRecording(exportActivity);
boolean isPaused = PreferencesUtils.getBoolean(exportActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(exportActivity);
// Get the wake lock if not recording or paused
if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
@@ -102,7 +102,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
this.context = context;
this.importTrackId = importTrackId;
this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
trackIds = new ArrayList<>();
waypoints = new ArrayList<>();
}
@@ -28,7 +28,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -104,7 +103,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
// Get the wake lock if not recording or paused
boolean isRecording = PreferencesUtils.isRecording(importActivity);
boolean isPaused = PreferencesUtils.getBoolean(importActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
}
@@ -101,7 +101,7 @@ public class KmzTrackImporter implements TrackImporter {
* @param trackId the trackId
*/
private void cleanImport(long trackId) {
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
if (PreferencesUtils.isRecording(trackId)) {
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
contentProviderUtils.deleteTrack(context, trackId);
}
@@ -33,10 +33,6 @@ public class LocationManagerConnector {
private final Handler handler;
private final LocationManager locationManager;
private LocationListener requestLocationUpdates;
private float requestLocationUpdatesDistance;
private long requestLocationUpdatesTime;
public LocationManagerConnector(Context context, Looper looper) {
this.handler = new Handler(looper);
@@ -55,10 +51,7 @@ public class LocationManagerConnector {
@Override
@SuppressLint("MissingPermission")
public void run() {
requestLocationUpdatesTime = minTime;
requestLocationUpdatesDistance = minDistance;
requestLocationUpdates = locationListener;
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, requestLocationUpdatesTime, requestLocationUpdatesDistance, locationListener);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime, minDistance, locationListener);
}
});
}
@@ -70,6 +63,5 @@ public class LocationManagerConnector {
*/
public void removeLocationUpdates(final LocationListener locationListener) {
locationManager.removeUpdates(locationListener);
requestLocationUpdates = null;
}
}
@@ -16,9 +16,6 @@
package de.dennisguse.opentracks.services;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
@@ -30,7 +27,6 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -38,7 +34,6 @@ import android.os.PowerManager.WakeLock;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
import androidx.core.app.TaskStackBuilder;
import java.util.concurrent.ExecutorService;
@@ -58,7 +53,6 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.sensors.RemoteSensorManagerFactory;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory;
@@ -81,13 +75,13 @@ import de.dennisguse.opentracks.util.UnitConversions;
public class TrackRecordingService extends Service {
// The name of extra intent property to indicate whether we want to resume a previously recorded track.
public static final String RESUME_TRACK_EXTRA_NAME = "de.dennisguse.opentracks.RESUME_TRACK";
public static final String RESUME_TRACK_EXTRA_NAME = "RESUME_TRACK";
private static final int NOTIFICATION_ID = 123;
public static final double PAUSE_LATITUDE = 100.0;
public static final double RESUME_LATITUDE = 200.0;
// Anything faster than that (in meters per second) will be considered moving.
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
@VisibleForTesting
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
@@ -96,13 +90,13 @@ public class TrackRecordingService extends Service {
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
// The following variables are set in onCreate:
private ExecutorService executorService;
private Context context;
private ContentProviderUtils contentProviderUtils;
private Handler handler;
private LocationManagerConnector locationManagerConnector;
private PeriodicTaskExecutor voiceExecutor;
private PeriodicTaskExecutor splitExecutor;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceNotificationManager notificationManager;
private long recordingTrackId;
private boolean recordingTrackPaused;
private LocationListenerPolicy locationListenerPolicy;
@@ -118,52 +112,50 @@ public class TrackRecordingService extends Service {
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) {
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
if (PreferencesUtils.isRecording(context)) {
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
if (PreferencesUtils.isRecording(TrackRecordingService.this)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingService.this);
}
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
voiceExecutor.setMetricUnits(metricUnits);
splitExecutor.setMetricUnits(metricUnits);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.voice_frequency_key))) {
voiceExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT));
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.split_frequency_key))) {
splitExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT));
if (PreferencesUtils.isKey(context, R.string.split_frequency_key, key)) {
splitExecutor.setTaskFrequency(PreferencesUtils.getSplitFrequency(context));
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.min_recording_interval_key))) {
int minRecordingInterval = PreferencesUtils.getInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
switch (minRecordingInterval) {
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
// Choose battery life over moving time accuracy.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5);
break;
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY:
// Get all the updates.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0);
break;
default:
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
// Choose battery life over moving time accuracy.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5);
} else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
// Get all the updates.
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0);
} else {
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
}
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) {
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.max_recording_distance_key))) {
maxRecordingDistance = PreferencesUtils.getInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.auto_resume_track_timeout_key))) {
autoResumeTrackTimeout = PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
if (PreferencesUtils.isKey(context, R.string.auto_resume_track_timeout_key, key)) {
autoResumeTrackTimeout = PreferencesUtils.getAutoResumeTrackTimeout(context);
}
}
};
@@ -222,7 +214,6 @@ public class TrackRecordingService extends Service {
public void onCreate() {
super.onCreate();
executorService = Executors.newSingleThreadExecutor();
context = this;
contentProviderUtils = ContentProviderUtils.Factory.get(this);
handler = new Handler();
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
@@ -230,6 +221,7 @@ public class TrackRecordingService extends Service {
splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory());
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
notificationManager = new TrackRecordingServiceNotificationManager(this);
// onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
@@ -266,12 +258,12 @@ public class TrackRecordingService extends Service {
@Override
public void onDestroy() {
if (remoteSensorManager != null) {
RemoteSensorManagerFactory.releaseSystemSensorManager();
remoteSensorManager.stop();
remoteSensorManager = null;
}
// Reverse order from onCreate
showNotification(false);
showNotification(false); //TODO Why?
handler.removeCallbacks(registerLocationRunnable);
unregisterLocationListener();
@@ -404,40 +396,6 @@ public class TrackRecordingService extends Service {
return Long.parseLong(uri.getLastPathSegment());
}
/**
* Starts the service as a foreground service.
*
* @param pendingIntent the notification pending intent
* @param messageId the notification message id
*/
@VisibleForTesting
protected void startForegroundService(PendingIntent pendingIntent, int messageId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(getString(R.string.app_name), getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (manager != null) {
manager.createNotificationChannel(notificationChannel);
}
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.app_name))
.setContentIntent(pendingIntent)
.setContentText(getString(messageId))
.setContentTitle(getString(R.string.app_name))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_logo_color_24dp)
.setCategory(Notification.CATEGORY_SERVICE)
.setWhen(System.currentTimeMillis());
startForeground(1, builder.build());
}
/**
* Stops the service as a foreground service.
*/
@VisibleForTesting
protected void stopForegroundService() {
stopForeground(true);
}
/**
* Handles start command.
*
@@ -465,17 +423,19 @@ public class TrackRecordingService extends Service {
return false;
}
int retries = PreferencesUtils.getInt(this, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
int retries = PreferencesUtils.getAutoResumeTrackCurrentRetryDefault(this);
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
Log.d(TAG, "Not resuming. Exceeded maximum retry attempts.");
return false;
}
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, retries + 1);
PreferencesUtils.incrementAutoResumeTrackCurrentRetryDefault(this);
if (autoResumeTrackTimeout == PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER) {
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_never))) {
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
return false;
} else if (autoResumeTrackTimeout == PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS) {
}
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_always))) {
Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
return true;
}
@@ -509,13 +469,13 @@ public class TrackRecordingService extends Service {
// Update shared preferences
updateRecordingState(trackId, false);
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, 0);
PreferencesUtils.resetAutoResumeTrackCurrentRetryDefault(this);
// Update database
track.setId(trackId);
track.setName(TrackNameUtils.getTrackName(this, trackId, now));
String category = PreferencesUtils.getString(this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
String category = PreferencesUtils.getDefaultActivity(this);
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(this, category));
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
@@ -595,7 +555,8 @@ public class TrackRecordingService extends Service {
*/
private void startRecording(boolean trackStarted) {
// Update instance variables
remoteSensorManager = RemoteSensorManagerFactory.getSystemSensorManager(this);
remoteSensorManager = new BluetoothRemoteSensorManager(this);
remoteSensorManager.start();
lastLocation = null;
currentSegmentHasLocation = false;
isIdle = false;
@@ -678,6 +639,8 @@ public class TrackRecordingService extends Service {
}
endRecording(false, recordingTrackId);
notificationManager.updateContent(getString(R.string.generic_paused));
}
/**
@@ -693,7 +656,7 @@ public class TrackRecordingService extends Service {
// Update instance variables
if (remoteSensorManager != null) {
RemoteSensorManagerFactory.releaseSystemSensorManager();
remoteSensorManager.stop();
remoteSensorManager = null;
}
lastLocation = null;
@@ -764,6 +727,8 @@ public class TrackRecordingService extends Service {
return;
}
notificationManager.updateLocation(this, location, recordingGpsAccuracy);
if (!location.hasAccuracy() || location.getAccuracy() >= recordingGpsAccuracy) {
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
return;
@@ -799,10 +764,7 @@ public class TrackRecordingService extends Service {
}
if (!LocationUtils.isValidLocation(lastValidTrackPoint)) {
/*
* Should not happen. The current segment should have a location. Just
* insert the current location.
*/
// Should not happen. The current segment should have a location. Just insert the current location.
insertLocation(track, location, null);
lastLocation = location;
return;
@@ -949,33 +911,33 @@ public class TrackRecordingService extends Service {
}
}
/**
* Shows the notification.
*
* @param isGpsStarted true if GPS is started
*/
private void showNotification(boolean isGpsStarted) {
if (isRecording()) {
if (isPaused()) {
stopForegroundService();
} else {
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackDetailActivity.class).addNextIntent(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
startForegroundService(pendingIntent, R.string.track_record_notification);
}
} else {
// Not recording
if (isGpsStarted) {
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addNextIntent(intent).getPendingIntent(0, 0);
startForegroundService(pendingIntent, R.string.gps_starting);
} else {
stopForegroundService();
}
if ((isRecording() && isPaused()) || (!isRecording() && !isGpsStarted)) {
stopForeground(true);
}
if (isRecording() && !isPaused()) {
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackDetailActivity.class)
.addNextIntent(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
notificationManager.updatePendingIntent(pendingIntent);
notificationManager.updateContent(getString(R.string.gps_starting));
startForeground(NOTIFICATION_ID, notificationManager.getNotification());
}
if (!isRecording() && isGpsStarted) {
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addNextIntent(intent)
.getPendingIntent(0, 0);
notificationManager.updatePendingIntent(pendingIntent);
notificationManager.updateContent(getString(R.string.gps_starting));
startForeground(NOTIFICATION_ID, notificationManager.getNotification());
}
}
@@ -203,9 +203,9 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
if (PreferencesUtils.isRecording(context)) {
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
}
boolean recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
boolean recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
if (!recordingTrackPaused) {
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
PreferencesUtils.defaultRecordingTrackPaused(context);
}
}
@@ -248,7 +248,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
try {
if (showEditor) {
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
trackRecordingService.endCurrentTrack();
if (PreferencesUtils.isRecording(context)) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
@@ -0,0 +1,94 @@
package de.dennisguse.opentracks.services;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.location.Location;
import android.os.Build;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
* Manages the content of the notification shown by {@link TrackRecordingService}.
*/
class TrackRecordingServiceNotificationManager {
private final static int NOTIFICATION_ID = 123;
private final static String CHANNEL_ID = TrackRecordingServiceNotificationManager.class.getSimpleName();
private NotificationCompat.Builder notificationBuilder;
private NotificationManager notificationManager;
private boolean previousLocationWasAccurate = true;
TrackRecordingServiceNotificationManager(Context context) {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
notificationChannel.setAllowBubbles(true);
}
notificationManager.createNotificationChannel(notificationChannel);
}
notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_ID);
notificationBuilder
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setContentTitle(context.getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_logo_color_24dp);
}
@VisibleForTesting
TrackRecordingServiceNotificationManager(NotificationManager notificationManager, NotificationCompat.Builder notificationBuilder) {
this.notificationManager = notificationManager;
this.notificationBuilder = notificationBuilder;
}
void updateContent(String content) {
notificationBuilder.setContentText(content);
updateNotification();
}
void updateLocation(Context context, Location location, int recordingGpsAccuracy) {
String formattedAccuracy = context.getString(R.string.value_none);
if (location.hasAccuracy()) {
formattedAccuracy = StringUtils.formatDistance(context, location.getAccuracy(), PreferencesUtils.isMetricUnits(context));
boolean currentLocationWasAccurate = location.getAccuracy() < recordingGpsAccuracy;
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
notificationBuilder.setOnlyAlertOnce(!shouldAlert);
previousLocationWasAccurate = currentLocationWasAccurate;
}
notificationBuilder.setContentText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
updateNotification();
notificationBuilder.setOnlyAlertOnce(true);
}
void updatePendingIntent(PendingIntent pendingIntent) {
notificationBuilder.setContentIntent(pendingIntent);
updateNotification();
}
Notification getNotification() {
return notificationBuilder.build();
}
private void updateNotification() {
notificationManager.notify(NOTIFICATION_ID, getNotification());
}
}
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
@@ -36,7 +37,6 @@ import de.dennisguse.opentracks.util.BluetoothUtils;
/**
* Manages connection to Bluetooth LE heart rate monitor.
* TODO: shutdown connection.
*/
public class BluetoothConnectionManager {
@@ -46,7 +46,6 @@ public class BluetoothConnectionManager {
static final int MESSAGE_READ = 3;
static final int MESSAGE_DISCONNECTED = 4;
private static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
@@ -91,10 +90,18 @@ public class BluetoothConnectionManager {
@Override
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
BluetoothGattCharacteristic characteristic = gatt
.getService(HEART_RATE_SERVICE_UUID)
.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);
BluetoothGattService service = gatt.getService(BluetoothUtils.HEART_RATE_SERVICE_UUID);
if (service == null) {
Log.e(TAG, "Could not get heart rate service for " + gatt.getDevice().getAddress());
return;
}
BluetoothGattCharacteristic characteristic = service.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);
if (characteristic == null) {
Log.e(TAG, "Could not get BluetoothCharacteristic for " + gatt.getDevice().getAddress());
return;
}
gatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
@@ -132,8 +139,9 @@ public class BluetoothConnectionManager {
Log.d(TAG, "Connecting to: " + bluetoothDevice);
setState(SensorState.CONNECTING);
bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
setState(SensorState.CONNECTING);
}
public synchronized void disconnect() {
@@ -26,15 +26,14 @@ import android.os.Message;
import android.util.Log;
import android.widget.Toast;
import java.util.ArrayList;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* Bluetooth sensor manager.
* Bluetooth LE sensor manager.
* Should only be instantiated once!
*
* @author Sandor Dornbush
*/
@@ -44,11 +43,12 @@ public class BluetoothRemoteSensorManager {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
private static final BluetoothAdapter bluetoothAdapter = BluetoothUtils.getDefaultBluetoothAdapter(TAG);
private final Context context;
private final SharedPreferences sharedPreferences;
// Handler that gets information back from the bluetoothConnectionManager
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override
@@ -85,8 +85,8 @@ public class BluetoothRemoteSensorManager {
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (bluetoothConnectionManager != null && key == null || key.equals(PreferencesUtils.getKey(context, R.string.bluetooth_sensor_key))) {
String address = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
stop();
return;
@@ -107,48 +107,11 @@ public class BluetoothRemoteSensorManager {
/**
* @param context the context
*/
BluetoothRemoteSensorManager(Context context) {
public BluetoothRemoteSensorManager(Context context) {
this.context = context;
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
}
private static BluetoothAdapter getDefaultBluetoothAdapter() {
// If from the main application thread, return directly
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
return BluetoothAdapter.getDefaultAdapter();
}
// Get the default adapter from the main application thread
final ArrayList<BluetoothAdapter> adapters = new ArrayList<>(1);
final Object mutex = new Object();
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
adapters.add(BluetoothAdapter.getDefaultAdapter());
synchronized (mutex) {
mutex.notify();
}
}
});
while (adapters.isEmpty()) {
synchronized (mutex) {
try {
mutex.wait(UnitConversions.ONE_SECOND);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
}
}
}
if (adapters.get(0) == null) {
Log.w(TAG, "No bluetooth adapter found.");
}
return adapters.get(0);
}
public void start() {
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
startCurrentSensor();
@@ -182,7 +145,8 @@ public class BluetoothRemoteSensorManager {
return;
}
String address = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
// String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
Log.w(TAG, "No bluetooth address.");
return;
@@ -1,64 +0,0 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services.sensors;
import android.content.Context;
/**
* A factory of {@link BluetoothRemoteSensorManager}.
*
* @author Sandor Dornbush
*/
public class RemoteSensorManagerFactory {
private static BluetoothRemoteSensorManager remoteSensorManagerSystem = null;
private RemoteSensorManagerFactory() {
}
/**
* Gets the system sensor manager.
*
* @param context the context
*/
public static BluetoothRemoteSensorManager getSystemSensorManager(Context context) {
releaseSystemSensorManager();
remoteSensorManagerSystem = getSensorManager(context);
remoteSensorManagerSystem.start();
return remoteSensorManagerSystem;
}
/**
* Releases the system sensor manager.
*/
public static void releaseSystemSensorManager() {
if (remoteSensorManagerSystem != null) {
remoteSensorManagerSystem.stop();
}
remoteSensorManagerSystem = null;
}
/**
* Gets the sensor manager.
*
* @param context the context
*/
private static BluetoothRemoteSensorManager getSensorManager(Context context) {
return new BluetoothRemoteSensorManager(context);
}
}
@@ -34,6 +34,7 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TTSUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
@@ -193,7 +194,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
* @param announcement the announcement
*/
private void speakAnnouncement(String announcement) {
int result = audioManager.requestAudioFocus(null, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
int result = audioManager.requestAudioFocus(null, TTSUtils.getTTSStream(), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "Failed to request audio focus.");
}
@@ -17,9 +17,9 @@ package de.dennisguse.opentracks.services.tasks;
import android.util.Log;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
@@ -35,13 +35,14 @@ public class PeriodicTaskExecutor {
private final TrackRecordingService trackRecordingService;
private final PeriodicTaskFactory periodicTaskFactory;
private final int TASK_FREQUENCY_OFF;
/**
* The task frequency.
* A positive value is a time frequency (minutes).
* A negative value is a distance frequency (km or mi).
* A zero value is to turn off periodic task.
*/
private int taskFrequency = PreferencesUtils.FREQUENCY_OFF;
private int taskFrequency;
private PeriodicTask periodicTask;
@@ -56,6 +57,9 @@ public class PeriodicTaskExecutor {
public PeriodicTaskExecutor(TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
this.trackRecordingService = trackRecordingService;
this.periodicTaskFactory = periodicTaskFactory;
TASK_FREQUENCY_OFF = Integer.parseInt(trackRecordingService.getBaseContext().getResources().getString(R.string.frequency_off));
taskFrequency = TASK_FREQUENCY_OFF;
}
/**
@@ -71,7 +75,7 @@ public class PeriodicTaskExecutor {
timerTaskExecutor.shutdown();
timerTaskExecutor = null;
}
if (taskFrequency == PreferencesUtils.FREQUENCY_OFF) {
if (taskFrequency == TASK_FREQUENCY_OFF) {
Log.d(TAG, "Task frequency is off.");
return;
}
@@ -32,6 +32,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.HackUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -42,14 +43,6 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
*/
public class ActivityTypePreference extends DialogPreference {
private SummaryProvider<DialogPreference> summaryProvider = new SummaryProvider<DialogPreference>() {
@Override
public CharSequence provideSummary(DialogPreference preference) {
String defaultActivity = PreferencesUtils.getString(ActivityTypePreference.this.getContext(), R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
return defaultActivity != null && !defaultActivity.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? defaultActivity : getContext().getString(R.string.value_unknown);
}
};
public ActivityTypePreference(Context context, AttributeSet attrs) {
super(context, attrs);
setDialogLayoutResource(R.layout.preference_activity_type);
@@ -57,8 +50,14 @@ public class ActivityTypePreference extends DialogPreference {
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
setPersistent(true);
//TODO: for some reason the summary is not updated; after changing it. Enable summaryprovider, when fixed.
// setSummaryProvider(summaryProvider);
SummaryProvider<DialogPreference> summaryProvider = new SummaryProvider<DialogPreference>() {
@Override
public CharSequence provideSummary(DialogPreference preference) {
return PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
}
};
setSummaryProvider(summaryProvider);
}
@Override
@@ -87,7 +86,7 @@ public class ActivityTypePreference extends DialogPreference {
final Context context = getActivity();
textView = view.findViewById(R.id.activity_type_preference_text_view);
String category = PreferencesUtils.getString(context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
String category = PreferencesUtils.getDefaultActivity(context);
textView.setText(category);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
textView.setAdapter(adapter);
@@ -132,7 +131,7 @@ public class ActivityTypePreference extends DialogPreference {
}
private void showIconSelectDialog() {
String category = PreferencesUtils.getString(getActivity(), R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
String category = PreferencesUtils.getDefaultActivity(getActivity());
ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
}
@@ -141,7 +140,8 @@ public class ActivityTypePreference extends DialogPreference {
if (positiveResult) {
String newDefaultActivity = textView.getText().toString();
if (getPreference().callChangeListener(newDefaultActivity)) {
PreferencesUtils.setString(getActivity(), R.string.default_activity_key, newDefaultActivity);
PreferencesUtils.setDefaultActivity(getActivity(), newDefaultActivity);
HackUtils.invalidatePreference(getPreference());
}
}
}
@@ -0,0 +1,112 @@
package de.dennisguse.opentracks.settings;
import android.bluetooth.BluetoothDevice;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class BluetoothLeAdapter extends BaseAdapter {
/**
* Contains a unique list (by address) of devices.
*/
private final List<Device> devices = new ArrayList<>();
@Override
public int getCount() {
return devices.size();
}
@Override
public Object getItem(int position) {
return devices.get(position);
}
@Override
public long getItemId(int position) {
return devices.get(position).hashCode();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View currentView = convertView;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
//TODO Check if there is a better way to achieve identical look and feel to ListPreference.
//Use material design single choice; for old style use: android.R.layout.select_dialog_singlechoice
currentView = inflater.inflate(androidx.appcompat.R.layout.select_dialog_singlechoice_material, null);
}
Device device = devices.get(position);
TextView textView = currentView.findViewById(android.R.id.text1);
textView.setText(device.getName());
return currentView;
}
public void add(String name, String address) {
Device device = new Device(name, address);
if (!devices.contains(device)) {
devices.add(new Device(name, address));
} else {
for (Device currentDevice : devices) {
if (currentDevice.getAddress().equals(address)) {
currentDevice.setName(name);
}
}
}
notifyDataSetChanged();
}
public void add(BluetoothDevice bluetoothDevice) {
add(bluetoothDevice.getName(), bluetoothDevice.getAddress());
}
public Device get(int index) {
return devices.get(index);
}
public class Device {
private String name;
private String address;
public Device(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
/**
* Check if the address is identical.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Device)) return false;
Device device = (Device) o;
return address.equals(device.address);
}
@Override
public int hashCode() {
return Objects.hash(name, address);
}
}
}
@@ -0,0 +1,199 @@
package de.dennisguse.opentracks.settings;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.ParcelUuid;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.BluetoothUtils;
/**
* Preference to select a discoverable Bluetooth LE device.
* Based upon ListPreference.
*/
public class BluetoothLePreference extends DialogPreference {
private final static String TAG = BluetoothLePreference.class.getSimpleName();
public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BluetoothLePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BluetoothLePreference(Context context) {
super(context);
}
private String value;
private boolean valueSet = false;
public String getValue() {
return value;
}
public void setValue(String value) {
final boolean changed = !TextUtils.equals(this.value, value);
if (changed || !valueSet) {
this.value = value;
valueSet = true;
persistString(value);
if (changed) {
notifyChanged();
}
}
}
@Override
protected void onSetInitialValue(Object defaultValue) {
setValue(getPersistedString((String) defaultValue));
}
@Override
public CharSequence getSummary() {
return getValue();
}
public static class BluetoothLePreferenceDialog extends PreferenceDialogFragmentCompat {
private int selectedEntryIndex;
private BluetoothLeAdapter listAdapter = new BluetoothLeAdapter();
private BluetoothLeScanner scanner = null;
private ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
Log.d(TAG, "Found device " + result.getDevice().getName() + " " + result);
listAdapter.add(result.getDevice());
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
for (ScanResult scanResult : results) {
listAdapter.add(scanResult.getDevice());
}
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "Bluetooth scan failed with errorCode " + errorCode);
Toast.makeText(getContext(), R.string.sensor_could_not_scan, Toast.LENGTH_LONG).show();
dismiss();
}
};
static BluetoothLePreferenceDialog newInstance(String key) {
final BluetoothLePreferenceDialog fragment = new BluetoothLePreferenceDialog();
final Bundle b = new Bundle(1);
b.putString(ARG_KEY, key);
fragment.setArguments(b);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getDefaultBluetoothAdapter(TAG);
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Log.w(TAG, "Bluetooth adapter is present or not enabled.");
Toast.makeText(getContext(), R.string.bluetooth_disabled, Toast.LENGTH_SHORT).show();
dismiss();
return;
}
if (bluetoothAdapter.isDiscovering()) {
Log.i(TAG, "Cancelling ongoing bluetooth discovery.");
bluetoothAdapter.cancelDiscovery();
}
scanner = bluetoothAdapter.getBluetoothLeScanner();
if (scanner == null) {
Log.e(TAG, "BluetoothLeScanner is null.");
dismiss();
return;
}
String deviceNone = getContext().getString(R.string.sensor_type_value_none);
listAdapter.add(getContext().getString(R.string.value_none), deviceNone);
selectedEntryIndex = 0;
BluetoothLePreference preference = (BluetoothLePreference) getPreference();
String deviceSelected = preference.getValue();
if (!deviceNone.equals(deviceSelected)) {
listAdapter.add(preference.getValue(), preference.getValue());
selectedEntryIndex = 1;
}
ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(BluetoothUtils.HEART_RATE_SERVICE_UUID));
List<ScanFilter> scanFilter = new ArrayList<>();
scanFilter.add(scanFilterBuilder.build());
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
scanner.startScan(scanFilter, scanSettingsBuilder.build(), scanCallback);
}
//Behave like ListPreferenceDialogFragmentCompat, but uses a custom listAdapter.
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setSingleChoiceItems(listAdapter, selectedEntryIndex,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
selectedEntryIndex = which;
BluetoothLePreferenceDialog.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
dialog.dismiss();
}
});
builder.setIcon(android.R.drawable.stat_sys_data_bluetooth);
builder.setPositiveButton(null, null);
}
@Override
public void onDialogClosed(boolean positiveResult) {
if (scanner != null) {
scanner.stopScan(scanCallback);
}
if (positiveResult && selectedEntryIndex >= 0) {
String value = listAdapter.get(selectedEntryIndex).getAddress();
BluetoothLePreference preference = (BluetoothLePreference) getPreference();
if (preference.callChangeListener(value)) {
preference.setValue(value);
}
}
}
}
}
@@ -1,213 +1,163 @@
package de.dennisguse.opentracks.settings;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import androidx.preference.ListPreference;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
final class PreferenceHelper {
/**
* Sets the min recording interval options.
*
* @param options the options
* @param values the values
*/
static void setMinRecordingIntervalOptions(Context context, String[] options, String[] values) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
switch (value) {
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
options[i] = context.getString(R.string.value_adapt_battery_life);
break;
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY:
options[i] = context.getString(R.string.value_adapt_accuracy);
break;
case PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT:
options[i] = context.getString(R.string.value_smallest_recommended);
break;
default:
options[i] = value < 60 ? context.getString(R.string.value_integer_second, value) : context.getString(R.string.value_integer_minute, value / 60);
static String[] getMinRecordingIntervalEntries(Context context) {
String[] entryValues = context.getResources().getStringArray(R.array.min_recording_interval_values);
String[] entries = new String[entryValues.length];
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
if (value == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
entries[i] = context.getString(R.string.value_adapt_accuracy);
} else if (value == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
entries[i] = context.getString(R.string.value_adapt_battery_life);
} else if (value == PreferencesUtils.getMinRecordingIntervalDefault(context)) {
entries[i] = context.getString(R.string.value_smallest_recommended);
} else {
entries[i] = value < 60 ? context.getString(R.string.value_integer_second, value) : context.getString(R.string.value_integer_minute, value / 60);
}
}
return entries;
}
/**
* Sets the recording distance interval options.
*
* @param options the options
* @param values the values
* @param metricUnits true for metric units
*/
static void setRecordingDistanceIntervalOptions(Context context, String[] options, String[] values, boolean metricUnits) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
static String[] getRecordingDistanceIntervalEntries(Context context, boolean metricUnits) {
String[] entryValues = context.getResources().getStringArray(R.array.recording_distance_interval_values);
String[] entries = new String[entryValues.length];
final int recordingDistanceIntervalDefault = PreferencesUtils.getRecordingDistanceIntervalDefault(context);
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value);
if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) {
options[i] = context.getString(R.string.value_integer_meter_recommended, value);
if (value == recordingDistanceIntervalDefault) {
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else {
options[i] = displayValue;
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = context.getString(R.string.value_integer_feet, feet);
if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) {
options[i] = context.getString(R.string.value_integer_feet_recommended, feet);
if (value == recordingDistanceIntervalDefault) {
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else {
options[i] = displayValue;
entries[i] = displayValue;
}
}
}
return entries;
}
/**
* Sets the max recording distance options.
*
* @param options the options
* @param values the values
* @param metricUnits true for metric units
*/
static void setMaxRecordingDistanceOptions(Context context, String[] options, String[] values, boolean metricUnits) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
static String[] getMaxRecordingDistanceEntries(Context context, boolean metricUnits) {
String[] entryValues = context.getResources().getStringArray(R.array.max_recording_distance_values);
String[] entries = new String[entryValues.length];
final int maxRecordingDistanceDefault = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value);
if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) {
options[i] = context.getString(R.string.value_integer_meter_recommended, value);
if (value == maxRecordingDistanceDefault) {
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else {
options[i] = displayValue;
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
if (feet < 2000) {
displayValue = context.getString(R.string.value_integer_feet, feet);
if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) {
options[i] = context.getString(R.string.value_integer_feet_recommended, feet);
if (value == maxRecordingDistanceDefault) {
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else {
options[i] = displayValue;
entries[i] = displayValue;
}
} else {
double mile = feet * UnitConversions.FT_TO_MI;
displayValue = context.getString(R.string.value_float_mile, mile);
options[i] = displayValue;
entries[i] = displayValue;
}
}
}
return entries;
}
/**
* Sets the recording gps accuracy options.
*
* @param options the options
* @param values the values
* @param metricUnits true for metric units
*/
static void setRecordingGpsAccuracyOptions(Context context, String[] options, String[] values, boolean metricUnits) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
static String[] getRecordingGpsAccuracyEntries(Context context, boolean metricUnits) {
String[] entryValues = context.getResources().getStringArray(R.array.recording_gps_accuracy_values);
String[] entries = new String[entryValues.length];
final int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
final int recordingGPSAccuracyExcellent = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_excellent));
final int recordingGPSAccuracyPoor = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_poor));
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value);
switch (value) {
case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT:
options[i] = context.getString(R.string.value_integer_meter_recommended, value);
break;
case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT:
options[i] = context.getString(R.string.value_integer_meter_excellent_gps, value);
break;
case PreferencesUtils.RECORDING_GPS_ACCURACY_POOR:
options[i] = context.getString(R.string.value_integer_meter_poor_gps, value);
break;
default:
options[i] = displayValue;
if (value == recordingGPSAccuracyDefault) {
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else if (value == recordingGPSAccuracyExcellent) {
entries[i] = context.getString(R.string.value_integer_meter_excellent_gps, value);
} else if (value == recordingGPSAccuracyPoor) {
entries[i] = context.getString(R.string.value_integer_meter_poor_gps, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
if (feet < 2000) {
displayValue = context.getString(R.string.value_integer_feet, feet);
switch (value) {
case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT:
options[i] = context.getString(R.string.value_integer_feet_recommended, feet);
break;
case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT:
options[i] = context.getString(R.string.value_integer_feet_excellent_gps, feet);
break;
default:
options[i] = displayValue;
if (value == recordingGPSAccuracyDefault) {
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else if (value == recordingGPSAccuracyExcellent) {
entries[i] = context.getString(R.string.value_integer_feet_excellent_gps, feet);
} else {
entries[i] = displayValue;
}
} else {
double mile = feet * UnitConversions.FT_TO_MI;
displayValue = context.getString(R.string.value_float_mile, mile);
if (value == PreferencesUtils.RECORDING_GPS_ACCURACY_POOR) {
options[i] = context.getString(R.string.value_float_mile_poor_gps, mile);
if (value == recordingGPSAccuracyPoor) {
entries[i] = context.getString(R.string.value_float_mile_poor_gps, mile);
} else {
options[i] = displayValue;
entries[i] = displayValue;
}
}
}
}
return entries;
}
/**
* Sets the auto resume track timeout options.
*
* @param options the options
* @param values the values
*/
static void setAutoResumeTrackTimeoutOptions(Context context, String[] options, String[] values) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
switch (value) {
case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER:
options[i] = context.getString(R.string.value_never);
break;
case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS:
options[i] = context.getString(R.string.value_always);
break;
default:
options[i] = context.getString(R.string.value_integer_minute, value);
static String[] getAutoResumeTrackTimeoutEntries(Context context) {
String[] entryValues = context.getResources().getStringArray(R.array.auto_resume_track_timeout_values);
String[] entries = new String[entryValues.length];
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
entries[i] = context.getString(R.string.value_never);
} else if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
entries[i] = context.getString(R.string.value_always);
} else {
entries[i] = context.getString(R.string.value_integer_minute, value);
}
}
}
/**
* Configures the bluetooth sensor.
*/
static void configureBluetoothSensorList(ListPreference preference) {
Context context = preference.getContext();
String value = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
List<String> devicesNameList = new ArrayList<>();
List<String> devicesAddressList = new ArrayList<>();
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null) {
BluetoothUtils.populateDeviceLists(bluetoothAdapter, devicesNameList, devicesAddressList);
}
// Was the previously configured device unpaired? Then forget it.
if (!devicesAddressList.contains(value)) {
value = PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT;
PreferencesUtils.setString(context, R.string.bluetooth_sensor_key, value);
}
devicesNameList.add(0, context.getString(R.string.value_none));
devicesAddressList.add(0, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
String[] values = devicesAddressList.toArray(new String[0]);
preference.setEntryValues(values);
String[] options = devicesNameList.toArray(new String[0]);
preference.setEntries(options);
return entries;
}
}
@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.Toast;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
@@ -17,6 +18,10 @@ public class ResetDialogPreference extends DialogPreference {
super(context, attrs);
}
interface ResetCallback {
void onReset();
}
public static class ResetPreferenceDialog extends PreferenceDialogFragmentCompat {
static PreferenceDialogFragmentCompat newInstance(String preferenceKey) {
@@ -31,9 +36,14 @@ public class ResetDialogPreference extends DialogPreference {
@Override
public void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
PreferencesUtils.resetPreferences(getActivity(), true);
FragmentActivity activity = getActivity();
Toast.makeText(getActivity(), R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
PreferencesUtils.resetPreferences(activity, true);
Toast.makeText(activity, R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
if (activity instanceof ResetCallback) {
((ResetCallback) activity).onReset();
}
}
}
}
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.settings;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
@@ -12,12 +13,16 @@ import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.HackUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
public class SettingsActivity extends AppCompatActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
public class SettingsActivity extends AppCompatActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ResetDialogPreference.ResetCallback {
private PrefsFragment prefsFragment = new PrefsFragment();
private static final String TAG = SettingsActivity.class.getSimpleName();
private PrefsFragment prefsFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -29,7 +34,7 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
toolbar.setTitle(R.string.menu_settings);
setSupportActionBar(toolbar);
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit();
onReset();
}
@Override
@@ -37,6 +42,12 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
prefsFragment.setDefaultActivity(iconValue);
}
@Override
public void onReset() {
prefsFragment = new PrefsFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit();
}
public static class PrefsFragment extends PreferenceFragmentCompat {
/*
@@ -46,14 +57,15 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
updateUI();
}
});
if (key == null || key.equals(PreferencesUtils.getKey(getActivity(), R.string.stats_units_key))) {
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
updateReset();
}
});
}
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -61,6 +73,14 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
}
});
}
if (PreferencesUtils.isKey(getActivity(), R.string.stats_rate_key, key)) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
updateSpeed();
}
});
}
}
};
@@ -69,15 +89,25 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.settings, rootKey);
updateUnits();
try {
setPreferencesFromResource(R.xml.settings, rootKey);
} catch (ClassCastException e) {
// Some sharedPreference is broken: delete all and reset them; it is just a last resort...
Toast.makeText(getContext(), R.string.settings_error_initial_values_restored, Toast.LENGTH_LONG).show();
PreferencesUtils.resetPreferences(getContext(), true);
setPreferencesFromResource(R.xml.settings, rootKey);
}
}
@Override
public void onResume() {
super.onResume();
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
updateUI();
updateSpeed();
updateUnits();
updateReset();
updateBluetooth();
}
@Override
@@ -86,41 +116,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
PreferencesUtils.getSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
private void configFrequencyPreference(int key, boolean metricUnits) {
ListPreference preference = findPreference(getString(key));
String[] options = StringUtils.getFrequencyOptions(getActivity(), metricUnits);
preference.setEntries(options);
}
private void configListPreference(int key, int valueArray, boolean metricUnits) {
//TODO Can we make values an int array?
String[] values = getResources().getStringArray(valueArray);
final String[] options = new String[values.length];
switch (key) {
case R.string.min_recording_interval_key:
PreferenceHelper.setMinRecordingIntervalOptions(getActivity(), options, values);
break;
case R.string.recording_distance_interval_key:
PreferenceHelper.setRecordingDistanceIntervalOptions(getActivity(), options, values, metricUnits);
break;
case R.string.max_recording_distance_key:
PreferenceHelper.setMaxRecordingDistanceOptions(getActivity(), options, values, metricUnits);
break;
case R.string.recording_gps_accuracy_key:
PreferenceHelper.setRecordingGpsAccuracyOptions(getActivity(), options, values, metricUnits);
break;
case R.string.auto_resume_track_timeout_key:
PreferenceHelper.setAutoResumeTrackTimeoutOptions(getActivity(), options, values);
break;
default:
return;
}
final ListPreference listPreference = findPreference(getString(key));
listPreference.setEntries(options);
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
@@ -129,6 +124,8 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
} else if (preference instanceof ActivityTypePreference) {
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
dialogFragment = activityPreferenceDialog;
} else if (preference instanceof BluetoothLePreference) {
dialogFragment = BluetoothLePreference.BluetoothLePreferenceDialog.newInstance(preference.getKey());
}
if (dialogFragment != null) {
@@ -146,37 +143,53 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
}
}
private void updateUI() {
private void updateReset() {
final boolean isRecording = PreferencesUtils.isRecording(getActivity());
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
resetPreference.setEnabled(!isRecording);
}
private void updateSpeed() {
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace);
}
ListPreference bluetoothPreference = findPreference(getString(R.string.bluetooth_sensor_key));
PreferenceHelper.configureBluetoothSensorList(bluetoothPreference);
private void updateBluetooth() {
// Disable Bluetooth preference if device does not have Bluetooth
BluetoothLePreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key));
bluetoothPreference.setVisible(BluetoothUtils.hasBluetooth(TAG));
}
private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity());
configFrequencyPreference(R.string.voice_frequency_key, metricUnits);
configFrequencyPreference(R.string.split_frequency_key, metricUnits);
configListPreference(R.string.min_recording_interval_key, R.array.min_recording_interval_values, metricUnits);
configListPreference(R.string.recording_distance_interval_key, R.array.recording_distance_interval_values, metricUnits);
configListPreference(R.string.max_recording_distance_key, R.array.max_recording_distance_values, metricUnits);
configListPreference(R.string.recording_gps_accuracy_key, R.array.recording_gps_accuracy_values, metricUnits);
configListPreference(R.string.auto_resume_track_timeout_key, R.array.auto_resume_track_timeout_values, metricUnits);
configListPreference(R.string.auto_resume_track_timeout_key, R.array.auto_resume_track_timeout_values, metricUnits);
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
final ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
String[] options = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
statsRatePreferences.setEntries(options);
//TODO This is a hack!!! Need to manually updated the summary as otherwise it will only be updated after scrolling down and up again (bring the object out of view).
//Check if this is still needed after upgraded PreferenceFragment.
statsRatePreferences.setSummary(statsRatePreferences.getEntry());
ListPreference splitFrequency = findPreference(getString(R.string.split_frequency_key));
splitFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
ListPreference recordingDistanceInterval = findPreference(getString(R.string.recording_distance_interval_key));
recordingDistanceInterval.setEntries(PreferenceHelper.getRecordingDistanceIntervalEntries(getActivity(), metricUnits));
ListPreference maxRecordingDistance = findPreference(getString(R.string.max_recording_distance_key));
maxRecordingDistance.setEntries(PreferenceHelper.getMaxRecordingDistanceEntries(getActivity(), metricUnits));
ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key));
recordingGpsAccuracy.setEntries(PreferenceHelper.getRecordingGpsAccuracyEntries(getActivity(), metricUnits));
ListPreference autoresumeTrack = findPreference(getString(R.string.auto_resume_track_timeout_key));
autoresumeTrack.setEntries(PreferenceHelper.getAutoResumeTrackTimeoutEntries(getActivity()));
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
statsRatePreferences.setEntries(entries);
HackUtils.invalidatePreference(statsRatePreferences);
}
}
}
@@ -16,12 +16,13 @@
package de.dennisguse.opentracks.util;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
import java.util.UUID;
/**
* Utilities for dealing with bluetooth devices.
@@ -30,29 +31,50 @@ import java.util.Set;
*/
public class BluetoothUtils {
public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
private BluetoothUtils() {
}
/**
* Populates the device names and the device addresses with all the suitable
* bluetooth devices.
*
* @param bluetoothAdapter the bluetooth adapter
* @param deviceNames list of device names
* @param deviceAddresses list of device addresses
*/
public static void populateDeviceLists(BluetoothAdapter bluetoothAdapter, List<String> deviceNames, List<String> deviceAddresses) {
// Ensure the bluetooth adapter is not in discovery mode.
bluetoothAdapter.cancelDiscovery();
public static BluetoothAdapter getDefaultBluetoothAdapter(final String TAG) {
// If from the main application thread, return directly
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
return BluetoothAdapter.getDefaultAdapter();
}
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice device : pairedDevices) {
BluetoothClass bluetoothClass = device.getBluetoothClass();
if (bluetoothClass != null && (bluetoothClass.getDeviceClass() & BluetoothClass.Device.HEALTH_PULSE_RATE) != 0) {
deviceAddresses.add(device.getAddress());
deviceNames.add(device.getName());
// Get the default adapter from the main application thread
final ArrayList<BluetoothAdapter> adapters = new ArrayList<>(1);
final Object mutex = new Object();
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
adapters.add(BluetoothAdapter.getDefaultAdapter());
synchronized (mutex) {
mutex.notify();
}
}
});
while (adapters.isEmpty()) {
synchronized (mutex) {
try {
mutex.wait(UnitConversions.ONE_SECOND);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
}
}
}
if (adapters.get(0) == null) {
Log.w(TAG, "No bluetooth adapter found.");
}
return adapters.get(0);
}
public static boolean hasBluetooth(final String TAG) {
return BluetoothUtils.getDefaultBluetoothAdapter(TAG) != null;
}
public static int parseHeartRate(BluetoothGattCharacteristic characteristic) {
@@ -0,0 +1,22 @@
package de.dennisguse.opentracks.util;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
public class HackUtils {
private HackUtils() {
}
/**
* Triggers a redraw of the summary of a preference if it was set programmatically.
* Need to trigger androidx.preference.preferences.notifyChanged() to trigger a redraw, but method is protected.
* This workaround also works when this preference has not changed, but it's entries (see R.string.stats_rate_key).
* TODO
*/
public static void invalidatePreference(@NonNull Preference preference) {
boolean isEnabled = preference.isEnabled();
preference.setEnabled(!isEnabled);
preference.setEnabled(isEnabled);
}
}
@@ -70,7 +70,6 @@ public class IntentUtils {
* @param context the context
* @param trackIds the track ids
*/
//TODO Share mulitple tracks in different files!
public static Intent newShareFileIntent(Context context, long[] trackIds) {
if (trackIds.length == 0) {
throw new RuntimeException("Need to share at least one track.");
@@ -29,7 +29,6 @@ import de.dennisguse.opentracks.content.Track;
*
* @author Leif Hendrik Wilden
*/
//TODO Cleanup: rename variables
public class LocationUtils {
private static final String TAG = LocationUtils.class.getSimpleName();
@@ -158,7 +157,7 @@ public class LocationUtils {
}
/**
* Checks if a given location is a valid (i.e. physically possible) locationon Earth.
* Checks if a given location is a valid (i.e. physically possible) location on Earth.
* Note: The special separator locations (which have latitude = 100) will not qualify as valid.
* Neither will locations with lat=0 and lng=0 as these are most likely "bad" measurements which often cause trouble.
*
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceManager;
import de.dennisguse.opentracks.R;
@@ -31,67 +32,8 @@ import de.dennisguse.opentracks.R;
*/
public class PreferencesUtils {
/*
* Preferences values.
* The defaults need to match the defaults in the xml files.
*/
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
// Values for auto_resume_track_timeout_key
public static final int AUTO_RESUME_TRACK_TIMEOUT_ALWAYS = -1;
public static final int AUTO_RESUME_TRACK_TIMEOUT_DEFAULT = 10;
public static final int AUTO_RESUME_TRACK_TIMEOUT_NEVER = 0;
public static final String BLUETOOTH_SENSOR_DEFAULT = "";
public static final boolean CHART_SHOW_CADENCE_DEFAULT = true;
public static final boolean CHART_SHOW_ELEVATION_DEFAULT = true;
public static final boolean CHART_SHOW_HEART_RATE_DEFAULT = true;
public static final boolean CHART_SHOW_POWER_DEFAULT = true;
public static final boolean CHART_SHOW_SPEED_DEFAULT = true;
@Deprecated
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
//NOTE: This is also used to recover from a reboot, but this data should not be exposed to the whole application.
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
public static final String DEFAULT_ACTIVITY_DEFAULT = "";
// Value for split_frequency_key and voice_frequency_key
public static final int FREQUENCY_OFF = 0;
public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200;
// Values for min_recording_interval_key
public static final int MIN_RECORDING_INTERVAL_ADAPT_ACCURACY = -1;
public static final int MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = -2;
public static final int MIN_RECORDING_INTERVAL_DEFAULT = 0;
public static final int RECORDING_DISTANCE_INTERVAL_DEFAULT = 10;
// Values for recording_gps_accuracy
public static final int RECORDING_GPS_ACCURACY_DEFAULT = 50;
public static final int RECORDING_GPS_ACCURACY_EXCELLENT = 10;
public static final int RECORDING_GPS_ACCURACY_POOR = 2000;
static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
public static final int SPLIT_FREQUENCY_DEFAULT = 0;
static final boolean STATS_SHOW_ELEVATION_DEFAULT = false;
static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601";
private static final String CHART_X_AXIS_DEFAULT = "DISTANCE";
public static final String STATS_UNITS_DEFAULT = "METRIC";
// Stats
private static final String STATS_RATE_DEFAULT = "SPEED";
public static final boolean SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN = false;
// Track widget
public static final int TRACK_WIDGET_ITEM1_DEFAULT = 3; // moving time
public static final int TRACK_WIDGET_ITEM2_DEFAULT = 0; // distance
public static final int TRACK_WIDGET_ITEM3_DEFAULT = 1; // total time
public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed
public static final int VOICE_FREQUENCY_DEFAULT = 0;
private PreferencesUtils() {
}
@@ -99,16 +41,60 @@ public class PreferencesUtils {
return PreferenceManager.getDefaultSharedPreferences(context);
}
@Deprecated
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
//NOTE: This is also used to recover from a reboot, but this data should not be exposed to the whole application.
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
public static long getRecordingTrackId(Context context) {
return PreferencesUtils.getLong(context, R.string.recording_track_id_key, RECORDING_TRACK_ID_DEFAULT);
}
@VisibleForTesting
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
public static int getAutoResumeTrackCurrentRetryDefault(Context context) {
return PreferencesUtils.getInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
}
public static void resetAutoResumeTrackCurrentRetryDefault(Context context) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
}
public static void incrementAutoResumeTrackCurrentRetryDefault(Context context) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, getAutoResumeTrackCurrentRetryDefault(context) + 1);
}
public static String getDefaultActivity(Context context) {
return PreferencesUtils.getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
}
public static void setDefaultActivity(Context context, String newDefaultActivity) {
PreferencesUtils.setString(context, R.string.default_activity_key, newDefaultActivity);
}
/**
* Gets a preference key
*
* @param context the context
* @param keyId the key id
*/
public static String getKey(Context context, int keyId) {
private static String getKey(Context context, int keyId) {
return context.getString(keyId);
}
/**
* Compares if keyId and key belong to the same shared preference key.
*
* @param keyId The resource id of the key
* @param key
* @return true if key == null or key belongs to keyId
*/
public static boolean isKey(Context context, int keyId, String key) {
return key == null || key.equals(PreferencesUtils.getKey(context, keyId));
}
/**
* Gets a boolean preference value.
*
@@ -180,9 +166,9 @@ public class PreferencesUtils {
* @param context the context
* @param keyId the key id
*/
public static long getLong(Context context, int keyId) {
private static long getLong(Context context, int keyId, long defaultValue) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
return sharedPreferences.getLong(getKey(context, keyId), -1L);
return sharedPreferences.getLong(getKey(context, keyId), defaultValue);
}
/**
@@ -231,7 +217,8 @@ public class PreferencesUtils {
* @param context the context
*/
public static boolean isMetricUnits(Context context) {
return STATS_UNITS_DEFAULT.equals(getString(context, R.string.stats_units_key, STATS_UNITS_DEFAULT));
final String STATS_UNIT = context.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(context, R.string.stats_units_key, STATS_UNIT));
}
/**
@@ -241,20 +228,129 @@ public class PreferencesUtils {
* @param context the context
*/
public static boolean isReportSpeed(Context context) {
final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT));
}
/**
* Returns true if chart x axis is by distance, false if by time.
*
* @param context the context
*/
public static boolean isChartByDistance(Context context) {
return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_DEFAULT));
public static int getAutoResumeTrackTimeout(Context context) {
final int AUTO_RESUME_TRACK_TIMEOUT = Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default));
return PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, AUTO_RESUME_TRACK_TIMEOUT);
}
public static boolean isRecordingTrackPaused(Context context) {
return PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
}
public static boolean isRecordingTrackPausedDefault(Context context) {
return context.getResources().getBoolean(R.bool.recording_track_paused_default);
}
public static void defaultRecordingTrackPaused(Context context) {
final boolean RECORDING_TRACK_PAUSED = context.getResources().getBoolean(R.bool.recording_track_paused_default);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
}
public static boolean isBluetoothHeartRateSensorAddressDefault(Context context) {
return PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(getBluetoothHeartRateSensorAddress(context));
}
public static String getBluetoothHeartRateSensorAddress(Context context) {
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
}
public static boolean shouldChartShowCadence(Context context) {
final boolean CHART_SHOW_CADENCE = context.getResources().getBoolean(R.bool.chart_show_cadence_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_cadence_key, CHART_SHOW_CADENCE);
}
public static boolean shouldChartShowElevation(Context context) {
final boolean CHART_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.chart_show_elevation_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_elevation_key, CHART_SHOW_ELEVATION);
}
public static boolean shouldChartShowHeartRate(Context context) {
final boolean CHART_SHOW_HEARTRATE = context.getResources().getBoolean(R.bool.chart_show_heart_rate_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_heart_rate_key, CHART_SHOW_HEARTRATE);
}
public static boolean shouldChartShowPower(Context context) {
final boolean CHART_SHOW_POWER = context.getResources().getBoolean(R.bool.chart_show_power_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_power_key, CHART_SHOW_POWER);
}
public static boolean shouldChartShowSpeed(Context context) {
final boolean CHART_SHOW_SPEED = context.getResources().getBoolean(R.bool.chart_show_speed_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_speed_key, CHART_SHOW_SPEED);
}
public static boolean shouldShowStatsOnLockscreen(Context context) {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
}
public static boolean isShowStatsGradeElevation(Context context) {
final boolean STATS_SHOW_GRADE_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_grade_elevation_default);
return PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_GRADE_ELEVATION);
}
public static boolean isStatsShowCoordinate(Context context) {
final boolean STATS_SHOW_COORDINATE = context.getResources().getBoolean(R.bool.stats_show_coordinate_default);
return PreferencesUtils.getBoolean(context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
}
public static int getVoiceFrequency(Context context) {
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
return PreferencesUtils.getInt(context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
}
public static int getSplitFrequency(Context context) {
final int SPLIT_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.split_frequency_default));
return PreferencesUtils.getInt(context, R.string.split_frequency_key, SPLIT_FREQUENCY_DEFAULT);
}
public static int getRecordingDistanceInterval(Context context) {
final int RECORDING_DISTANCE_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
return PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
}
public static int getRecordingDistanceIntervalDefault(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
}
public static int getMaxRecordingDistance(Context context) {
final int MAX_RECORDING_DISTANCE = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
return PreferencesUtils.getInt(context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE);
}
public static int getMinRecordingInterval(Context context) {
final int MIN_RECORDING_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
return PreferencesUtils.getInt(context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
}
public static int getMinRecordingIntervalAdaptAccuracy(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_adapt_accuracy));
}
public static int getMinRecordingIntervalAdaptBatteryLife(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_adapt_battery_life));
}
public static int getMinRecordingIntervalDefault(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
}
public static int getRecordingGPSAccuracy(Context context) {
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
return PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
}
public static boolean isRecording(Context context) {
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
return recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@@ -263,6 +359,10 @@ public class PreferencesUtils {
}
public static void resetPreferences(Context context, boolean readAgain) {
if (readAgain) {
// We want to really clear settings now.
getSharedPreferences(context).edit().clear().commit();
}
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
}
}
@@ -70,7 +70,7 @@ public class StatsUtils {
}
// Set elevation
boolean showGradeElevation = PreferencesUtils.getBoolean(activity, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_ELEVATION_DEFAULT) && isRecording;
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(activity);
View elevationContainer = activity.findViewById(R.id.stats_elevation);
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
@@ -85,7 +85,7 @@ public class StatsUtils {
}
// Set coordinate
boolean showCoordinate = isRecording && PreferencesUtils.getBoolean(activity, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(activity);
View coordinateSeparator = activity.findViewById(R.id.stats_coordinate_separator);
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
@@ -108,7 +108,7 @@ public class StatsUtils {
public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
// heart rate
int isVisible = View.VISIBLE;
if (!isRecording || PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(PreferencesUtils.getString(activity, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT))) {
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(activity)) {
isVisible = View.INVISIBLE;
}
activity.findViewById(R.id.stats_sensor_container).setVisibility(isVisible);
@@ -125,11 +125,18 @@ public class StatsUtils {
}
}
heartRateValue.setText(heartRate);
setBluetoothHeartRateSensorName(activity, sensor, isRecording);
heartRateSensor.setText(sensor);
}
}
public static void setBluetoothHeartRateSensorName(Activity activity, String sensorName, boolean isRecording) {
if (isRecording) {
TextView heartRateSensor = activity.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
heartRateSensor.setText(sensorName);
}
}
/**
* Sets the total time value.
*
@@ -151,6 +158,7 @@ public class StatsUtils {
public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics, String trackIconValue) {
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
boolean isRecording = PreferencesUtils.isRecording(activity);
// Set total distance
{
@@ -173,6 +181,7 @@ public class StatsUtils {
if (trackIconValue != null) {
TrackIconUtils.setIconSpinner(spinner, trackIconValue);
}
spinner.setEnabled(isRecording);
}
// Set time
@@ -241,7 +250,7 @@ public class StatsUtils {
// Set elevation
{
boolean showElevation = PreferencesUtils.getBoolean(activity, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_ELEVATION_DEFAULT);
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(activity);
View gradeElevationSeparator = activity.findViewById(R.id.stats_elevation_separator);
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
@@ -359,7 +359,7 @@ public class StringUtils {
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
if (value == PreferencesUtils.FREQUENCY_OFF) {
if (context.getString(R.string.frequency_off).equals(values[i])) {
options[i] = context.getString(R.string.value_off);
} else if (value < 0) {
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, Math.abs(value));
@@ -0,0 +1,13 @@
package de.dennisguse.opentracks.util;
import android.speech.tts.TextToSpeech;
public class TTSUtils {
private TTSUtils() {
}
public static int getTTSStream() {
return TextToSpeech.Engine.DEFAULT_STREAM;
}
}
@@ -47,7 +47,7 @@ public class TrackNameUtils {
* @param startTime the track start time
*/
public static String getTrackName(Context context, long trackId, long startTime) {
String trackName = PreferencesUtils.getString(context, R.string.track_name_key, PreferencesUtils.TRACK_NAME_DEFAULT);
String trackName = PreferencesUtils.getString(context, R.string.track_name_key, context.getString(R.string.track_name_default));
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
return StringUtils.formatDateTime(context, startTime);
+188
View File
@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_feature_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_feature_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_track_map_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:linksClickable="true"
android:text="@string/help_track_map_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_data_statistics_customized_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_data_statistics_customized_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_recording_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_recording_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_gps_imprecise_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_gps_imprecise_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_heart_rate_sensors_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_heart_rate_sensors_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_storage_pictures_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_storage_pictures_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_track_export_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_track_export_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_data_upload_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/generic_no" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_data_google_play_services_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/generic_no" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_data_online_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/generic_no" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/help_data_storage_title"
android:textStyle="bold" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_data_storage_content" />
</LinearLayout>
</ScrollView>
</LinearLayout>
+1 -1
View File
@@ -42,7 +42,7 @@ limitations under the License.
android:id="@+id/track_detail_markers"
android:icon="@drawable/ic_marker_show_24dp"
android:orderInCategory="5"
android:showAsAction="ifRoom"
app:showAsAction="ifRoom"
android:title="@string/menu_markers" />
<item
android:id="@+id/track_detail_edit"
+58 -10
View File
@@ -15,20 +15,68 @@ limitations under the License.
-->
<resources>
<string name="app_web_url" translatable="false">https://github.com/dennisguse/opentracks</string>
<string name="chart_x_axis_distance" translatable="false">DISTANCE</string>
<string name="chart_x_axis_time" translatable="false">TIME</string>
<!-- List here all used libraries (will be shown in the about activity -->
<string name="third_party_libraries" translatable="false"><a href="https://developer.android.com/topic/libraries/support-library/">Google\'s androidX support</a> (Apache License, Version 2.0)</string>
<string name="marker_statistics_icon_url" translatable="false">http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png</string>
<string name="marker_waypoint_icon_url" translatable="false">http://maps.google.com/mapfiles/ms/micons/blue-pushpin.png</string>
<string name="sensor_type_value_none" translatable="false">NONE</string>
<string name="sensor_unit_beats_per_minute" translatable="false">BPM</string>
<string name="settings_recording_track_name_date_iso_8601_value" translatable="false">DATE_ISO_8601</string>
<string name="settings_recording_track_name_date_local_value" translatable="false">DATE_LOCAL</string>
<string name="settings_recording_track_name_number_value" translatable="false">NUMBER</string>
<string name="stats_rate_pace" translatable="false">PACE</string>
<string name="stats_rate_speed" translatable="false">SPEED</string>
<string name="stats_units_imperial" translatable="false">IMPERIAL</string>
<string name="stats_units_metric" translatable="false">METRIC</string>
<string name="value_unknown" translatable="false">-</string>
<string-array name="waypoint_types">
<item>@string/waypoint_type_atm</item>
<item>@string/waypoint_type_bank</item>
<item>@string/waypoint_type_bar</item>
<item>@string/waypoint_type_barbecue</item>
<item>@string/waypoint_type_building</item>
<item>@string/waypoint_type_bus_stop</item>
<item>@string/waypoint_type_cafe</item>
<item>@string/waypoint_type_campfire</item>
<item>@string/waypoint_type_campground</item>
<item>@string/waypoint_type_church</item>
<item>@string/waypoint_type_coffee_house</item>
<item>@string/waypoint_type_convenience_store</item>
<item>@string/waypoint_type_drinking_water</item>
<item>@string/waypoint_type_falling_rocks</item>
<item>@string/waypoint_type_ferry</item>
<item>@string/waypoint_type_fire_place</item>
<item>@string/waypoint_type_fire_station</item>
<item>@string/waypoint_type_fishing</item>
<item>@string/waypoint_type_gas_station</item>
<item>@string/waypoint_type_golf_course</item>
<item>@string/waypoint_type_grocery_store</item>
<item>@string/waypoint_type_hair_dresser</item>
<item>@string/waypoint_type_harbor</item>
<item>@string/waypoint_type_hiking_path</item>
<item>@string/waypoint_type_hotel</item>
<item>@string/waypoint_type_house</item>
<item>@string/waypoint_type_information</item>
<item>@string/waypoint_type_intersection</item>
<item>@string/waypoint_type_mail_box</item>
<item>@string/waypoint_type_market</item>
<item>@string/waypoint_type_meeting_point</item>
<item>@string/waypoint_type_opera</item>
<item>@string/waypoint_type_parking</item>
<item>@string/waypoint_type_police_station</item>
<item>@string/waypoint_type_post_office</item>
<item>@string/waypoint_type_pothole</item>
<item>@string/waypoint_type_railway_crossing</item>
<item>@string/waypoint_type_restaurant</item>
<item>@string/waypoint_type_shopping</item>
<item>@string/waypoint_type_sight</item>
<item>@string/waypoint_type_store</item>
<item>@string/waypoint_type_swimming_pool</item>
<item>@string/waypoint_type_telephone</item>
<item>@string/waypoint_type_theater</item>
<item>@string/waypoint_type_toilet</item>
<item>@string/waypoint_type_tourist_information</item>
<item>@string/waypoint_type_tree</item>
<item>@string/waypoint_type_tunnel</item>
<item>@string/waypoint_type_waterfall</item>
<item>@string/waypoint_type_water_fountain</item>
<item>@string/waypoint_type_waypoint</item>
</string-array>
</resources>
-49
View File
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Keys for persisted preferences. Sorted alphabetically. DO NOT TRANSLATE. -->
<string name="auto_resume_track_timeout_key" translatable="false">autoResumeTrackTimeout</string>
<string name="chart_show_cadence_key" translatable="false">chartShowCadence</string>
<string name="chart_show_elevation_key" translatable="false">chartShowElevation</string>
<string name="chart_show_heart_rate_key" translatable="false">chartShowHeartRate</string>
<string name="chart_show_power_key" translatable="false">chartShowPower</string>
<string name="chart_show_speed_key" translatable="false">chartShowSpeed</string>
<string name="chart_x_axis_key" translatable="false">chartXAxis</string>
<string name="default_activity_key" translatable="false">defaultActivity</string>
<string name="max_recording_distance_key" translatable="false">maxRecordingDistance</string>
<string name="min_recording_interval_key" translatable="false">minRecordingInterval</string>
<string name="recording_distance_interval_key" translatable="false">recordingDistanceInterval</string>
<string name="recording_gps_accuracy_key" translatable="false">recordingGpsAccuracy</string>
<string name="split_frequency_key" translatable="false">splitFrequency</string>
<string name="stats_rate_key" translatable="false">statsRate</string>
<string name="stats_show_coordinate_key" translatable="false">statsShowCoordinate</string>
<string name="stats_show_grade_elevation_key" translatable="false">statsShowGradeElevation</string>
<string name="stats_units_key" translatable="false">statsUnits</string>
<string name="trackdetail_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
<string name="track_name_key" translatable="false">trackName</string>
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
<!-- Keys for persistend preferences. But they should not get backed up or restored. -->
<string name="auto_resume_track_current_retry_key" translatable="false">autoResumeTrackCurrentRetry</string>
<string name="bluetooth_sensor_key" translatable="false">bluetoothSensor</string>
<string name="recording_track_id_key" translatable="false">recordingTrackId</string>
<string name="recording_track_paused_key" translatable="false">recordingTrackPaused</string>
<!-- Keys for non-persisted preferences. Sorted alphabetically. DO NOT TRANSLATE. -->
<string name="settings_recording_key" translatable="false">settingsRecording</string>
<string name="settings_reset_key" translatable="false">settingsReset</string>
<string name="settings_sensor_bluetooth_pairing_key" translatable="false">settingsSensorBluetoothPairing</string>
<string name="settings_stats_key" translatable="false">settingsStats</string>
</resources>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Keys for persisted preferences that are used by internally. -->
<!--But they should not get backed up or restored.-->
<string name="auto_resume_track_current_retry_key" translatable="false">autoResumeTrackCurrentRetry</string>
<string name="recording_track_id_key" translatable="false">recordingTrackId</string>
<string name="recording_track_paused_key" translatable="false">recordingTrackPaused</string>
<bool name="recording_track_paused_default" translatable="false">true</bool>
</resources>
@@ -1,21 +1,40 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Do not translate any arrays in this file. -->
<!-- Do not translate anything in this file directly. -->
<string name="chart_show_cadence_key" translatable="false">chartShowCadence</string>
<bool name="chart_show_cadence_default" translatable="false">true</bool>
<string name="chart_show_elevation_key" translatable="false">chartShowElevation</string>
<bool name="chart_show_elevation_default" translatable="false">true</bool>
<string name="chart_show_heart_rate_key" translatable="false">chartShowHeartRate</string>
<bool name="chart_show_heart_rate_default" translatable="false">true</bool>
<string name="chart_show_power_key" translatable="false">chartShowPower</string>
<bool name="chart_show_power_default" translatable="false">true</bool>
<string name="chart_show_speed_key" translatable="false">chartShowSpeed</string>
<bool name="chart_show_speed_default" translatable="false">true</bool>
<string name="stats_show_coordinate_key" translatable="false">statsShowCoordinate</string>
<bool name="stats_show_coordinate_default" translatable="false">false</bool>
<string name="stats_show_grade_elevation_key" translatable="false">statsShowGradeElevation</string>
<bool name="stats_show_grade_elevation_default" translatable="false">false</bool>
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
<string name="settings_reset_key" translatable="false">settingsReset</string>
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
<string name="sensor_type_value_none" translatable="false">NONE</string>
<string name="default_activity_key" translatable="false">defaultActivity</string>
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
<string-array name="activity_types">
<item>@string/default_activity_default</item>
<item>@string/activity_type_airplane</item>
<item>@string/activity_type_atv</item>
<item>@string/activity_type_biking</item>
@@ -77,29 +96,31 @@ limitations under the License.
<item>@string/activity_type_trail_running</item>
<item>@string/activity_type_train</item>
<item>@string/activity_type_ultimate_frisbee</item>
<item>@string/activity_type_unknown</item>
<item>@string/activity_type_wakeboarding</item>
<item>@string/activity_type_walking</item>
<item>@string/activity_type_water_skiing</item>
<item>@string/activity_type_wind_surfing</item>
</string-array>
<string name="auto_resume_track_timeout_key" translatable="false">autoResumeTrackTimeout</string>
<string name="auto_resume_track_timeout_default" translatable="false">10</string>
<string name="auto_resume_track_timeout_always" translatable="false">-1</string>
<string name="auto_resume_track_timeout_never" translatable="false">0</string>
<string-array name="auto_resume_track_timeout_values">
<item>0</item>
<item>@string/auto_resume_track_timeout_never</item>
<item>5</item>
<item>10</item>
<item>@string/auto_resume_track_timeout_default</item>
<item>15</item>
<item>-1</item>
</string-array>
<string-array name="chart_x_axis_options">
<item>@string/settings_chart_by_distance</item>
<item>@string/settings_chart_by_time</item>
</string-array>
<string-array name="chart_x_axis_values">
<item>@string/chart_x_axis_distance</item>
<item>@string/chart_x_axis_time</item>
<item>@string/auto_resume_track_timeout_always</item>
</string-array>
<string name="split_frequency_key" translatable="false">splitFrequency</string>
<string name="split_frequency_default" translatable="false">@string/frequency_off</string>
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
<string name="frequency_off" translatable="false">0</string>
<string-array name="frequency_values">
<item>0</item>
<item>@string/frequency_off</item>
<item>1</item>
<item>2</item>
<item>5</item>
@@ -114,18 +135,38 @@ limitations under the License.
<item>-50</item>
<item>-100</item>
</string-array>
<string name="recording_distance_interval_key" translatable="false">recordingDistanceInterval</string>
<string name="recording_distance_interval_default" translatable="false">10</string>
<string-array name="recording_distance_interval_values">
<item>1</item>
<item>2</item>
<item>5</item>
<item>@string/recording_distance_interval_default</item>
<item>20</item>
<item>50</item>
<item>100</item>
</string-array>
<string name="max_recording_distance_key" translatable="false">maxRecordingDistance</string>
<string name="max_recording_distance_default" translatable="false">200</string>
<string-array name="max_recording_distance_values">
<item>50</item>
<item>100</item>
<item>200</item>
<item>@string/max_recording_distance_default</item>
<item>500</item>
<item>1000</item>
<item>5000</item>
</string-array>
<string name="min_recording_interval_key" translatable="false">minRecordingInterval</string>
<string name="min_recording_interval_default" translatable="false">0</string>
<string name="min_recording_interval_adapt_accuracy" translatable="false">-1</string>
<string name="min_recording_interval_adapt_battery_life" translatable="false">-2</string>
<string-array name="min_recording_interval_values">
<item>-2</item>
<item>-1</item>
<item>0</item>
<item>@string/min_recording_interval_adapt_battery_life</item>
<item>@string/min_recording_interval_adapt_accuracy</item>
<item>@string/min_recording_interval_default</item>
<item>2</item>
<item>3</item>
<item>4</item>
@@ -140,26 +181,25 @@ limitations under the License.
<item>900</item>
<item>1800</item>
</string-array>
<string-array name="recording_distance_interval_values">
<item>1</item>
<item>2</item>
<item>5</item>
<item>10</item>
<item>20</item>
<item>50</item>
<item>100</item>
</string-array>
<string name="recording_gps_accuracy_key" translatable="false">recordingGpsAccuracy</string>
<string name="recording_gps_accuracy_default" translatable="false">50</string>
<string name="recording_gps_accuracy_excellent" translatable="false">10</string>
<string name="recording_gps_accuracy_poor" translatable="false">2000</string>
<string-array name="recording_gps_accuracy_values">
<item>10</item>
<item>@string/recording_gps_accuracy_excellent</item>
<item>20</item>
<item>30</item>
<item>50</item>
<item>@string/recording_gps_accuracy_default</item>
<item>100</item>
<item>200</item>
<item>500</item>
<item>1000</item>
<item>2000</item>
<item>@string/recording_gps_accuracy_poor</item>
</string-array>
<string name="stats_rate_key" translatable="false">statsRate</string>
<string name="stats_rate_default" translatable="false">@string/stats_rate_speed</string>
<string-array name="stats_rate_imperial_options">
<item>@string/description_speed_imperial</item>
<item>@string/description_pace_imperial</item>
@@ -168,79 +208,40 @@ limitations under the License.
<item>@string/description_speed_metric</item>
<item>@string/description_pace_metric</item>
</string-array>
<string name="stats_rate_pace" translatable="false">PACE</string>
<string name="stats_rate_speed" translatable="false">SPEED</string>
<string-array name="stats_rate_values">
<item>@string/stats_rate_speed</item>
<item>@string/stats_rate_pace</item>
</string-array>
<string name="stats_units_key" translatable="false">statsUnits</string>
<string name="stats_units_default" translatable="false">@string/stats_units_metric</string>
<string-array name="stats_units_values">
<item>@string/stats_units_metric</item>
<item>@string/stats_units_imperial</item>
</string-array>
<string name="stats_units_metric" translatable="false">METRIC</string>
<string name="stats_units_imperial" translatable="false">IMPERIAL</string>
<string-array name="stats_units_options">
<item>@string/settings_stats_units_metric</item>
<item>@string/settings_stats_units_imperial</item>
</string-array>
<string-array name="stats_units_values">
<item>@string/stats_units_metric</item>
<item>@string/stats_units_imperial</item>
<string name="track_name_key" translatable="false">trackName</string>
<string name="track_name_default" translatable="false">@string/settings_recording_track_name_date_iso_8601_value</string>
<string name="settings_recording_track_name_date_iso_8601_value" translatable="false">DATE_ISO_8601</string>
<string name="settings_recording_track_name_date_local_value" translatable="false">DATE_LOCAL</string>
<string name="settings_recording_track_name_number_value" translatable="false">NUMBER</string>
<string-array name="track_name_values">
<item>@string/settings_recording_track_name_date_local_value</item>
<item>@string/settings_recording_track_name_date_iso_8601_value</item>
<item>@string/settings_recording_track_name_number_value</item>
</string-array>
<string-array name="track_name_options">
<item>@string/settings_recording_track_name_date_local_option</item>
<item>@string/settings_recording_track_name_date_iso_8601_option</item>
<item>@string/settings_recording_track_name_number_option</item>
</string-array>
<string-array name="track_name_values">
<item>@string/settings_recording_track_name_date_local_value</item>
<item>@string/settings_recording_track_name_date_iso_8601_value</item>
<item>@string/settings_recording_track_name_number_value</item>
</string-array>
<string-array name="waypoint_types">
<item>@string/waypoint_type_atm</item>
<item>@string/waypoint_type_bank</item>
<item>@string/waypoint_type_bar</item>
<item>@string/waypoint_type_barbecue</item>
<item>@string/waypoint_type_building</item>
<item>@string/waypoint_type_bus_stop</item>
<item>@string/waypoint_type_cafe</item>
<item>@string/waypoint_type_campfire</item>
<item>@string/waypoint_type_campground</item>
<item>@string/waypoint_type_church</item>
<item>@string/waypoint_type_coffee_house</item>
<item>@string/waypoint_type_convenience_store</item>
<item>@string/waypoint_type_drinking_water</item>
<item>@string/waypoint_type_falling_rocks</item>
<item>@string/waypoint_type_ferry</item>
<item>@string/waypoint_type_fire_place</item>
<item>@string/waypoint_type_fire_station</item>
<item>@string/waypoint_type_fishing</item>
<item>@string/waypoint_type_gas_station</item>
<item>@string/waypoint_type_golf_course</item>
<item>@string/waypoint_type_grocery_store</item>
<item>@string/waypoint_type_hair_dresser</item>
<item>@string/waypoint_type_harbor</item>
<item>@string/waypoint_type_hiking_path</item>
<item>@string/waypoint_type_hotel</item>
<item>@string/waypoint_type_house</item>
<item>@string/waypoint_type_information</item>
<item>@string/waypoint_type_intersection</item>
<item>@string/waypoint_type_mail_box</item>
<item>@string/waypoint_type_market</item>
<item>@string/waypoint_type_meeting_point</item>
<item>@string/waypoint_type_opera</item>
<item>@string/waypoint_type_parking</item>
<item>@string/waypoint_type_police_station</item>
<item>@string/waypoint_type_post_office</item>
<item>@string/waypoint_type_pothole</item>
<item>@string/waypoint_type_railway_crossing</item>
<item>@string/waypoint_type_restaurant</item>
<item>@string/waypoint_type_shopping</item>
<item>@string/waypoint_type_sight</item>
<item>@string/waypoint_type_store</item>
<item>@string/waypoint_type_swimming_pool</item>
<item>@string/waypoint_type_telephone</item>
<item>@string/waypoint_type_theater</item>
<item>@string/waypoint_type_toilet</item>
<item>@string/waypoint_type_tourist_information</item>
<item>@string/waypoint_type_tree</item>
<item>@string/waypoint_type_tunnel</item>
<item>@string/waypoint_type_waterfall</item>
<item>@string/waypoint_type_water_fountain</item>
<item>@string/waypoint_type_waypoint</item>
</string-array>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- This file contains old and now unused settings. -->
<!-- It is recommended not to reuse the keys. -->
<string name="chart_x_axis_key" translatable="false">chartXAxis</string>
<string name="chart_x_axis_default" translatable="false">@string/chart_x_axis_distance</string>
<string-array name="chart_x_axis_values">
<item>@string/chart_x_axis_distance</item>
<item>@string/chart_x_axis_time</item>
</string-array>
<string name="chart_x_axis_time" translatable="false">TIME</string>
<string name="chart_x_axis_distance" translatable="false">DISTANCE</string>
<string-array name="chart_x_axis_options">
<item>@string/settings_chart_by_distance</item>
<item>@string/settings_chart_by_time</item>
</string-array>
</resources>
+104 -2
View File
@@ -38,6 +38,95 @@ limitations under the License.
Libraries
</string>
<!-- Help -->
<string name="help_feature_title">
OpenTracks has the following features:
</string>
<string name="help_feature_content">
* Create markers (incl. pictures)\n
* Heart rate sensor (Bluetooth LE)\n
* Voice announcements by time or distance\n
* While recording, show on lock screen\n
* Share tracks
</string>
<string name="help_data_statistics_customized_title">
Can the statistics view be customized?
</string>
<string name="help_data_statistics_customized_content">
Yes, it can be customized in the Settings.
</string>
<string name="help_recording_title">
How does OpenTracks record data?
</string>
<string name="help_recording_content">
OpenTracks uses the GPS sensor of the device to record the location continuously.
</string>
<string name="help_gps_imprecise_title">
Statistics (e.g., speed) reported by OpenTracks are imprecise?
</string>
<string name="help_gps_imprecise_content">
OpenTracks directly uses the data provided by the GPS of the mobile device.
If the GPS device reports inaccurate locations, OpenTracks cannot do anything about it.
</string>
<string name="help_heart_rate_sensors_title">
Which heart rate sensors does OpenTracks support?
</string>
<string name="help_heart_rate_sensors_content">
OpenTracks should work with any Bluetooth LE heart sensor that support streaming the data in real-time.
Smartwatches that store the heart rate data are not supported.
</string>
<string name="help_track_map_title">
How to show a track on a map?
</string>
<string name="help_track_map_content">
This requires a 3rd party application to be installed that can consume KMZ-encoded geo data.
This works well with <a href="http://osmand.net/">OsmAnd~</a> and <a href="https://maps.me/">Maps.ME</a>.
In fact, an App needs to support <i>ACTION_VIEW</i> with MIME <i>application/vnd.google-earth.kmz</i>.
At the moment, the recorded locations as well as the timestamps are send to an App while pictures and sensor data are not shared.
</string>
<string name="help_storage_pictures_title">
How does OpenTracks store pictures that were taken as markers?
</string>
<string name="help_storage_pictures_content">
These are stored directly on the file system.
</string>
<string name="help_track_export_title">
How can I export/share recorded tracks?
</string>
<string name="help_track_export_content">
Tracks can be exported from the Settings as KMZ, KML, or GPX.
KMZ contains all data incl. images, while KML contains only the sensor data, and GPX only the location.
Tracks shared via the <i>share icon</i> are shared as KMZ and any App can be used to send the data.
</string>
<string name="help_data_upload_title">
Can OpenTracks upload the data automatically?
</string>
<string name="help_data_google_play_services_title">
Does OpenTracks require/use Google Play Service?
</string>
<string name="help_data_online_title">
Does OpenTracks use online services?
</string>
<string name="help_data_storage_title">
How does OpenTracks store sensor data?
</string>
<string name="help_data_storage_content">
OpenTracks stores sensor data per recorded location.
In terms of a heart rate sensor this means that only <i>one</i> heart rate measurement is stored per location.
If you are standing still or are indoors, no sensor data will be recorded (but it will be shown).
</string>
<!-- Activity Type -->
<string name="activity_type_airplane" translation_description="When entering an 'Activity type', in the auto-complete text box, the
completion suggestion for airplane.">
@@ -1055,7 +1144,13 @@ limitations under the License.
Sending
</string>
<string name="sensor_not_known" translation_description="Error message if sensor is not available in Android (e.g., pairing was removed in Bluetooth Settings).">
Sensor %1$s is not known. Please check the settings.
Sensor %1$s is unknown. Please check the settings.
</string>
<string name="sensor_could_not_scan">
Could not scan for Bluetooth devices (error %1$i).
</string>
<string name="bluetooth_disabled">
Please enable Bluetooth.
</string>
<!-- Settings -->
<string name="settings_not_while_recording" translation_description="In settings, the summary for an option that is not available during a
@@ -1219,7 +1314,11 @@ limitations under the License.
All settings have been reverted to the default values
</string>
<!-- Settings Sensor -->
<string name="settings_error_initial_values_restored" translation_description="If the ">
Sorry, but an error occurred. All settings have been reverted to the default values.
</string>
<!-- Settings Sensor -->
<string name="settings_sensor" translation_description="In the 'Settings' page, the option to change the sensor settings.
Character limit: ~26 (1 line)">
@@ -1415,6 +1514,9 @@ limitations under the License.
<string name="track_record_notification" translation_description="When recording a track, the message on the OpenTracks notification
informing the user that OpenTracks is actively recording his track.">
Recording your track&#8230;
</string>
<string name="track_recording_notification_accuracy">
Location accuracy: %1$s
</string>
<string name="track_markers" translation_description="Title indicating a particular track's markers.
+49 -56
View File
@@ -14,44 +14,46 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/menu_settings">
<PreferenceCategory android:title="@string/menu_help">
<Preference android:title="@string/menu_help">
<intent
android:targetClass="de.dennisguse.opentracks.HelpActivity"
android:targetPackage="@string/applicationId" />
</Preference>
</PreferenceCategory>
<PreferenceCategory android:title="@string/track_detail_stats_tab">
<ListPreference
android:defaultValue="@string/stats_units_metric"
android:defaultValue="@string/stats_units_default"
android:entries="@array/stats_units_options"
android:entryValues="@array/stats_units_values"
android:key="@string/stats_units_key"
android:summary="%s"
android:title="@string/settings_stats_units_title" />
android:title="@string/settings_stats_units_title"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="@string/stats_rate_speed"
android:defaultValue="@string/stats_rate_default"
android:entries="@array/stats_rate_metric_options"
android:entryValues="@array/stats_rate_values"
android:key="@string/stats_rate_key"
android:summary="%s"
android:title="@string/settings_stats_rate_title" />
android:title="@string/settings_stats_rate_title"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_show">
<CheckBoxPreference
android:defaultValue="false"
android:defaultValue="@bool/stats_show_grade_elevation_default"
android:key="@string/stats_show_grade_elevation_key"
android:title="@string/settings_stats_grade_elevation" />
<CheckBoxPreference
android:defaultValue="false"
android:defaultValue="@bool/stats_show_coordinate_default"
android:key="@string/stats_show_coordinate_key"
android:title="@string/settings_stats_coordinate" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/track_detail_chart_tab">
<ListPreference
android:defaultValue="@string/chart_x_axis_distance"
android:entries="@array/chart_x_axis_options"
android:entryValues="@array/chart_x_axis_values"
android:key="@string/chart_x_axis_key"
android:summary="%s"
android:title="@string/settings_chart_x_axis" />
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/chart_show_elevation_key"
@@ -68,80 +70,71 @@ limitations under the License.
<PreferenceCategory android:title="@string/settings_recording">
<CheckBoxPreference
android:defaultValue="false"
android:key="@string/trackdetail_show_on_lockscreen_while_recording_key"
android:defaultValue="@bool/stats_show_on_lockscreen_while_recording_default"
android:key="@string/stats_show_on_lockscreen_while_recording_key"
android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary"
android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" />
<ListPreference
android:defaultValue="0"
android:defaultValue="@string/voice_frequency_default"
android:entryValues="@array/frequency_values"
android:key="@string/voice_frequency_key"
android:summary="%s"
android:title="@string/menu_voice_frequency" />
android:title="@string/menu_voice_frequency"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="0"
android:defaultValue="@string/split_frequency_default"
android:entryValues="@array/frequency_values"
android:key="@string/split_frequency_key"
android:summary="%s"
android:title="@string/menu_split_frequency" />
android:title="@string/menu_split_frequency"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="@string/settings_recording_track_name_date_iso_8601_value"
android:defaultValue="@string/track_name_default"
android:entries="@array/track_name_options"
android:entryValues="@array/track_name_values"
android:key="@string/track_name_key"
android:summary="%s"
android:title="@string/settings_recording_track_name_title" />
android:title="@string/settings_recording_track_name_title"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="0"
android:defaultValue="@string/min_recording_interval_default"
android:entryValues="@array/min_recording_interval_values"
android:key="@string/min_recording_interval_key"
android:summary="%s"
android:title="@string/settings_recording_min_recording_interval_title" />
android:title="@string/settings_recording_min_recording_interval_title"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="10"
android:defaultValue="@string/recording_distance_interval_default"
android:entryValues="@array/recording_distance_interval_values"
android:key="@string/recording_distance_interval_key"
android:summary="%s"
android:title="@string/settings_recording_min_recording_distance_title" />
android:title="@string/settings_recording_min_recording_distance_title"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="200"
android:defaultValue="@string/max_recording_distance_default"
android:entryValues="@array/max_recording_distance_values"
android:key="@string/max_recording_distance_key"
android:summary="%s"
android:title="@string/settings_recording_max_recording_distance_title" />
android:title="@string/settings_recording_max_recording_distance_title"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="50"
android:defaultValue="@string/recording_gps_accuracy_default"
android:entryValues="@array/recording_gps_accuracy_values"
android:key="@string/recording_gps_accuracy_key"
android:summary="%s"
android:title="@string/settings_recording_min_required_accuracy_title" />
android:title="@string/settings_recording_min_required_accuracy_title"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="10"
android:defaultValue="@string/auto_resume_track_timeout_default"
android:entries="@array/auto_resume_track_timeout_values"
android:entryValues="@array/auto_resume_track_timeout_values"
android:key="@string/auto_resume_track_timeout_key"
android:summary="%s"
android:title="@string/settings_recording_auto_resume_track_timeout_title" />
android:title="@string/settings_recording_auto_resume_track_timeout_title"
app:useSimpleSummaryProvider="true" />
<de.dennisguse.opentracks.settings.ActivityTypePreference
android:defaultValue="@string/activity_type_running"
android:defaultValue="@string/default_activity_default"
android:key="@string/default_activity_key"
android:title="@string/settings_recording_default_activity_title" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/settings_sensor">
<ListPreference
<PreferenceCategory android:title="@string/settings_sensor_bluetooth">
<de.dennisguse.opentracks.settings.BluetoothLePreference
android:defaultValue="@string/sensor_type_value_none"
android:key="@string/bluetooth_sensor_key"
android:summary="%s"
android:title="@string/settings_sensor_bluetooth_sensor" />
<Preference
android:key="@string/settings_sensor_bluetooth_pairing_key"
android:persistent="false"
android:summary="@string/settings_sensor_bluetooth_pairing_summary"
android:title="@string/settings_sensor_bluetooth_pairing">
<intent android:action="android.settings.BLUETOOTH_SETTINGS" />
</Preference>
android:key="@string/settings_sensor_bluetooth_heart_rate_key"
android:title="@string/sensor_state_heart_rate" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/generic_tracks">
@@ -159,8 +152,8 @@ limitations under the License.
<PreferenceCategory android:title="@string/settings_reset">
<de.dennisguse.opentracks.settings.ResetDialogPreference
android:dialogTitle="@string/settings_reset_confirm_title"
android:dialogMessage="@string/settings_reset_confirm_message"
android:dialogTitle="@string/settings_reset_confirm_title"
android:key="@string/settings_reset_key"
android:negativeButtonText="@string/generic_cancel"
android:persistent="false"