Compare commits

...

36 Commits

Author SHA1 Message Date
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
Dennis Guse 3aaee270eb Release v3.2.4 2019-11-01 23:54:34 +01:00
Dennis Guse d53a7156e2 Bugfix: removed custom dialogs to set voice/split frequency preferences from the context menu directly.
Fixed #49.
2019-11-01 23:38:07 +01:00
57 changed files with 1294 additions and 962 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.
+7 -3
View File
@@ -7,6 +7,10 @@
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid) 3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
4. Create changelog (`_version code_.txt`) 4. Create changelog (`_version code_.txt`)
5. Create commit with all changes 5. Create commit with all changes
6. Push commits 6. Tag newly create commit with _version name_ (e.g., v3.2.1)
7. Tag newly create commit with _version name_ (e.g., v3.2.1) 7. Push commits and tags to public repository
8. Push the tagged commit 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"> <img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
</a> </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 ## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)). _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() jcenter()
} }
dependencies { 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 { try {
def stdout = new ByteArrayOutputStream() def stdout = new ByteArrayOutputStream()
exec { exec {
commandLine 'git', 'describe', '--tags', '--dirty' commandLine 'git', 'describe', '--tags'
standardOutput = stdout 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) { catch (ignored) {
return null return null
@@ -75,10 +79,6 @@ android {
applicationVariants.all { variant -> applicationVariants.all { variant ->
variant.resValue "string", "applicationId", variant.applicationId variant.resValue "string", "applicationId", variant.applicationId
} }
lintOptions {
disable "AppCompatResource" // TODO Disable complaint to use androidx's SearchView as it is null.
}
} }
dependencies { dependencies {
@@ -0,0 +1,5 @@
v3.2.4: OpenTracks
Bugfix:
- Settings activity crashes
Removed possibility to change voice/split frequency from the context menu
@@ -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
@@ -27,7 +27,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -95,7 +94,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end // A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1); 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.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); ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0); expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -124,7 +123,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end // A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5); 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.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); ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0); expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -156,7 +155,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end // A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5); 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.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); ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0); 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.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0); when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getLastTrackPointId(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); contentProviderUtils.deleteTrack(context, TRACK_ID_0);
InputStream inputStream = new ByteArrayInputStream(xml.getBytes()); InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
@@ -27,7 +27,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -79,7 +78,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end // A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1); 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.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); ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0); expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -110,7 +109,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end // A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5); 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.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); ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0); expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
@@ -79,7 +79,7 @@ public class TrackRecordingServiceTest {
static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) { static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts); PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins); PreferencesUtils.setString(context, R.string.auto_resume_track_timeout_key, "" + timeoutMins);
} }
@Before @Before
@@ -135,7 +135,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis(), true); createDummyTrack(trackId, System.currentTimeMillis(), true);
// Clear the number of attempts and set the timeout to 10 min. // 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). // Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context); Intent startIntent = createStartIntent(context);
@@ -154,7 +154,7 @@ public class TrackRecordingServiceTest {
@MediumTest @MediumTest
@Test @Test
public void testResumeAfterReboot_simulateReboot() throws Exception { 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))); ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
@@ -163,7 +163,7 @@ public class TrackRecordingServiceTest {
Assert.assertTrue(service.isRecording()); Assert.assertTrue(service.isRecording());
Assert.assertEquals(id, service.getRecordingTrackId()); Assert.assertEquals(id, service.getRecordingTrackId());
mServiceRule.unbindService(); 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). // Start the service in "resume" mode (simulates the on-reboot action).
@@ -184,7 +184,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis(), false); createDummyTrack(trackId, System.currentTimeMillis(), false);
// Clear the number of attempts and set the timeout to 10 min. // 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). // Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context); Intent startIntent = createStartIntent(context);
@@ -207,7 +207,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true); createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
// Clear the number of attempts and set the timeout to 10 min. // 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). // Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context); Intent startIntent = createStartIntent(context);
@@ -230,7 +230,7 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId, System.currentTimeMillis(), true); createDummyTrack(trackId, System.currentTimeMillis(), true);
// Set the number of attempts to max. // 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). // Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context); Intent startIntent = createStartIntent(context);
@@ -297,7 +297,7 @@ public class TrackRecordingServiceTest {
long newTrackId = service.startNewTrack(); long newTrackId = service.startNewTrack();
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId); 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()); 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. // Ending the current track when there is no recording should not result in any error.
service.endCurrentTrack(); 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()); Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
} }
@@ -103,7 +103,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -125,7 +125,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -139,7 +139,8 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -153,7 +154,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_splitFrequencyDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -174,7 +175,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -188,7 +189,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -202,7 +203,8 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -225,7 +227,7 @@ public class TrackRecordingServiceTestLooper {
Track track = providerUtils.getTrack(id); Track track = providerUtils.getTrack(id);
Assert.assertNotNull(track); Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId()); 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()); Assert.assertEquals(id, service.getRecordingTrackId());
// Insert a few points, markers and statistics. // Insert a few points, markers and statistics.
+3 -2
View File
@@ -16,8 +16,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks" package="de.dennisguse.opentracks"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="3101" android:versionCode="3137"
android:versionName="v3.2.3"> <!-- For FDroid. --> android:versionName="v3.2.6"> <!-- For FDroid. -->
<!-- Permission to initialize services --> <!-- Permission to initialize services -->
<application <application
android:name=".Startup" android:name=".Startup"
@@ -124,6 +124,7 @@ limitations under the License.
android:name=".services.TrackRecordingService" android:name=".services.TrackRecordingService"
android:enabled="true" android:enabled="true"
android:exported="false" android:exported="false"
android:foregroundServiceType="location|connectedDevice"
android:icon="@drawable/ic_logo_color_24dp" android:icon="@drawable/ic_logo_color_24dp"
android:label="@string/recording_service" /> android:label="@string/recording_service" />
</application> </application>
@@ -65,7 +65,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
if (trackIds.length == 1 && trackIds[0] == -1L) { if (trackIds.length == 1 && trackIds[0] == -1L) {
stopRecording = true; stopRecording = true;
} else { } else {
long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key); long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (long trackId : trackIds) { for (long trackId : trackIds) {
if (trackId == recordingTrackId) { if (trackId == recordingTrackId) {
stopRecording = true; stopRecording = true;
@@ -69,7 +69,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; 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 * Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected. * class. Anonymous inner class will get garbage collected.
@@ -79,11 +79,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
// Note that the key can be null // Note that the key can be null
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) { if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key); recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
} }
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) { if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
} }
if (key != null) { if (key != null) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -128,6 +128,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(this); contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(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 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 // Callback when an item is selected in the contextual action mode
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@@ -143,14 +143,14 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { 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); metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this);
} }
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) { if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key); recordingTrackId = PreferencesUtils.getRecordingTrackId(SearchListActivity.this);
} }
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) { if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
} }
if (key != null) { if (key != null) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -168,6 +168,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(this); contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
@@ -24,6 +24,7 @@ import android.widget.TabHost;
import android.widget.TabHost.TabContentFactory; import android.widget.TabHost.TabContentFactory;
import android.widget.TabWidget; import android.widget.TabWidget;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
@@ -79,6 +80,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
} }
@Override @Override
@NonNull
public Fragment getItem(int position) { public Fragment getItem(int position) {
TabInfo info = tabInfos.get(position); TabInfo info = tabInfos.get(position);
return Fragment.instantiate(context, info.clss.getName(), info.bundle); return Fragment.instantiate(context, info.clss.getName(), info.bundle);
@@ -48,13 +48,13 @@ import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.fragments.ChartFragment; import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.FrequencyDialogFragment;
import de.dennisguse.opentracks.fragments.StatsFragment; import de.dennisguse.opentracks.fragments.StatsFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity; import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils; import de.dennisguse.opentracks.util.TrackUtils;
@@ -94,8 +94,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
// Preferences // Preferences
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; private boolean recordingTrackPaused;
private boolean showOnLockScreen = PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN;
private final Runnable bindChangedCallback = new Runnable() { private final Runnable bindChangedCallback = new Runnable() {
@Override @Override
@@ -122,20 +121,18 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
* Note that sharedPreferenceChangeListener cannot be an anonymous inner * Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected. * class. Anonymous inner class will get garbage collected.
*/ */
private final OnSharedPreferenceChangeListener private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) { if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key); recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
} }
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) { if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
} }
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key))) { if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
showOnLockScreen = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key, PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN);
setLockscreenPolicy(); setLockscreenPolicy();
} }
@@ -156,8 +153,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
private MenuItem insertPhotoMenuItem; private MenuItem insertPhotoMenuItem;
private MenuItem markerListMenuItem; private MenuItem markerListMenuItem;
private MenuItem shareMenuItem; private MenuItem shareMenuItem;
private MenuItem voiceFrequencyMenuItem;
private MenuItem splitFrequencyMenuItem;
private final OnClickListener recordListener = new OnClickListener() { private final OnClickListener recordListener = new OnClickListener() {
@Override @Override
@@ -187,6 +182,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null; photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false); hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
@@ -230,12 +227,14 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
} }
private void setLockscreenPolicy() { private void setLockscreenPolicy() {
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen); setShowWhenLocked(showOnLockScreen);
} else if (showOnLockScreen) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
} else { } else {
if (showOnLockScreen) getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
} }
} }
@@ -275,7 +274,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag()); outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
if (photoUri != null) { if (photoUri != null) {
@@ -316,8 +315,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo); insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null); insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
shareMenuItem = menu.findItem(R.id.track_detail_share); shareMenuItem = menu.findItem(R.id.track_detail_share);
voiceFrequencyMenuItem = menu.findItem(R.id.track_detail_voice_frequency);
splitFrequencyMenuItem = menu.findItem(R.id.track_detail_split_frequency);
markerListMenuItem = menu.findItem(R.id.track_detail_markers); markerListMenuItem = menu.findItem(R.id.track_detail_markers);
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
@@ -359,12 +356,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId); .putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.track_detail_voice_frequency:
FrequencyDialogFragment.showDialog(getSupportFragmentManager(), R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.string.menu_voice_frequency);
return true;
case R.id.track_detail_split_frequency:
FrequencyDialogFragment.showDialog(getSupportFragmentManager(), R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.string.menu_split_frequency);
return true;
case R.id.track_detail_edit: case R.id.track_detail_edit:
intent = IntentUtils.newIntent(this, TrackEditActivity.class) intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId); .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
@@ -421,10 +412,16 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
/** /**
* Gets the track id. * Gets the track id.
*/ */
// TODO Seems to be no used.
@Deprecated
public long getTrackId() { public long getTrackId() {
return trackId; return trackId;
} }
private void setBluetoothHeartRateSensorName(String sensorName) {
StatsUtils.setBluetoothHeartRateSensorName(this, sensorName, isRecording());
}
/** /**
* Handles the data in the intent. * Handles the data in the intent.
*/ */
@@ -466,8 +463,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused); insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
shareMenuItem.setVisible(!isRecording()); shareMenuItem.setVisible(!isRecording());
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM); markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
voiceFrequencyMenuItem.setVisible(isRecording());
splitFrequencyMenuItem.setVisible(isRecording());
String title; String title;
if (isRecording()) { if (isRecording()) {
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording); title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
@@ -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. * Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
@@ -135,22 +135,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { 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); metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
} }
if (key == null || key.equals( if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) { recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
recordingTrackId = PreferencesUtils.getLong(
TrackListActivity.this, R.string.recording_track_id_key);
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) { if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
trackRecordingServiceConnection.startAndBind(); trackRecordingServiceConnection.startAndBind();
} }
} }
if (key == null || key.equals(PreferencesUtils.getKey( if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
TrackListActivity.this, R.string.recording_track_paused_key))) { recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this,
R.string.recording_track_paused_key,
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
} }
if (key != null) { if (key != null) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -226,12 +221,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private final OnClickListener recordListener = new OnClickListener() { private final OnClickListener recordListener = new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { if (!PreferencesUtils.isRecording(recordingTrackId)) {
// Not recording -> Recording // Not recording -> Recording
updateMenuItems(false, true); updateMenuItems(false, true);
startRecording(); startRecording();
} else { } else if (recordingTrackPaused) {
if (recordingTrackPaused) {
// Paused -> Resume // Paused -> Resume
updateMenuItems(false, true); updateMenuItems(false, true);
trackRecordingServiceConnection.resumeTrack(); trackRecordingServiceConnection.resumeTrack();
@@ -242,7 +236,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
trackRecordingServiceConnection.pauseTrack(); trackRecordingServiceConnection.pauseTrack();
trackController.update(true, true); trackController.update(true, true);
} }
}
} }
}; };
@@ -250,6 +243,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(this); contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
@@ -28,11 +28,6 @@ public class SensorDataSetLocation extends Location {
private SensorDataSet sensorDataSet = null; private SensorDataSet sensorDataSet = null;
/**
* The id of this location from the provider.
*/
private int id = -1;
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) { public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
super(location); super(location);
this.sensorDataSet = sensorDataSet; this.sensorDataSet = sensorDataSet;
@@ -50,17 +45,8 @@ public class SensorDataSetLocation extends Location {
this.sensorDataSet = sensorDataSet; this.sensorDataSet = sensorDataSet;
} }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void reset() { public void reset() {
super.reset(); super.reset();
sensorDataSet = null; sensorDataSet = null;
id = -1;
} }
} }
@@ -254,13 +254,13 @@ public class TrackDataHub implements DataSourceListener {
runInHandlerThread(new Runnable() { runInHandlerThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) { if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) { if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); 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); metricUnits = PreferencesUtils.isMetricUnits(context);
if (key != null) { if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { 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); reportSpeed = PreferencesUtils.isReportSpeed(context);
if (key != null) { if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { 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))) { if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
if (key != null) { if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) { 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))) { if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
if (key != null) { if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) { if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
@@ -4,7 +4,7 @@ public final class SensorDataSet {
public static final float DATA_UNAVAILABLE = Float.NaN; 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 sensorName;
public String sensorAddress; public String sensorAddress;
private float heartRate; private float heartRate;
@@ -68,7 +68,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private boolean metricUnits = true; private boolean metricUnits = true;
private boolean reportSpeed = true; private boolean reportSpeed = true;
private int recordingDistanceInterval = PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT; private int recordingDistanceInterval;
// Modes of operation // Modes of operation
private boolean chartByDistance = true; private boolean chartByDistance = true;
@@ -99,6 +99,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
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. * Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
*/ */
@@ -263,8 +265,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
} }
reportSpeed = speed; reportSpeed = speed;
chartView.setReportSpeed(reportSpeed); chartView.setReportSpeed(reportSpeed);
boolean chartShowSpeed = PreferencesUtils.getBoolean( boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed); setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed); setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -309,24 +310,24 @@ public class ChartFragment extends Fragment implements TrackDataListener {
reloadTrackDataHub(); reloadTrackDataHub();
needUpdate = true; 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; 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)) { if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
needUpdate = true; needUpdate = true;
} }
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) { if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
needUpdate = true; 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; 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; 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; needUpdate = true;
} }
if (needUpdate) { if (needUpdate) {
@@ -1,6 +1,5 @@
package de.dennisguse.opentracks.fragments; package de.dennisguse.opentracks.fragments;
import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@@ -79,19 +78,6 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment implements
return view; 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) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
try { try {
@@ -1,105 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
* A DialogFragment to configure frequency.
*
* @author Jimmy Shih
*/
public class FrequencyDialogFragment extends DialogFragment {
private static final String FREQUENCY_DIALOG_TAG = "frequencyDialog";
private int preferenceId;
private int defaultValue;
private int titleId;
public FrequencyDialogFragment(int preferenceId, int defaultValue, int titleId) {
this.preferenceId = preferenceId;
this.defaultValue = defaultValue;
this.titleId = titleId;
}
public static void showDialog(FragmentManager fragmentManager, int preferenceId, int defaultValue, int titleId) {
new FrequencyDialogFragment(preferenceId, defaultValue, titleId).show(fragmentManager, FREQUENCY_DIALOG_TAG);
}
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
FragmentActivity fragmentActivity = getActivity();
int frequencyValue = PreferencesUtils.getInt(fragmentActivity, preferenceId, defaultValue);
return new AlertDialog.Builder(fragmentActivity).setPositiveButton(
R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int listIndex = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
PreferencesUtils.setInt(getActivity(), preferenceId, getFrequencyValue(listIndex));
}
}).setSingleChoiceItems(
getFrequencyDisplayOptions(fragmentActivity), getListIndex(frequencyValue), null)
.setTitle(titleId).create();
}
/**
* Gets the frequency display options.
*/
private String[] getFrequencyDisplayOptions(FragmentActivity fragmentActivity) {
boolean metricUnits = PreferencesUtils.isMetricUnits(fragmentActivity);
return StringUtils.getFrequencyOptions(fragmentActivity, metricUnits);
}
/**
* Gets the list index for a frequency value.
* Returns 0 if the value is not on the list.
*/
private int getListIndex(int frequencyValue) {
String[] values = getResources().getStringArray(R.array.frequency_values);
for (int i = 0; i < values.length; i++) {
if (frequencyValue == Integer.parseInt(values[i])) {
return i;
}
}
return 0;
}
/**
* Gets the frequency value from a list index.
*
* @param listIndex the list index
*/
private int getFrequencyValue(int listIndex) {
String[] values = getResources().getStringArray(R.array.frequency_values);
return Integer.parseInt(values[listIndex]);
}
}
@@ -45,7 +45,6 @@ import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StatsUtils; import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
@@ -67,7 +66,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private Location lastLocation = null; private Location lastLocation = null;
private TripStatistics lastTripStatistics = null; private TripStatistics lastTripStatistics = null;
private SensorDataSet sensorDataSet = null;
private final Runnable updateUIeachSecond = new Runnable() { private final Runnable updateUIeachSecond = new Runnable() {
public void run() { public void run() {
@@ -82,7 +80,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
} }
}; };
private String category = ""; private String category = "";
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT; private int recordingGpsAccuracy;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -92,6 +90,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
recordingGpsAccuracy = Integer.parseInt(getContext().getResources().getString(R.string.recording_gps_accuracy_default));
handlerUpdateUI = new Handler(); handlerUpdateUI = new Handler();
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon); Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
@@ -304,9 +305,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
*/ */
private void updateSensorDataUI() { private void updateSensorDataUI() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
SensorDataSet sensorDataSet = null;
if (trackRecordingService == null) { if (trackRecordingService == null) {
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service."); Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
sensorDataSet = null;
} else { } else {
//TODO sensorState = trackRecordingService.getSensorState(); //TODO sensorState = trackRecordingService.getSensorState();
sensorDataSet = trackRecordingService.getSensorData(); sensorDataSet = trackRecordingService.getSensorData();
@@ -27,7 +27,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns; import de.dennisguse.opentracks.content.TracksColumns;
@@ -100,7 +99,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
try { try {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
boolean isRecording = PreferencesUtils.isRecording(exportActivity); 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 // Get the wake lock if not recording or paused
if (!isRecording || isPaused) { if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock); wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
@@ -102,7 +102,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
this.context = context; this.context = context;
this.importTrackId = importTrackId; this.importTrackId = importTrackId;
this.contentProviderUtils = contentProviderUtils; 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<>(); trackIds = new ArrayList<>();
waypoints = new ArrayList<>(); waypoints = new ArrayList<>();
} }
@@ -28,7 +28,6 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat; 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); Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
// Get the wake lock if not recording or paused // Get the wake lock if not recording or paused
boolean isRecording = PreferencesUtils.isRecording(importActivity); 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) { if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock); wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
} }
@@ -101,7 +101,7 @@ public class KmzTrackImporter implements TrackImporter {
* @param trackId the trackId * @param trackId the trackId
*/ */
private void cleanImport(long trackId) { private void cleanImport(long trackId) {
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { if (PreferencesUtils.isRecording(trackId)) {
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
contentProviderUtils.deleteTrack(context, trackId); contentProviderUtils.deleteTrack(context, trackId);
} }
@@ -58,7 +58,6 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest; import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; 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.AnnouncementPeriodicTaskFactory;
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor; import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory; import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory;
@@ -118,52 +117,49 @@ public class TrackRecordingService extends Service {
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) { if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L. // Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
if (PreferencesUtils.isRecording(context)) { if (PreferencesUtils.isRecording(context)) {
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
} }
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) { if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); 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); boolean metricUnits = PreferencesUtils.isMetricUnits(context);
voiceExecutor.setMetricUnits(metricUnits); voiceExecutor.setMetricUnits(metricUnits);
splitExecutor.setMetricUnits(metricUnits); splitExecutor.setMetricUnits(metricUnits);
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.voice_frequency_key))) { if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
voiceExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT)); voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.split_frequency_key))) { if (PreferencesUtils.isKey(context, R.string.split_frequency_key, key)) {
splitExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT)); splitExecutor.setTaskFrequency(PreferencesUtils.getSplitFrequency(context));
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.min_recording_interval_key))) { if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
int minRecordingInterval = PreferencesUtils.getInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT); int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
switch (minRecordingInterval) { if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE: // Choose battery life over moving time accuracy.
// Choose battery life over moving time accuracy. locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5);
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5); } else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
break; // Get all the updates.
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY: locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0);
// Get all the updates. } else {
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0); locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
break;
default:
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
} }
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) { if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.max_recording_distance_key))) { if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
maxRecordingDistance = PreferencesUtils.getInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT); maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) { if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
} }
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.auto_resume_track_timeout_key))) { if (PreferencesUtils.isKey(context, R.string.auto_resume_track_timeout_key, key)) {
autoResumeTrackTimeout = PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); autoResumeTrackTimeout = PreferencesUtils.getAutoResumeTrackTimeout(context);
} }
} }
}; };
@@ -266,7 +262,7 @@ public class TrackRecordingService extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
if (remoteSensorManager != null) { if (remoteSensorManager != null) {
RemoteSensorManagerFactory.releaseSystemSensorManager(); remoteSensorManager.stop();
remoteSensorManager = null; remoteSensorManager = null;
} }
@@ -465,17 +461,19 @@ public class TrackRecordingService extends Service {
return false; 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) { if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
Log.d(TAG, "Not resuming. Exceeded maximum retry attempts."); Log.d(TAG, "Not resuming. Exceeded maximum retry attempts.");
return false; 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(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never."); Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
return false; return false;
} else if (autoResumeTrackTimeout == PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS) { }
if (autoResumeTrackTimeout == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
Log.d(TAG, "Resuming. Auto-resume track timeout set to always."); Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
return true; return true;
} }
@@ -509,13 +507,13 @@ public class TrackRecordingService extends Service {
// Update shared preferences // Update shared preferences
updateRecordingState(trackId, false); updateRecordingState(trackId, false);
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, 0); PreferencesUtils.resetAutoResumeTrackCurrentRetryDefault(this);
// Update database // Update database
track.setId(trackId); track.setId(trackId);
track.setName(TrackNameUtils.getTrackName(this, trackId, now)); 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.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(this, category)); track.setIcon(TrackIconUtils.getIconValue(this, category));
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics()); track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
@@ -595,7 +593,8 @@ public class TrackRecordingService extends Service {
*/ */
private void startRecording(boolean trackStarted) { private void startRecording(boolean trackStarted) {
// Update instance variables // Update instance variables
remoteSensorManager = RemoteSensorManagerFactory.getSystemSensorManager(this); remoteSensorManager = new BluetoothRemoteSensorManager(this);
remoteSensorManager.start();
lastLocation = null; lastLocation = null;
currentSegmentHasLocation = false; currentSegmentHasLocation = false;
isIdle = false; isIdle = false;
@@ -693,7 +692,7 @@ public class TrackRecordingService extends Service {
// Update instance variables // Update instance variables
if (remoteSensorManager != null) { if (remoteSensorManager != null) {
RemoteSensorManagerFactory.releaseSystemSensorManager(); remoteSensorManager.stop();
remoteSensorManager = null; remoteSensorManager = null;
} }
lastLocation = null; lastLocation = null;
@@ -203,9 +203,9 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
if (PreferencesUtils.isRecording(context)) { if (PreferencesUtils.isRecording(context)) {
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); 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) { 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 { try {
if (showEditor) { if (showEditor) {
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L. // 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(); trackRecordingService.endCurrentTrack();
if (PreferencesUtils.isRecording(context)) { if (PreferencesUtils.isRecording(context)) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class) Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
@@ -36,7 +37,6 @@ import de.dennisguse.opentracks.util.BluetoothUtils;
/** /**
* Manages connection to Bluetooth LE heart rate monitor. * Manages connection to Bluetooth LE heart rate monitor.
* TODO: shutdown connection.
*/ */
public class BluetoothConnectionManager { public class BluetoothConnectionManager {
@@ -46,7 +46,6 @@ public class BluetoothConnectionManager {
static final int MESSAGE_READ = 3; static final int MESSAGE_READ = 3;
static final int MESSAGE_DISCONNECTED = 4; 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 HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL); private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
@@ -91,10 +90,18 @@ public class BluetoothConnectionManager {
@Override @Override
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) { public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
BluetoothGattCharacteristic characteristic = gatt BluetoothGattService service = gatt.getService(BluetoothUtils.HEART_RATE_SERVICE_UUID);
.getService(HEART_RATE_SERVICE_UUID) if (service == null) {
.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID); 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); gatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID); BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
@@ -132,8 +139,9 @@ public class BluetoothConnectionManager {
Log.d(TAG, "Connecting to: " + bluetoothDevice); Log.d(TAG, "Connecting to: " + bluetoothDevice);
setState(SensorState.CONNECTING);
bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback); bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
setState(SensorState.CONNECTING);
} }
public synchronized void disconnect() { public synchronized void disconnect() {
@@ -26,15 +26,14 @@ import android.os.Message;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import java.util.ArrayList;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; 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 * @author Sandor Dornbush
*/ */
@@ -44,11 +43,12 @@ public class BluetoothRemoteSensorManager {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName(); 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 Context context;
private final SharedPreferences sharedPreferences; private final SharedPreferences sharedPreferences;
// Handler that gets information back from the bluetoothConnectionManager // Handler that gets information back from the bluetoothConnectionManager
private final Handler messageHandler = new Handler(Looper.getMainLooper()) { private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override @Override
@@ -85,8 +85,8 @@ public class BluetoothRemoteSensorManager {
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (bluetoothConnectionManager != null && key == null || key.equals(PreferencesUtils.getKey(context, R.string.bluetooth_sensor_key))) { if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) { if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
stop(); stop();
return; return;
@@ -107,48 +107,11 @@ public class BluetoothRemoteSensorManager {
/** /**
* @param context the context * @param context the context
*/ */
BluetoothRemoteSensorManager(Context context) { public BluetoothRemoteSensorManager(Context context) {
this.context = context; this.context = context;
sharedPreferences = PreferencesUtils.getSharedPreferences(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() { public void start() {
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
startCurrentSensor(); startCurrentSensor();
@@ -182,7 +145,8 @@ public class BluetoothRemoteSensorManager {
return; 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)) { if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
Log.w(TAG, "No bluetooth address."); Log.w(TAG, "No bluetooth address.");
return; 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);
}
}
@@ -17,9 +17,9 @@ package de.dennisguse.opentracks.services.tasks;
import android.util.Log; import android.util.Log;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
/** /**
@@ -35,13 +35,14 @@ public class PeriodicTaskExecutor {
private final TrackRecordingService trackRecordingService; private final TrackRecordingService trackRecordingService;
private final PeriodicTaskFactory periodicTaskFactory; private final PeriodicTaskFactory periodicTaskFactory;
private final int TASK_FREQUENCY_OFF;
/** /**
* The task frequency. * The task frequency.
* A positive value is a time frequency (minutes). * A positive value is a time frequency (minutes).
* A negative value is a distance frequency (km or mi). * A negative value is a distance frequency (km or mi).
* A zero value is to turn off periodic task. * A zero value is to turn off periodic task.
*/ */
private int taskFrequency = PreferencesUtils.FREQUENCY_OFF; private int taskFrequency;
private PeriodicTask periodicTask; private PeriodicTask periodicTask;
@@ -56,6 +57,9 @@ public class PeriodicTaskExecutor {
public PeriodicTaskExecutor(TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) { public PeriodicTaskExecutor(TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
this.trackRecordingService = trackRecordingService; this.trackRecordingService = trackRecordingService;
this.periodicTaskFactory = periodicTaskFactory; this.periodicTaskFactory = periodicTaskFactory;
TASK_FREQUENCY_OFF = Integer.parseInt(trackRecordingService.getBaseContext().getResources().getString(R.string.frequency_off));
taskFrequency = TASK_FREQUENCY_OFF;
} }
/** /**
@@ -71,7 +75,7 @@ public class PeriodicTaskExecutor {
timerTaskExecutor.shutdown(); timerTaskExecutor.shutdown();
timerTaskExecutor = null; timerTaskExecutor = null;
} }
if (taskFrequency == PreferencesUtils.FREQUENCY_OFF) { if (taskFrequency == TASK_FREQUENCY_OFF) {
Log.d(TAG, "Task frequency is off."); Log.d(TAG, "Task frequency is off.");
return; return;
} }
@@ -32,6 +32,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.HackUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -42,14 +43,6 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
*/ */
public class ActivityTypePreference extends DialogPreference { 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) { public ActivityTypePreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
setDialogLayoutResource(R.layout.preference_activity_type); setDialogLayoutResource(R.layout.preference_activity_type);
@@ -57,8 +50,14 @@ public class ActivityTypePreference extends DialogPreference {
setNegativeButtonText(android.R.string.cancel); setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null); setDialogIcon(null);
setPersistent(true); 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 @Override
@@ -87,7 +86,7 @@ public class ActivityTypePreference extends DialogPreference {
final Context context = getActivity(); final Context context = getActivity();
textView = view.findViewById(R.id.activity_type_preference_text_view); 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); textView.setText(category);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
textView.setAdapter(adapter); textView.setAdapter(adapter);
@@ -132,7 +131,7 @@ public class ActivityTypePreference extends DialogPreference {
} }
private void showIconSelectDialog() { 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); ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
} }
@@ -141,7 +140,8 @@ public class ActivityTypePreference extends DialogPreference {
if (positiveResult) { if (positiveResult) {
String newDefaultActivity = textView.getText().toString(); String newDefaultActivity = textView.getText().toString();
if (getPreference().callChangeListener(newDefaultActivity)) { 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; package de.dennisguse.opentracks.settings;
import android.bluetooth.BluetoothAdapter;
import android.content.Context; 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.R;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
final class PreferenceHelper { final class PreferenceHelper {
/** static String[] getMinRecordingIntervalEntries(Context context) {
* Sets the min recording interval options. String[] entryValues = context.getResources().getStringArray(R.array.min_recording_interval_values);
* String[] entries = new String[entryValues.length];
* @param options the options for (int i = 0; i < entryValues.length; i++) {
* @param values the values int value = Integer.parseInt(entryValues[i]);
*/
static void setMinRecordingIntervalOptions(Context context, String[] options, String[] values) { if (value == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
for (int i = 0; i < values.length; i++) { entries[i] = context.getString(R.string.value_adapt_accuracy);
int value = Integer.parseInt(values[i]); } else if (value == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
switch (value) { entries[i] = context.getString(R.string.value_adapt_battery_life);
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE: } else if (value == PreferencesUtils.getMinRecordingIntervalDefault(context)) {
options[i] = context.getString(R.string.value_adapt_battery_life); entries[i] = context.getString(R.string.value_smallest_recommended);
break; } else {
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY: entries[i] = value < 60 ? context.getString(R.string.value_integer_second, value) : context.getString(R.string.value_integer_minute, value / 60);
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);
} }
} }
return entries;
} }
/** static String[] getRecordingDistanceIntervalEntries(Context context, boolean metricUnits) {
* Sets the recording distance interval options. String[] entryValues = context.getResources().getStringArray(R.array.recording_distance_interval_values);
* String[] entries = new String[entryValues.length];
* @param options the options
* @param values the values final int recordingDistanceIntervalDefault = PreferencesUtils.getRecordingDistanceIntervalDefault(context);
* @param metricUnits true for metric units
*/ for (int i = 0; i < entryValues.length; i++) {
static void setRecordingDistanceIntervalOptions(Context context, String[] options, String[] values, boolean metricUnits) { int value = Integer.parseInt(entryValues[i]);
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
String displayValue; String displayValue;
if (metricUnits) { if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value); displayValue = context.getString(R.string.value_integer_meter, value);
if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) { if (value == recordingDistanceIntervalDefault) {
options[i] = context.getString(R.string.value_integer_meter_recommended, value); entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else { } else {
options[i] = displayValue; entries[i] = displayValue;
} }
} else { } else {
int feet = (int) (value * UnitConversions.M_TO_FT); int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = context.getString(R.string.value_integer_feet, feet); displayValue = context.getString(R.string.value_integer_feet, feet);
if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) { if (value == recordingDistanceIntervalDefault) {
options[i] = context.getString(R.string.value_integer_feet_recommended, feet); entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else { } else {
options[i] = displayValue; entries[i] = displayValue;
} }
} }
} }
return entries;
} }
/** static String[] getMaxRecordingDistanceEntries(Context context, boolean metricUnits) {
* Sets the max recording distance options. String[] entryValues = context.getResources().getStringArray(R.array.max_recording_distance_values);
* String[] entries = new String[entryValues.length];
* @param options the options
* @param values the values final int maxRecordingDistanceDefault = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
* @param metricUnits true for metric units
*/ for (int i = 0; i < entryValues.length; i++) {
static void setMaxRecordingDistanceOptions(Context context, String[] options, String[] values, boolean metricUnits) { int value = Integer.parseInt(entryValues[i]);
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
String displayValue; String displayValue;
if (metricUnits) { if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value); displayValue = context.getString(R.string.value_integer_meter, value);
if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) { if (value == maxRecordingDistanceDefault) {
options[i] = context.getString(R.string.value_integer_meter_recommended, value); entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else { } else {
options[i] = displayValue; entries[i] = displayValue;
} }
} else { } else {
int feet = (int) (value * UnitConversions.M_TO_FT); int feet = (int) (value * UnitConversions.M_TO_FT);
if (feet < 2000) { if (feet < 2000) {
displayValue = context.getString(R.string.value_integer_feet, feet); displayValue = context.getString(R.string.value_integer_feet, feet);
if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) { if (value == maxRecordingDistanceDefault) {
options[i] = context.getString(R.string.value_integer_feet_recommended, feet); entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else { } else {
options[i] = displayValue; entries[i] = displayValue;
} }
} else { } else {
double mile = feet * UnitConversions.FT_TO_MI; double mile = feet * UnitConversions.FT_TO_MI;
displayValue = context.getString(R.string.value_float_mile, mile); displayValue = context.getString(R.string.value_float_mile, mile);
options[i] = displayValue; entries[i] = displayValue;
} }
} }
} }
return entries;
} }
/** static String[] getRecordingGpsAccuracyEntries(Context context, boolean metricUnits) {
* Sets the recording gps accuracy options. String[] entryValues = context.getResources().getStringArray(R.array.recording_gps_accuracy_values);
* String[] entries = new String[entryValues.length];
* @param options the options
* @param values the values final int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
* @param metricUnits true for metric units 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));
static void setRecordingGpsAccuracyOptions(Context context, String[] options, String[] values, boolean metricUnits) {
for (int i = 0; i < values.length; i++) { for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(values[i]); int value = Integer.parseInt(entryValues[i]);
String displayValue; String displayValue;
if (metricUnits) { if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value); displayValue = context.getString(R.string.value_integer_meter, value);
switch (value) { if (value == recordingGPSAccuracyDefault) {
case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT: entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
options[i] = context.getString(R.string.value_integer_meter_recommended, value); } else if (value == recordingGPSAccuracyExcellent) {
break; entries[i] = context.getString(R.string.value_integer_meter_excellent_gps, value);
case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT: } else if (value == recordingGPSAccuracyPoor) {
options[i] = context.getString(R.string.value_integer_meter_excellent_gps, value); entries[i] = context.getString(R.string.value_integer_meter_poor_gps, value);
break; } else {
case PreferencesUtils.RECORDING_GPS_ACCURACY_POOR: entries[i] = displayValue;
options[i] = context.getString(R.string.value_integer_meter_poor_gps, value);
break;
default:
options[i] = displayValue;
} }
} else { } else {
int feet = (int) (value * UnitConversions.M_TO_FT); int feet = (int) (value * UnitConversions.M_TO_FT);
if (feet < 2000) { if (feet < 2000) {
displayValue = context.getString(R.string.value_integer_feet, feet); displayValue = context.getString(R.string.value_integer_feet, feet);
switch (value) {
case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT: if (value == recordingGPSAccuracyDefault) {
options[i] = context.getString(R.string.value_integer_feet_recommended, feet); entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
break; } else if (value == recordingGPSAccuracyExcellent) {
case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT: entries[i] = context.getString(R.string.value_integer_feet_excellent_gps, feet);
options[i] = context.getString(R.string.value_integer_feet_excellent_gps, feet); } else {
break; entries[i] = displayValue;
default:
options[i] = displayValue;
} }
} else { } else {
double mile = feet * UnitConversions.FT_TO_MI; double mile = feet * UnitConversions.FT_TO_MI;
displayValue = context.getString(R.string.value_float_mile, mile); displayValue = context.getString(R.string.value_float_mile, mile);
if (value == PreferencesUtils.RECORDING_GPS_ACCURACY_POOR) { if (value == recordingGPSAccuracyPoor) {
options[i] = context.getString(R.string.value_float_mile_poor_gps, mile); entries[i] = context.getString(R.string.value_float_mile_poor_gps, mile);
} else { } else {
options[i] = displayValue; entries[i] = displayValue;
} }
} }
} }
} }
return entries;
} }
/** static String[] getAutoResumeTrackTimeoutEntries(Context context) {
* Sets the auto resume track timeout options. String[] entryValues = context.getResources().getStringArray(R.array.auto_resume_track_timeout_values);
* String[] entries = new String[entryValues.length];
* @param options the options
* @param values the values for (int i = 0; i < entryValues.length; i++) {
*/ int value = Integer.parseInt(entryValues[i]);
static void setAutoResumeTrackTimeoutOptions(Context context, String[] options, String[] values) { if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
for (int i = 0; i < values.length; i++) { entries[i] = context.getString(R.string.value_never);
int value = Integer.parseInt(values[i]); } else if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
switch (value) { entries[i] = context.getString(R.string.value_always);
case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER: } else {
options[i] = context.getString(R.string.value_never); entries[i] = context.getString(R.string.value_integer_minute, value);
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);
} }
} }
}
/** return entries;
* 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);
} }
} }
@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.Toast; import android.widget.Toast;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.DialogPreference; import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat; import androidx.preference.PreferenceDialogFragmentCompat;
@@ -17,6 +18,10 @@ public class ResetDialogPreference extends DialogPreference {
super(context, attrs); super(context, attrs);
} }
interface ResetCallback {
void onReset();
}
public static class ResetPreferenceDialog extends PreferenceDialogFragmentCompat { public static class ResetPreferenceDialog extends PreferenceDialogFragmentCompat {
static PreferenceDialogFragmentCompat newInstance(String preferenceKey) { static PreferenceDialogFragmentCompat newInstance(String preferenceKey) {
@@ -31,9 +36,14 @@ public class ResetDialogPreference extends DialogPreference {
@Override @Override
public void onDialogClosed(boolean positiveResult) { public void onDialogClosed(boolean positiveResult) {
if (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.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
@@ -12,12 +13,16 @@ import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; 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.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils; 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -29,7 +34,7 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
toolbar.setTitle(R.string.menu_settings); toolbar.setTitle(R.string.menu_settings);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit(); onReset();
} }
@Override @Override
@@ -37,6 +42,12 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
prefsFragment.setDefaultActivity(iconValue); 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 { 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() { private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
getActivity().runOnUiThread(new Runnable() { if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
@Override getActivity().runOnUiThread(new Runnable() {
public void run() { @Override
updateUI(); public void run() {
} updateReset();
}); }
});
if (key == null || key.equals(PreferencesUtils.getKey(getActivity(), R.string.stats_units_key))) { }
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void run() { 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 @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.settings, rootKey); try {
updateUnits(); 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 @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
updateUI(); updateSpeed();
updateUnits();
updateReset();
updateBluetooth();
} }
@Override @Override
@@ -86,41 +116,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
PreferencesUtils.getSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); 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 @Override
public void onDisplayPreferenceDialog(Preference preference) { public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null; DialogFragment dialogFragment = null;
@@ -129,6 +124,8 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
} else if (preference instanceof ActivityTypePreference) { } else if (preference instanceof ActivityTypePreference) {
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey()); activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
dialogFragment = activityPreferenceDialog; dialogFragment = activityPreferenceDialog;
} else if (preference instanceof BluetoothLePreference) {
dialogFragment = BluetoothLePreference.BluetoothLePreferenceDialog.newInstance(preference.getKey());
} }
if (dialogFragment != null) { 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()); final boolean isRecording = PreferencesUtils.isRecording(getActivity());
Preference resetPreference = findPreference(getString(R.string.settings_reset_key)); Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
resetPreference.setEnabled(!isRecording); resetPreference.setEnabled(!isRecording);
}
private void updateSpeed() {
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key)); Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace); speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace);
}
ListPreference bluetoothPreference = findPreference(getString(R.string.bluetooth_sensor_key)); private void updateBluetooth() {
PreferenceHelper.configureBluetoothSensorList(bluetoothPreference); // 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() { private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity()); boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity());
configFrequencyPreference(R.string.voice_frequency_key, metricUnits); ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
configFrequencyPreference(R.string.split_frequency_key, metricUnits); voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), 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);
final ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key)); ListPreference splitFrequency = findPreference(getString(R.string.split_frequency_key));
String[] options = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options); splitFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
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). ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
//Check if this is still needed after upgraded PreferenceFragment. minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
statsRatePreferences.setSummary(statsRatePreferences.getEntry());
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; package de.dennisguse.opentracks.util;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.util.List; import java.util.ArrayList;
import java.util.Set; import java.util.UUID;
/** /**
* Utilities for dealing with bluetooth devices. * Utilities for dealing with bluetooth devices.
@@ -30,29 +31,50 @@ import java.util.Set;
*/ */
public class BluetoothUtils { public class BluetoothUtils {
public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
private BluetoothUtils() { private BluetoothUtils() {
} }
/** public static BluetoothAdapter getDefaultBluetoothAdapter(final String TAG) {
* Populates the device names and the device addresses with all the suitable // If from the main application thread, return directly
* bluetooth devices. if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
* return BluetoothAdapter.getDefaultAdapter();
* @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();
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices(); // Get the default adapter from the main application thread
for (BluetoothDevice device : pairedDevices) { final ArrayList<BluetoothAdapter> adapters = new ArrayList<>(1);
BluetoothClass bluetoothClass = device.getBluetoothClass(); final Object mutex = new Object();
if (bluetoothClass != null && (bluetoothClass.getDeviceClass() & BluetoothClass.Device.HEALTH_PULSE_RATE) != 0) {
deviceAddresses.add(device.getAddress()); Handler handler = new Handler(Looper.getMainLooper());
deviceNames.add(device.getName()); 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) { 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 context the context
* @param trackIds the track ids * @param trackIds the track ids
*/ */
//TODO Share mulitple tracks in different files!
public static Intent newShareFileIntent(Context context, long[] trackIds) { public static Intent newShareFileIntent(Context context, long[] trackIds) {
if (trackIds.length == 0) { if (trackIds.length == 0) {
throw new RuntimeException("Need to share at least one track."); throw new RuntimeException("Need to share at least one track.");
@@ -29,7 +29,6 @@ import de.dennisguse.opentracks.content.Track;
* *
* @author Leif Hendrik Wilden * @author Leif Hendrik Wilden
*/ */
//TODO Cleanup: rename variables
public class LocationUtils { public class LocationUtils {
private static final String TAG = LocationUtils.class.getSimpleName(); private static final String TAG = LocationUtils.class.getSimpleName();
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
@@ -31,67 +32,8 @@ import de.dennisguse.opentracks.R;
*/ */
public class PreferencesUtils { 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 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() { private PreferencesUtils() {
} }
@@ -99,16 +41,60 @@ public class PreferencesUtils {
return PreferenceManager.getDefaultSharedPreferences(context); 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 * Gets a preference key
* *
* @param context the context * @param context the context
* @param keyId the key id * @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); 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. * Gets a boolean preference value.
* *
@@ -180,9 +166,9 @@ public class PreferencesUtils {
* @param context the context * @param context the context
* @param keyId the key id * @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); 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 * @param context the context
*/ */
public static boolean isMetricUnits(Context 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,134 @@ public class PreferencesUtils {
* @param context the context * @param context the context
*/ */
public static boolean isReportSpeed(Context 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)); return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT));
} }
/** public static int getAutoResumeTrackTimeout(Context context) {
* Returns true if chart x axis is by distance, false if by time. 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);
* @param context the context }
*/
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 isChartByDistance(Context context) { public static boolean isChartByDistance(Context context) {
final String CHART_X_AXIS_DEFAULT = context.getString(R.string.chart_x_axis_default);
return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_DEFAULT)); return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_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) { 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; return recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
} }
@@ -263,6 +364,10 @@ public class PreferencesUtils {
} }
public static void resetPreferences(Context context, boolean readAgain) { 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); PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
} }
} }
@@ -70,7 +70,7 @@ public class StatsUtils {
} }
// Set elevation // 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); View elevationContainer = activity.findViewById(R.id.stats_elevation);
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE); elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
@@ -85,7 +85,7 @@ public class StatsUtils {
} }
// Set coordinate // 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); View coordinateSeparator = activity.findViewById(R.id.stats_coordinate_separator);
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE); coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
@@ -108,7 +108,7 @@ public class StatsUtils {
public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) { public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
// heart rate // heart rate
int isVisible = View.VISIBLE; 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; isVisible = View.INVISIBLE;
} }
activity.findViewById(R.id.stats_sensor_container).setVisibility(isVisible); 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); 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. * Sets the total time value.
* *
@@ -151,6 +158,7 @@ public class StatsUtils {
public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics, String trackIconValue) { public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics, String trackIconValue) {
boolean metricUnits = PreferencesUtils.isMetricUnits(activity); boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity); boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
boolean isRecording = PreferencesUtils.isRecording(activity);
// Set total distance // Set total distance
{ {
@@ -173,6 +181,7 @@ public class StatsUtils {
if (trackIconValue != null) { if (trackIconValue != null) {
TrackIconUtils.setIconSpinner(spinner, trackIconValue); TrackIconUtils.setIconSpinner(spinner, trackIconValue);
} }
spinner.setEnabled(isRecording);
} }
// Set time // Set time
@@ -241,7 +250,7 @@ public class StatsUtils {
// Set elevation // 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); View gradeElevationSeparator = activity.findViewById(R.id.stats_elevation_separator);
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE); gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
@@ -359,7 +359,7 @@ public class StringUtils {
String[] options = new String[values.length]; String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[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); options[i] = context.getString(R.string.value_off);
} else if (value < 0) { } else if (value < 0) {
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, Math.abs(value)); options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, Math.abs(value));
@@ -47,7 +47,7 @@ public class TrackNameUtils {
* @param startTime the track start time * @param startTime the track start time
*/ */
public static String getTrackName(Context context, long trackId, long startTime) { 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))) { if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
return StringUtils.formatDateTime(context, startTime); return StringUtils.formatDateTime(context, startTime);
+1 -10
View File
@@ -42,7 +42,7 @@ limitations under the License.
android:id="@+id/track_detail_markers" android:id="@+id/track_detail_markers"
android:icon="@drawable/ic_marker_show_24dp" android:icon="@drawable/ic_marker_show_24dp"
android:orderInCategory="5" android:orderInCategory="5"
android:showAsAction="ifRoom" app:showAsAction="ifRoom"
android:title="@string/menu_markers" /> android:title="@string/menu_markers" />
<item <item
android:id="@+id/track_detail_edit" android:id="@+id/track_detail_edit"
@@ -54,15 +54,6 @@ limitations under the License.
android:icon="@drawable/ic_delete_forever_24dp" android:icon="@drawable/ic_delete_forever_24dp"
android:orderInCategory="10" android:orderInCategory="10"
android:title="@string/menu_delete" /> android:title="@string/menu_delete" />
<item
android:id="@+id/track_detail_voice_frequency"
android:orderInCategory="11"
android:title="@string/menu_voice_frequency" />
<item
android:id="@+id/track_detail_split_frequency"
android:icon="@drawable/ic_split_frequency_24dp"
android:orderInCategory="12"
android:title="@string/menu_split_frequency" />
<item <item
android:id="@+id/track_detail_settings" android:id="@+id/track_detail_settings"
android:icon="@drawable/ic_settings_24dp" android:icon="@drawable/ic_settings_24dp"
+58 -10
View File
@@ -15,20 +15,68 @@ limitations under the License.
--> -->
<resources> <resources>
<string name="app_web_url" translatable="false">https://github.com/dennisguse/opentracks</string> <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 --> <!-- 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="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_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="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="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 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> </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"?><!-- <?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> <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"> <string-array name="activity_types">
<item>@string/default_activity_default</item>
<item>@string/activity_type_airplane</item> <item>@string/activity_type_airplane</item>
<item>@string/activity_type_atv</item> <item>@string/activity_type_atv</item>
<item>@string/activity_type_biking</item> <item>@string/activity_type_biking</item>
@@ -77,29 +96,44 @@ limitations under the License.
<item>@string/activity_type_trail_running</item> <item>@string/activity_type_trail_running</item>
<item>@string/activity_type_train</item> <item>@string/activity_type_train</item>
<item>@string/activity_type_ultimate_frisbee</item> <item>@string/activity_type_ultimate_frisbee</item>
<item>@string/activity_type_unknown</item>
<item>@string/activity_type_wakeboarding</item> <item>@string/activity_type_wakeboarding</item>
<item>@string/activity_type_walking</item> <item>@string/activity_type_walking</item>
<item>@string/activity_type_water_skiing</item> <item>@string/activity_type_water_skiing</item>
<item>@string/activity_type_wind_surfing</item> <item>@string/activity_type_wind_surfing</item>
</string-array> </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"> <string-array name="auto_resume_track_timeout_values">
<item>0</item> <item>@string/auto_resume_track_timeout_never</item>
<item>5</item> <item>5</item>
<item>10</item> <item>@string/auto_resume_track_timeout_default</item>
<item>15</item> <item>15</item>
<item>-1</item> <item>@string/auto_resume_track_timeout_always</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>
<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"> <string-array name="chart_x_axis_values">
<item>@string/chart_x_axis_distance</item> <item>@string/chart_x_axis_distance</item>
<item>@string/chart_x_axis_time</item> <item>@string/chart_x_axis_time</item>
</string-array> </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>
<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"> <string-array name="frequency_values">
<item>0</item> <item>@string/frequency_off</item>
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
<item>5</item> <item>5</item>
@@ -114,18 +148,38 @@ limitations under the License.
<item>-50</item> <item>-50</item>
<item>-100</item> <item>-100</item>
</string-array> </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"> <string-array name="max_recording_distance_values">
<item>50</item> <item>50</item>
<item>100</item> <item>100</item>
<item>200</item> <item>@string/max_recording_distance_default</item>
<item>500</item> <item>500</item>
<item>1000</item> <item>1000</item>
<item>5000</item> <item>5000</item>
</string-array> </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"> <string-array name="min_recording_interval_values">
<item>-2</item> <item>@string/min_recording_interval_adapt_battery_life</item>
<item>-1</item> <item>@string/min_recording_interval_adapt_accuracy</item>
<item>0</item> <item>@string/min_recording_interval_default</item>
<item>2</item> <item>2</item>
<item>3</item> <item>3</item>
<item>4</item> <item>4</item>
@@ -140,26 +194,25 @@ limitations under the License.
<item>900</item> <item>900</item>
<item>1800</item> <item>1800</item>
</string-array> </string-array>
<string-array name="recording_distance_interval_values">
<item>1</item> <string name="recording_gps_accuracy_key" translatable="false">recordingGpsAccuracy</string>
<item>2</item> <string name="recording_gps_accuracy_default" translatable="false">50</string>
<item>5</item> <string name="recording_gps_accuracy_excellent" translatable="false">10</string>
<item>10</item> <string name="recording_gps_accuracy_poor" translatable="false">2000</string>
<item>20</item>
<item>50</item>
<item>100</item>
</string-array>
<string-array name="recording_gps_accuracy_values"> <string-array name="recording_gps_accuracy_values">
<item>10</item> <item>@string/recording_gps_accuracy_excellent</item>
<item>20</item> <item>20</item>
<item>30</item> <item>30</item>
<item>50</item> <item>@string/recording_gps_accuracy_default</item>
<item>100</item> <item>100</item>
<item>200</item> <item>200</item>
<item>500</item> <item>500</item>
<item>1000</item> <item>1000</item>
<item>2000</item> <item>@string/recording_gps_accuracy_poor</item>
</string-array> </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"> <string-array name="stats_rate_imperial_options">
<item>@string/description_speed_imperial</item> <item>@string/description_speed_imperial</item>
<item>@string/description_pace_imperial</item> <item>@string/description_pace_imperial</item>
@@ -168,79 +221,40 @@ limitations under the License.
<item>@string/description_speed_metric</item> <item>@string/description_speed_metric</item>
<item>@string/description_pace_metric</item> <item>@string/description_pace_metric</item>
</string-array> </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"> <string-array name="stats_rate_values">
<item>@string/stats_rate_speed</item> <item>@string/stats_rate_speed</item>
<item>@string/stats_rate_pace</item> <item>@string/stats_rate_pace</item>
</string-array> </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"> <string-array name="stats_units_options">
<item>@string/settings_stats_units_metric</item> <item>@string/settings_stats_units_metric</item>
<item>@string/settings_stats_units_imperial</item> <item>@string/settings_stats_units_imperial</item>
</string-array> </string-array>
<string-array name="stats_units_values">
<item>@string/stats_units_metric</item> <string name="track_name_key" translatable="false">trackName</string>
<item>@string/stats_units_imperial</item> <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>
<string-array name="track_name_options"> <string-array name="track_name_options">
<item>@string/settings_recording_track_name_date_local_option</item> <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_date_iso_8601_option</item>
<item>@string/settings_recording_track_name_number_option</item> <item>@string/settings_recording_track_name_number_option</item>
</string-array> </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> </resources>
+12 -2
View File
@@ -1055,7 +1055,13 @@ limitations under the License.
Sending Sending
</string> </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)."> <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> </string>
<!-- Settings --> <!-- Settings -->
<string name="settings_not_while_recording" translation_description="In settings, the summary for an option that is not available during a <string name="settings_not_while_recording" translation_description="In settings, the summary for an option that is not available during a
@@ -1219,7 +1225,11 @@ limitations under the License.
All settings have been reverted to the default values All settings have been reverted to the default values
</string> </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. <string name="settings_sensor" translation_description="In the 'Settings' page, the option to change the sensor settings.
Character limit: ~26 (1 line)"> Character limit: ~26 (1 line)">
+44 -52
View File
@@ -14,44 +14,45 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/menu_settings"> android:title="@string/menu_settings">
<PreferenceCategory android:title="@string/track_detail_stats_tab"> <PreferenceCategory android:title="@string/track_detail_stats_tab">
<ListPreference <ListPreference
android:defaultValue="@string/stats_units_metric" android:defaultValue="@string/stats_units_default"
android:entries="@array/stats_units_options" android:entries="@array/stats_units_options"
android:entryValues="@array/stats_units_values" android:entryValues="@array/stats_units_values"
android:key="@string/stats_units_key" 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 <ListPreference
android:defaultValue="@string/stats_rate_speed" android:defaultValue="@string/stats_rate_default"
android:entries="@array/stats_rate_metric_options" android:entries="@array/stats_rate_metric_options"
android:entryValues="@array/stats_rate_values" android:entryValues="@array/stats_rate_values"
android:key="@string/stats_rate_key" 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>
<PreferenceCategory android:title="@string/settings_show"> <PreferenceCategory android:title="@string/settings_show">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="@bool/stats_show_grade_elevation_default"
android:key="@string/stats_show_grade_elevation_key" android:key="@string/stats_show_grade_elevation_key"
android:title="@string/settings_stats_grade_elevation" /> android:title="@string/settings_stats_grade_elevation" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="@bool/stats_show_coordinate_default"
android:key="@string/stats_show_coordinate_key" android:key="@string/stats_show_coordinate_key"
android:title="@string/settings_stats_coordinate" /> android:title="@string/settings_stats_coordinate" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/track_detail_chart_tab"> <PreferenceCategory android:title="@string/track_detail_chart_tab">
<ListPreference <ListPreference
android:defaultValue="@string/chart_x_axis_distance" android:defaultValue="@string/chart_x_axis_default"
android:entries="@array/chart_x_axis_options" android:entries="@array/chart_x_axis_options"
android:entryValues="@array/chart_x_axis_values" android:entryValues="@array/chart_x_axis_values"
android:key="@string/chart_x_axis_key" android:key="@string/chart_x_axis_key"
android:summary="%s" android:title="@string/settings_chart_x_axis"
android:title="@string/settings_chart_x_axis" /> app:useSimpleSummaryProvider="true" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="@string/chart_show_elevation_key" android:key="@string/chart_show_elevation_key"
@@ -68,80 +69,71 @@ limitations under the License.
<PreferenceCategory android:title="@string/settings_recording"> <PreferenceCategory android:title="@string/settings_recording">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="@bool/stats_show_on_lockscreen_while_recording_default"
android:key="@string/trackdetail_show_on_lockscreen_while_recording_key" android:key="@string/stats_show_on_lockscreen_while_recording_key"
android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary" android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary"
android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" /> android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" />
<ListPreference <ListPreference
android:defaultValue="0" android:defaultValue="@string/voice_frequency_default"
android:entryValues="@array/frequency_values" android:entryValues="@array/frequency_values"
android:key="@string/voice_frequency_key" 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 <ListPreference
android:defaultValue="0" android:defaultValue="@string/split_frequency_default"
android:entryValues="@array/frequency_values" android:entryValues="@array/frequency_values"
android:key="@string/split_frequency_key" 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 <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:entries="@array/track_name_options"
android:entryValues="@array/track_name_values" android:entryValues="@array/track_name_values"
android:key="@string/track_name_key" 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 <ListPreference
android:defaultValue="0" android:defaultValue="@string/min_recording_interval_default"
android:entryValues="@array/min_recording_interval_values" android:entryValues="@array/min_recording_interval_values"
android:key="@string/min_recording_interval_key" 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 <ListPreference
android:defaultValue="10" android:defaultValue="@string/recording_distance_interval_default"
android:entryValues="@array/recording_distance_interval_values" android:entryValues="@array/recording_distance_interval_values"
android:key="@string/recording_distance_interval_key" 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 <ListPreference
android:defaultValue="200" android:defaultValue="@string/max_recording_distance_default"
android:entryValues="@array/max_recording_distance_values" android:entryValues="@array/max_recording_distance_values"
android:key="@string/max_recording_distance_key" 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 <ListPreference
android:defaultValue="50" android:defaultValue="@string/recording_gps_accuracy_default"
android:entryValues="@array/recording_gps_accuracy_values" android:entryValues="@array/recording_gps_accuracy_values"
android:key="@string/recording_gps_accuracy_key" 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 <ListPreference
android:defaultValue="10" android:defaultValue="@string/auto_resume_track_timeout_default"
android:entries="@array/auto_resume_track_timeout_values" android:entries="@array/auto_resume_track_timeout_values"
android:entryValues="@array/auto_resume_track_timeout_values" android:entryValues="@array/auto_resume_track_timeout_values"
android:key="@string/auto_resume_track_timeout_key" 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 <de.dennisguse.opentracks.settings.ActivityTypePreference
android:defaultValue="@string/activity_type_running" android:defaultValue="@string/default_activity_default"
android:key="@string/default_activity_key" android:key="@string/default_activity_key"
android:title="@string/settings_recording_default_activity_title" /> android:title="@string/settings_recording_default_activity_title" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory android:title="@string/settings_sensor_bluetooth">
android:title="@string/settings_sensor"> <de.dennisguse.opentracks.settings.BluetoothLePreference
<ListPreference
android:defaultValue="@string/sensor_type_value_none" android:defaultValue="@string/sensor_type_value_none"
android:key="@string/bluetooth_sensor_key" android:key="@string/settings_sensor_bluetooth_heart_rate_key"
android:summary="%s" android:title="@string/sensor_state_heart_rate" />
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>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/generic_tracks"> <PreferenceCategory android:title="@string/generic_tracks">
@@ -159,8 +151,8 @@ limitations under the License.
<PreferenceCategory android:title="@string/settings_reset"> <PreferenceCategory android:title="@string/settings_reset">
<de.dennisguse.opentracks.settings.ResetDialogPreference <de.dennisguse.opentracks.settings.ResetDialogPreference
android:dialogTitle="@string/settings_reset_confirm_title"
android:dialogMessage="@string/settings_reset_confirm_message" android:dialogMessage="@string/settings_reset_confirm_message"
android:dialogTitle="@string/settings_reset_confirm_title"
android:key="@string/settings_reset_key" android:key="@string/settings_reset_key"
android:negativeButtonText="@string/generic_cancel" android:negativeButtonText="@string/generic_cancel"
android:persistent="false" android:persistent="false"