forked from upstream-mirrors/OpenTracks
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b34805272f | |||
| 98443dba99 | |||
| dd6e733d8c | |||
| 92d3439c3b | |||
| bd155cd600 | |||
| e14e53c0ae | |||
| 77a6a63174 | |||
| 40f2463f09 | |||
| e5d1048c8d | |||
| bf3b529fb2 | |||
| d0fde46027 | |||
| 0f348dff10 | |||
| d3f5080e73 | |||
| a453c8ec1e | |||
| 2a60aa8f59 | |||
| 41e7870509 | |||
| 57f66b77ab | |||
| 53572406e9 | |||
| a288255ab1 | |||
| 353c958ae0 | |||
| bc5ab0d7e5 | |||
| 9d915b5640 | |||
| 3aaee270eb | |||
| d53a7156e2 | |||
| 3af24ea3d0 | |||
| 5483212b06 | |||
| 419279fe5e | |||
| 501e899b27 | |||
| 3bb9edb009 | |||
| e52b1188a3 | |||
| 9e877e4762 | |||
| 5711757110 | |||
| e1c33dad43 | |||
| 4b2400774c | |||
| afd4a59622 | |||
| bdce437470 | |||
| 33479cda11 | |||
| 385cee3dc7 | |||
| d0bbd7655b | |||
| a83b8f62fe | |||
| 0cd9176703 | |||
| d8e526923d | |||
| 3a6f417662 | |||
| 7d807a7f11 | |||
| e9d5718495 | |||
| 0dd7f74a11 |
@@ -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]
|
||||
@@ -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.
|
||||
+38
-5
@@ -1,12 +1,45 @@
|
||||
#See https://docs.travis-ci.com/user/languages/android/
|
||||
#See https://github.com/ankidroid/Anki-Android/blob/master/.travis.yml
|
||||
language: android
|
||||
dist: trusty
|
||||
|
||||
env:
|
||||
global:
|
||||
- ADB_INSTALL_TIMEOUT=8
|
||||
- ABI=armeabi-v7a
|
||||
- EMU_FLAVOR=default
|
||||
# PATH order is incredibly important. e.g. the 'emulator' script exists in more than one place!
|
||||
- ANDROID_HOME=/usr/local/android-sdk
|
||||
- TOOLS=${ANDROID_HOME}/tools
|
||||
- PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH}
|
||||
matrix:
|
||||
- API=27 ABI=x86_64
|
||||
|
||||
android:
|
||||
components:
|
||||
# installing tools to start, then use `sdkmanager` below to get the rest
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- android-29
|
||||
- android-28
|
||||
|
||||
# Emulator Management: Create, Start and Wait
|
||||
install:
|
||||
- echo y | sdkmanager "platform-tools"
|
||||
- echo y | sdkmanager --channel=4 "emulator"
|
||||
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
- android-wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
|
||||
script:
|
||||
- ./gradlew build
|
||||
#TODO: When all tests are passing, CI should also execute them.
|
||||
#- ./gradlew connectedCheck
|
||||
- ./gradlew clean build
|
||||
- ./gradlew connectedCheck
|
||||
|
||||
sbefore_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
- $HOME/.android/build-cache
|
||||
+7
-3
@@ -7,6 +7,10 @@
|
||||
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
|
||||
4. Create changelog (`_version code_.txt`)
|
||||
5. Create commit with all changes
|
||||
6. Push commits
|
||||
7. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||
8. Push the tagged commit to public repository
|
||||
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||
7. Push commits and tags to public repository
|
||||
8. Add changelog to tag (Github releases)
|
||||
|
||||
## Translations (localization)
|
||||
|
||||
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
|
||||
@@ -0,0 +1,14 @@
|
||||
v3.2.3: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Sharing: use Android Sharesheet
|
||||
- Sharing: share multiple tracks as individual files
|
||||
- Use Android ShareSheet
|
||||
- Heart rate sensor (Bluetooth LE): automatic reconnect
|
||||
|
||||
Bugfix:
|
||||
- Heart rate sensor (Bluetooth LE): disconnect properly
|
||||
- Heart rate wasn't always shown while recording
|
||||
|
||||
Developer:
|
||||
- Enabled TravisCI instrumentation tests
|
||||
@@ -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
|
||||
-4
@@ -40,7 +40,6 @@ import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
@@ -214,7 +213,6 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Track track = providerUtils.createTrack(cursorMock);
|
||||
Assert.assertEquals(trackId, track.getId());
|
||||
Assert.assertEquals(name, track.getName());
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,7 +412,6 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Assert.assertEquals(id, waypoint.getId());
|
||||
Assert.assertEquals(name, waypoint.getName());
|
||||
Assert.assertEquals(trackId, waypoint.getTrackId());
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -709,7 +706,6 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
|
||||
Assert.assertEquals(time, location.getTime(), 0.01);
|
||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
* @param lastTrack true if it is the last track in the gpx
|
||||
* @param trackId the track id
|
||||
*/
|
||||
protected void expectUpdateTrack(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
|
||||
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
|
||||
contentProviderUtils.updateTrack(trackCaptor.capture());
|
||||
|
||||
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
|
||||
|
||||
+30
-19
@@ -27,13 +27,13 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -84,8 +84,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
@@ -95,25 +94,28 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
@@ -121,12 +123,18 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
@@ -135,30 +143,33 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegmentsNoTime() {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
|
||||
}
|
||||
|
||||
@@ -199,7 +210,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
||||
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
||||
|
||||
+20
-12
@@ -27,11 +27,11 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -68,8 +68,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
@@ -77,27 +76,31 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint(new Location[]{location1}, 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.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
contentProviderUtils.clearTrack(context, TRACK_ID_0);
|
||||
@@ -106,12 +109,18 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
@@ -120,7 +129,6 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
}
|
||||
|
||||
+35
-65
@@ -15,14 +15,12 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -38,7 +36,6 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -77,14 +74,12 @@ public class TrackRecordingServiceTest {
|
||||
private long trackId = Math.abs(new Random().nextLong());
|
||||
|
||||
static Intent createStartIntent(Context context) {
|
||||
Intent startIntent = new Intent();
|
||||
startIntent.setClass(context, TrackRecordingService.class);
|
||||
return startIntent;
|
||||
return new Intent(context, TrackRecordingService.class);
|
||||
}
|
||||
|
||||
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_timeout_key, timeoutMins);
|
||||
PreferencesUtils.setString(context, R.string.auto_resume_track_timeout_key, "" + timeoutMins);
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -140,12 +135,15 @@ public class TrackRecordingServiceTest {
|
||||
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
||||
|
||||
// Clear the number of attempts and set the timeout to 10 min.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We expect to resume the previous track.
|
||||
@@ -156,7 +154,7 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_simulateReboot() throws Exception {
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
@@ -165,15 +163,17 @@ public class TrackRecordingServiceTest {
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||
mServiceRule.unbindService();
|
||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
Assert.assertTrue(service.isRecording());
|
||||
}
|
||||
|
||||
@@ -184,12 +184,15 @@ public class TrackRecordingServiceTest {
|
||||
createDummyTrack(trackId, System.currentTimeMillis(), false);
|
||||
|
||||
// Clear the number of attempts and set the timeout to 10 min.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it was stopped.
|
||||
@@ -201,15 +204,18 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
||||
// Insert a dummy track last updated 20 min ago.
|
||||
createDummyTrack(trackId, System.currentTimeMillis() - 20 * 60 * 1000, true);
|
||||
createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
|
||||
|
||||
// Clear the number of attempts and set the timeout to 10 min.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it has expired.
|
||||
@@ -224,16 +230,15 @@ public class TrackRecordingServiceTest {
|
||||
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
||||
|
||||
// Set the number of attempts to max.
|
||||
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
|
||||
//Explicit start service, so `startCommand()` is executed - which would be called by BootReceiver.
|
||||
context.startService(startIntent);
|
||||
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
//then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because there were already too many attempts.
|
||||
@@ -247,7 +252,10 @@ public class TrackRecordingServiceTest {
|
||||
List<Track> tracks = providerUtils.getAllTracks();
|
||||
Assert.assertTrue(tracks.isEmpty());
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Intent startIntent = createStartIntent(context);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// Test if we start in no-recording mode by default.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
@@ -266,11 +274,14 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||
Intent startIntent = createStartIntent(context);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// Just set recording track to a bogus value.
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
@@ -286,7 +297,7 @@ public class TrackRecordingServiceTest {
|
||||
long newTrackId = service.startNewTrack();
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||
|
||||
Assert.assertEquals(trackId, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@@ -299,7 +310,7 @@ public class TrackRecordingServiceTest {
|
||||
// Ending the current track when there is no recording should not result in any error.
|
||||
service.endCurrentTrack();
|
||||
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertFalse(PreferencesUtils.isRecording(context));
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@@ -405,45 +416,4 @@ public class TrackRecordingServiceTest {
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNull(wpt.getTripStatistics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous/waitable broadcast receiver to be used in testing.
|
||||
*/
|
||||
private class BlockingBroadcastReceiver extends BroadcastReceiver {
|
||||
private static final long MAX_WAIT_TIME_MS = 3000;
|
||||
private final List<Intent> receivedIntents = new ArrayList<>();
|
||||
|
||||
public List<Intent> getReceivedIntents() {
|
||||
return receivedIntents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
Log.d("Test", "Got broadcast: " + intent);
|
||||
synchronized (receivedIntents) {
|
||||
receivedIntents.add(intent);
|
||||
receivedIntents.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean waitUntilReceived(int receiveCount) {
|
||||
long deadline = System.currentTimeMillis() + MAX_WAIT_TIME_MS;
|
||||
synchronized (receivedIntents) {
|
||||
while (receivedIntents.size() < receiveCount) {
|
||||
try {
|
||||
// Wait releases synchronized lock until it returns
|
||||
receivedIntents.wait(500);
|
||||
} catch (InterruptedException e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() > deadline) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -103,7 +103,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT);
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT);
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, Integer.parseInt(context.getResources().getString(R.string.split_frequency_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
Track track = providerUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
Assert.assertEquals(id, track.getId());
|
||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
|
||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
|
||||
-557
@@ -1,557 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks.services.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.same;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@link AnnouncementPeriodicTask}.
|
||||
*
|
||||
* WARNING: I'm not responsible if your eyes start bleeding while reading this code.
|
||||
* You have been warned.
|
||||
* It's still better than no test, though.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AnnouncementPeriodicTaskTest {
|
||||
|
||||
// Use something other than our hardcoded value
|
||||
private static final Locale DEFAULT_LOCALE = Locale.KOREAN;
|
||||
private static final String ANNOUNCEMENT = "I can haz cheeseburger?";
|
||||
private Locale oldDefaultLocale;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private AnnouncementPeriodicTask task;
|
||||
@Mock
|
||||
private AnnouncementPeriodicTask mockTask;
|
||||
private ArgumentCaptor<OnInitListener> initListenerCapture = ArgumentCaptor.forClass(OnInitListener.class);
|
||||
private ArgumentCaptor<PhoneStateListener> phoneListenerCapture = ArgumentCaptor.forClass(PhoneStateListener.class);
|
||||
private TextToSpeechDelegate ttsDelegate;
|
||||
@Mock
|
||||
private TextToSpeechInterface tts;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
oldDefaultLocale = Locale.getDefault();
|
||||
Locale.setDefault(DEFAULT_LOCALE);
|
||||
|
||||
// Eww, the effort required just to mock TextToSpeech is insane
|
||||
final AtomicBoolean listenerCalled = new AtomicBoolean();
|
||||
OnInitListener blockingListener = new OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
synchronized (this) {
|
||||
listenerCalled.set(true);
|
||||
notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ttsDelegate = new TextToSpeechDelegate(context, blockingListener);
|
||||
|
||||
// Wait for all async operations done in the constructor to finish.
|
||||
synchronized (blockingListener) {
|
||||
while (!listenerCalled.get()) {
|
||||
// Releases the synchronized lock until we're woken up.
|
||||
blockingListener.wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a partial forwarding mock
|
||||
task = new AnnouncementPeriodicTask(context) {
|
||||
@Override
|
||||
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
|
||||
return mockTask.newTextToSpeech(ctx, onInitListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAnnouncement(TripStatistics stats) {
|
||||
return mockTask.getAnnouncement(stats);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void listenToPhoneState(PhoneStateListener listener, int events) {
|
||||
mockTask.listenToPhoneState(listener, events);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Locale.setDefault(oldDefaultLocale);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart() {
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
Assert.assertNotNull(ttsInitListener);
|
||||
|
||||
ttsInitListener.onInit(TextToSpeech.SUCCESS);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart_notReady() {
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
Assert.assertNotNull(ttsInitListener);
|
||||
|
||||
ttsInitListener.onInit(TextToSpeech.ERROR);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdown() {
|
||||
// First, start
|
||||
doStart();
|
||||
verify(mockTask);
|
||||
reset(mockTask);
|
||||
|
||||
// Then, shut down
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
mockTask.listenToPhoneState(
|
||||
same(phoneListener), eq(PhoneStateListener.LISTEN_NONE));
|
||||
tts.shutdown();
|
||||
task.shutdown();
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun() {
|
||||
// when service data calls
|
||||
TripStatistics stats = new TripStatistics();
|
||||
|
||||
// when announcement building call
|
||||
when(mockTask.getAnnouncement(same(stats))).thenReturn(ANNOUNCEMENT);
|
||||
|
||||
// Put task in "ready" state
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isLanguageAvailable(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
||||
when(tts.setLanguage(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
||||
when(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).thenReturn(TextToSpeech.SUCCESS);
|
||||
when(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) any())).thenReturn(0);
|
||||
|
||||
// Run the announcement
|
||||
task.announce(stats);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_notReady() {
|
||||
// Put task in "not ready" state
|
||||
startTask(TextToSpeech.ERROR);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_duringCall() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(false);
|
||||
|
||||
// Run the announcement
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
|
||||
task.run(null);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_ringWhileSpeaking() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(true);
|
||||
when(tts.stop()).thenReturn(TextToSpeech.SUCCESS);
|
||||
|
||||
// Update the state to ringing - this should stop the current announcement.
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
||||
|
||||
// Run the announcement - this should do nothing.
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_whileRinging() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(false);
|
||||
|
||||
// Run the announcement
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_noService() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_noStats() {
|
||||
// when service data calls
|
||||
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_time_zero() {
|
||||
long time = 0; // 0 seconds
|
||||
Assert.assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time one.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_time_one() {
|
||||
long time = 1000; // 1 second
|
||||
Assert.assertEquals("0 minutes 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
||||
* numbers with the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_singular_has_hour() {
|
||||
long time = (60 * 60 * 1000) + (60 * 1000) + (1000); // 1 hour 1 minute 1 second
|
||||
Assert.assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
||||
* with the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_plural_has_hour() {
|
||||
long time = (2 * 60 * 60 * 1000) + (2 * 60 * 1000) + (2 * 1000); // 2 hours 2 minutes 2 seconds
|
||||
Assert.assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
||||
* numbers without the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_singular_no_hour() {
|
||||
long time = (1 * 60 * 1000) + (1 * 1000); // 1 minute 1 second
|
||||
Assert.assertEquals("1 minute 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
||||
* without the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_plural_no_hour() {
|
||||
long time = (2 * 60 * 1000) + (2 * 1000); // 2 minutes 2 seconds
|
||||
Assert.assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
private void startTask(int state) {
|
||||
reset(tts);
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
ttsInitListener.onInit(state);
|
||||
//TODO
|
||||
reset(tts);
|
||||
//AndroidMock.resetToDefault(tts);
|
||||
}
|
||||
|
||||
private void doStart() {
|
||||
mockTask.listenToPhoneState(phoneListenerCapture.capture(), eq(PhoneStateListener.LISTEN_CALL_STATE));
|
||||
when(mockTask.newTextToSpeech(same(context), initListenerCapture.capture())).thenReturn(ttsDelegate);
|
||||
|
||||
task.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mockable interface that we delegate TTS calls to.
|
||||
*/
|
||||
interface TextToSpeechInterface {
|
||||
int addEarcon(String earcon, String packagename, int resourceId);
|
||||
|
||||
int addEarcon(String earcon, String filename);
|
||||
|
||||
int addSpeech(String text, String packagename, int resourceId);
|
||||
|
||||
int addSpeech(String text, String filename);
|
||||
|
||||
boolean areDefaultsEnforced();
|
||||
|
||||
String getDefaultEngine();
|
||||
|
||||
Locale getLanguage();
|
||||
|
||||
int isLanguageAvailable(Locale loc);
|
||||
|
||||
boolean isSpeaking();
|
||||
|
||||
int playEarcon(String earcon, int queueMode,
|
||||
HashMap<String, String> params);
|
||||
|
||||
int playSilence(long durationInMs, int queueMode, HashMap<String, String> params);
|
||||
|
||||
int setLanguage(Locale loc);
|
||||
|
||||
int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener);
|
||||
|
||||
int setPitch(float pitch);
|
||||
|
||||
int setSpeechRate(float speechRate);
|
||||
|
||||
void shutdown();
|
||||
|
||||
int speak(String text, int queueMode, HashMap<String, String> params);
|
||||
|
||||
int stop();
|
||||
|
||||
int synthesizeToFile(String text, HashMap<String, String> params, String filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass of {@link TextToSpeech} which delegates calls to the interface
|
||||
* above.
|
||||
* The logic here is stupid and the author is ashamed of having to write it
|
||||
* like this, but basically the issue is that TextToSpeech cannot be mocked
|
||||
* without running its constructor, its constructor runs async operations
|
||||
* which call other methods (and then if the methods are part of a mock we'd
|
||||
* have to set a behavior, but we can't 'cause the object hasn't been fully
|
||||
* built yet).
|
||||
* The logic is that calls made during the constructor (when tts is not yet
|
||||
* set) will go up to the original class, but after tts is set we'll forward
|
||||
* them all to the mock.
|
||||
*/
|
||||
private class TextToSpeechDelegate extends TextToSpeech implements TextToSpeechInterface {
|
||||
public TextToSpeechDelegate(Context context, OnInitListener listener) {
|
||||
super(context, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addEarcon(String earcon, String packagename, int resourceId) {
|
||||
if (tts == null) {
|
||||
return super.addEarcon(earcon, packagename, resourceId);
|
||||
}
|
||||
return tts.addEarcon(earcon, packagename, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addEarcon(String earcon, String filename) {
|
||||
if (tts == null) {
|
||||
return super.addEarcon(earcon, filename);
|
||||
}
|
||||
return tts.addEarcon(earcon, filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addSpeech(String text, String packagename, int resourceId) {
|
||||
if (tts == null) {
|
||||
return super.addSpeech(text, packagename, resourceId);
|
||||
}
|
||||
return tts.addSpeech(text, packagename, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addSpeech(String text, String filename) {
|
||||
if (tts == null) {
|
||||
return super.addSpeech(text, filename);
|
||||
}
|
||||
return tts.addSpeech(text, filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLanguage() {
|
||||
if (tts == null) {
|
||||
return super.getLanguage();
|
||||
}
|
||||
return tts.getLanguage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int isLanguageAvailable(Locale loc) {
|
||||
if (tts == null) {
|
||||
return super.isLanguageAvailable(loc);
|
||||
}
|
||||
return tts.isLanguageAvailable(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpeaking() {
|
||||
if (tts == null) {
|
||||
return super.isSpeaking();
|
||||
}
|
||||
return tts.isSpeaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int playEarcon(String earcon, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.playEarcon(earcon, queueMode, params);
|
||||
}
|
||||
return tts.playEarcon(earcon, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int playSilence(long durationInMs, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.playSilence(durationInMs, queueMode, params);
|
||||
}
|
||||
return tts.playSilence(durationInMs, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setLanguage(Locale loc) {
|
||||
if (tts == null) {
|
||||
return super.setLanguage(loc);
|
||||
}
|
||||
return tts.setLanguage(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener) {
|
||||
if (tts == null) {
|
||||
return super.setOnUtteranceCompletedListener(listener);
|
||||
}
|
||||
return tts.setOnUtteranceCompletedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setPitch(float pitch) {
|
||||
if (tts == null) {
|
||||
return super.setPitch(pitch);
|
||||
}
|
||||
return tts.setPitch(pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setSpeechRate(float speechRate) {
|
||||
if (tts == null) {
|
||||
return super.setSpeechRate(speechRate);
|
||||
}
|
||||
return tts.setSpeechRate(speechRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (tts == null) {
|
||||
super.shutdown();
|
||||
return;
|
||||
}
|
||||
tts.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int speak(
|
||||
String text, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.speak(text, queueMode, params);
|
||||
}
|
||||
return tts.speak(text, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stop() {
|
||||
if (tts == null) {
|
||||
return super.stop();
|
||||
}
|
||||
return tts.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int synthesizeToFile(String text, HashMap<String, String> params, String filename) {
|
||||
if (tts == null) {
|
||||
return super.synthesizeToFile(text, params, filename);
|
||||
}
|
||||
return tts.synthesizeToFile(text, params, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ public class StringUtilsTest {
|
||||
assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300);
|
||||
assertGetTime("2010-05-04T03:02:01.35", 2010, 5, 4, 3, 2, 1, 350);
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3525Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 353);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,4 +199,24 @@ public class StringUtilsTest {
|
||||
Assert.assertEquals(-1, parts[1]);
|
||||
Assert.assertEquals(0, parts[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDecimal() {
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 0));
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.1, 0));
|
||||
Assert.assertEquals("1", StringUtils.formatDecimal(1.1, 0));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10, 0));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10.1, 0));
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(-0.1, 0));
|
||||
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 2));
|
||||
Assert.assertEquals("0.1", StringUtils.formatDecimal(0.1, 2));
|
||||
Assert.assertEquals("1.1", StringUtils.formatDecimal(1.1, 2));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10, 2));
|
||||
Assert.assertEquals("10.1", StringUtils.formatDecimal(10.1, 2));
|
||||
Assert.assertEquals("10.11", StringUtils.formatDecimal(10.111, 2));
|
||||
Assert.assertEquals("-0.1", StringUtils.formatDecimal(-0.1, 2));
|
||||
|
||||
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ limitations under the License.
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="3079"
|
||||
android:versionName="v3.2.2"> <!-- For FDroid. -->
|
||||
android:versionCode="3123"
|
||||
android:versionName="v3.2.5"> <!-- For FDroid. -->
|
||||
<!-- Permission to initialize services -->
|
||||
<application
|
||||
android:name=".Startup"
|
||||
@@ -124,6 +124,7 @@ limitations under the License.
|
||||
android:name=".services.TrackRecordingService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="location|connectedDevice"
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
</application>
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||
stopRecording = true;
|
||||
} else {
|
||||
long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);
|
||||
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||
for (long trackId : trackIds) {
|
||||
if (trackId == recordingTrackId) {
|
||||
stopRecording = true;
|
||||
|
||||
@@ -79,11 +79,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
// Note that the key can be null
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key);
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@@ -57,7 +57,6 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.ToolbarUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
/**
|
||||
@@ -123,10 +122,6 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
MenuItem shareMenuItem = menu.findItem(R.id.list_context_menu_share);
|
||||
if (isSingleSelectionTrack) {
|
||||
shareMenuItem.setVisible(!isRecording);
|
||||
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
|
||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||
|
||||
ToolbarUtils.setupShareActionProvider(SearchListActivity.this, shareMenuItem, new long[]{trackId});
|
||||
}
|
||||
|
||||
// One item, item is a marker
|
||||
@@ -148,14 +143,14 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) {
|
||||
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key);
|
||||
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(SearchListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -290,6 +285,11 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentUtils.showCoordinateOnMap(this, (double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + "");
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
if (markerId != null) {
|
||||
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.widget.TabHost;
|
||||
import android.widget.TabHost.TabContentFactory;
|
||||
import android.widget.TabWidget;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
@@ -79,6 +80,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Fragment getItem(int position) {
|
||||
TabInfo info = tabInfos.get(position);
|
||||
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
|
||||
|
||||
@@ -48,14 +48,12 @@ import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.FrequencyDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ToolbarUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
@@ -96,7 +94,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
// Preferences
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
private boolean showOnLockScreen = PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN;
|
||||
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
@@ -127,16 +124,15 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key);
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||
}
|
||||
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
||||
}
|
||||
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key))) {
|
||||
showOnLockScreen = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key, PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN);
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||
setLockscreenPolicy();
|
||||
}
|
||||
|
||||
@@ -157,8 +153,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
private MenuItem shareMenuItem;
|
||||
private MenuItem voiceFrequencyMenuItem;
|
||||
private MenuItem splitFrequencyMenuItem;
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -231,12 +225,14 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
} else if (showOnLockScreen) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
} else {
|
||||
if (showOnLockScreen)
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +272,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
|
||||
if (photoUri != null) {
|
||||
@@ -316,12 +312,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
||||
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
||||
|
||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||
ToolbarUtils.setupShareActionProviderCompat(this, shareMenuItem, new long[]{trackId});
|
||||
|
||||
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);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
@@ -343,6 +334,11 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_insert_photo:
|
||||
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
||||
@@ -358,12 +354,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
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:
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
@@ -465,8 +455,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
||||
shareMenuItem.setVisible(!isRecording());
|
||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
voiceFrequencyMenuItem.setVisible(isRecording());
|
||||
splitFrequencyMenuItem.setVisible(isRecording());
|
||||
String title;
|
||||
if (isRecording()) {
|
||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
|
||||
@@ -60,8 +60,8 @@ import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ServiceUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.ToolbarUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
@@ -115,9 +115,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
|
||||
boolean isSingleSelection = trackIds.length == 1;
|
||||
|
||||
MenuItem shareMenuItem = menu.findItem(R.id.list_context_menu_share);
|
||||
ToolbarUtils.setupShareActionProvider(TrackListActivity.this, shareMenuItem, trackIds);
|
||||
|
||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
||||
}
|
||||
@@ -138,22 +135,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.stats_units_key))) {
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(
|
||||
PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(
|
||||
TrackListActivity.this, R.string.recording_track_id_key);
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
|
||||
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
trackRecordingServiceConnection.startAndBind();
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(
|
||||
TrackListActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this,
|
||||
R.string.recording_track_paused_key,
|
||||
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -229,7 +221,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
startRecording();
|
||||
@@ -386,7 +378,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
boolean isGpsStarted = TrackRecordingServiceConnection.isRecordingServiceRunning(this);
|
||||
boolean isGpsStarted = ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
updateMenuItems(isGpsStarted, isRecording);
|
||||
|
||||
@@ -406,7 +398,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
startGps = !TrackRecordingServiceConnection.isRecordingServiceRunning(this);
|
||||
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||
|
||||
// Show toast
|
||||
Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT);
|
||||
@@ -531,12 +523,18 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
* @return true if handled.
|
||||
*/
|
||||
private boolean handleContextItem(int itemId, long[] trackIds) {
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, trackIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
@@ -28,11 +28,6 @@ public class SensorDataSetLocation extends Location {
|
||||
|
||||
private SensorDataSet sensorDataSet = null;
|
||||
|
||||
/**
|
||||
* The id of this location from the provider.
|
||||
*/
|
||||
private int id = -1;
|
||||
|
||||
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
|
||||
super(location);
|
||||
this.sensorDataSet = sensorDataSet;
|
||||
@@ -50,17 +45,8 @@ public class SensorDataSetLocation extends Location {
|
||||
this.sensorDataSet = sensorDataSet;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
super.reset();
|
||||
sensorDataSet = null;
|
||||
id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,13 +254,13 @@ public class TrackDataHub implements DataSourceListener {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
|
||||
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
if (key != null) {
|
||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
@@ -270,7 +270,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
|
||||
if (PreferencesUtils.isKey(context, R.string.stats_rate_key, key)) {
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||
if (key != null) {
|
||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
@@ -280,8 +280,8 @@ public class TrackDataHub implements DataSourceListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
if (key != null) {
|
||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
|
||||
@@ -290,8 +290,8 @@ public class TrackDataHub implements DataSourceListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) {
|
||||
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
if (key != null) {
|
||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
|
||||
|
||||
@@ -4,7 +4,7 @@ public final class SensorDataSet {
|
||||
|
||||
public static final float DATA_UNAVAILABLE = Float.NaN;
|
||||
|
||||
//TODO It might be necessary to consider: sensor if sensor was connected as well.
|
||||
//TODO It might be necessary to consider: if sensor was connected as well.
|
||||
public String sensorName;
|
||||
public String sensorAddress;
|
||||
private float heartRate;
|
||||
@@ -40,7 +40,7 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
public boolean hasHeartRate() {
|
||||
return !Float.isNaN(heartRate);
|
||||
return !Float.isNaN(heartRate) && heartRate > 0;
|
||||
}
|
||||
|
||||
public float getHeartRate() {
|
||||
|
||||
@@ -8,6 +8,7 @@ public enum SensorState {
|
||||
NONE,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING,
|
||||
DISCONNECTED;
|
||||
|
||||
public static String getStateAsString(SensorState state, Context c) {
|
||||
@@ -18,6 +19,8 @@ public enum SensorState {
|
||||
return c.getString(R.string.sensor_state_connecting);
|
||||
case CONNECTED:
|
||||
return c.getString(R.string.sensor_state_connected);
|
||||
case DISCONNECTING:
|
||||
return c.getString(R.string.sensor_state_disconnected);
|
||||
case DISCONNECTED:
|
||||
return c.getString(R.string.sensor_state_disconnected);
|
||||
default:
|
||||
|
||||
@@ -263,8 +263,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
reportSpeed = speed;
|
||||
chartView.setReportSpeed(reportSpeed);
|
||||
boolean chartShowSpeed = PreferencesUtils.getBoolean(
|
||||
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
|
||||
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
|
||||
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
|
||||
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -309,24 +308,24 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
reloadTrackDataHub();
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
|
||||
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.shouldChartShowElevation(getActivity()))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
boolean chartShowSpeed = PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
|
||||
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
|
||||
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_power_key, PreferencesUtils.CHART_SHOW_POWER_DEFAULT))) {
|
||||
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.shouldChartShowPower(getActivity()))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_cadence_key, PreferencesUtils.CHART_SHOW_CADENCE_DEFAULT))) {
|
||||
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.shouldChartShowCadence(getActivity()))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_heart_rate_key, PreferencesUtils.CHART_SHOW_HEART_RATE_DEFAULT))) {
|
||||
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.shouldChartShowHeartRate(getActivity()))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (needUpdate) {
|
||||
|
||||
-14
@@ -1,6 +1,5 @@
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -79,19 +78,6 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment implements
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Only used by obsolete Androidx preferences. Remove when onAttach(Context) is called.
|
||||
*/
|
||||
@Override
|
||||
public void onAttach(@NonNull Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
chooseActivityTypeCaller = (ChooseActivityTypeCaller) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity + " must implement " + ChooseActivityTypeCaller.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,6 @@ import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.sensors.RemoteSensorManager;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
@@ -59,7 +58,7 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
*/
|
||||
public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
public static final String STATS_FRAGMENT_TAG = "statsFragment";
|
||||
public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
private Handler handlerUpdateUI;
|
||||
@@ -68,7 +67,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
private Location lastLocation = null;
|
||||
private TripStatistics lastTripStatistics = null;
|
||||
private SensorDataSet sensorDataSet = null;
|
||||
|
||||
private final Runnable updateUIeachSecond = new Runnable() {
|
||||
public void run() {
|
||||
@@ -94,8 +92,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
handlerUpdateUI = new Handler();
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
|
||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
|
||||
@@ -123,6 +119,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
|
||||
updateUi(getActivity());
|
||||
if (isSelectedTrackRecording()) {
|
||||
handlerUpdateUI.post(updateUIeachSecond);
|
||||
@@ -140,6 +140,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind();
|
||||
trackRecordingServiceConnection = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -298,13 +299,14 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to fetch most recent {@link SensorDataSet} {@link RemoteSensorManager}.
|
||||
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}.
|
||||
*/
|
||||
private void updateSensorDataUI() {
|
||||
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
|
||||
SensorDataSet sensorDataSet = null;
|
||||
if (trackRecordingService == null) {
|
||||
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
|
||||
sensorDataSet = null;
|
||||
} else {
|
||||
//TODO sensorState = trackRecordingService.getSensorState();
|
||||
sensorDataSet = trackRecordingService.getSensorData();
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.TracksColumns;
|
||||
@@ -100,7 +99,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
boolean isRecording = PreferencesUtils.isRecording(exportActivity);
|
||||
boolean isPaused = PreferencesUtils.getBoolean(exportActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(exportActivity);
|
||||
// Get the wake lock if not recording or paused
|
||||
if (!isRecording || isPaused) {
|
||||
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
this.context = context;
|
||||
this.importTrackId = importTrackId;
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
||||
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
trackIds = new ArrayList<>();
|
||||
waypoints = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
@@ -104,7 +103,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
// Get the wake lock if not recording or paused
|
||||
boolean isRecording = PreferencesUtils.isRecording(importActivity);
|
||||
boolean isPaused = PreferencesUtils.getBoolean(importActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
|
||||
if (!isRecording || isPaused) {
|
||||
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
||||
}
|
||||
|
||||
@@ -182,6 +182,10 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
* On waypoint end.
|
||||
*/
|
||||
private void onWaypointEnd() throws SAXException {
|
||||
if (waypointType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a waypoint if the waypointType matches
|
||||
WaypointType type;
|
||||
switch (waypointType) {
|
||||
|
||||
@@ -101,7 +101,7 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
* @param trackId the trackId
|
||||
*/
|
||||
private void cleanImport(long trackId) {
|
||||
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
if (PreferencesUtils.isRecording(trackId)) {
|
||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
}
|
||||
|
||||
@@ -57,8 +57,7 @@ import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.RemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.sensors.RemoteSensorManagerFactory;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||
import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory;
|
||||
@@ -118,28 +117,28 @@ public class TrackRecordingService extends Service {
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
|
||||
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.
|
||||
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))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
|
||||
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
voiceExecutor.setMetricUnits(metricUnits);
|
||||
splitExecutor.setMetricUnits(metricUnits);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.voice_frequency_key))) {
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT));
|
||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.split_frequency_key))) {
|
||||
splitExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT));
|
||||
if (PreferencesUtils.isKey(context, R.string.split_frequency_key, key)) {
|
||||
splitExecutor.setTaskFrequency(PreferencesUtils.getSplitFrequency(context));
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.min_recording_interval_key))) {
|
||||
int minRecordingInterval = PreferencesUtils.getInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||
switch (minRecordingInterval) {
|
||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
|
||||
// Choose battery life over moving time accuracy.
|
||||
@@ -153,23 +152,23 @@ public class TrackRecordingService extends Service {
|
||||
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) {
|
||||
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.max_recording_distance_key))) {
|
||||
maxRecordingDistance = PreferencesUtils.getInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.auto_resume_track_timeout_key))) {
|
||||
autoResumeTrackTimeout = PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
if (PreferencesUtils.isKey(context, R.string.auto_resume_track_timeout_key, key)) {
|
||||
autoResumeTrackTimeout = PreferencesUtils.getAutoResumeTrackTimeout(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
private TripStatisticsUpdater markerTripStatisticsUpdater;
|
||||
private WakeLock wakeLock;
|
||||
private RemoteSensorManager remoteSensorManager;
|
||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||
private Location lastLocation;
|
||||
private boolean currentSegmentHasLocation;
|
||||
private boolean isIdle; // true if idle
|
||||
@@ -266,7 +265,7 @@ public class TrackRecordingService extends Service {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (remoteSensorManager != null) {
|
||||
RemoteSensorManagerFactory.releaseSystemSensorManager();
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
|
||||
@@ -446,7 +445,8 @@ public class TrackRecordingService extends Service {
|
||||
*/
|
||||
private void handleStartCommand(Intent intent, int startId) {
|
||||
// Check if the service is called to resume track (from phone reboot)
|
||||
if (intent != null && intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false) && !shouldResumeTrack()) {
|
||||
boolean resumeTrackRequested = intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false);
|
||||
if (resumeTrackRequested && !shouldResumeTrack()) {
|
||||
Log.i(TAG, "Stop resume track.");
|
||||
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
|
||||
stopSelfResult(startId);
|
||||
@@ -464,17 +464,19 @@ public class TrackRecordingService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
int retries = PreferencesUtils.getInt(this, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
|
||||
int retries = PreferencesUtils.getAutoResumeTrackCurrentRetryDefault(this);
|
||||
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
|
||||
Log.d(TAG, "Not resuming. Exceeded maximum retry attempts.");
|
||||
return false;
|
||||
}
|
||||
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, retries + 1);
|
||||
PreferencesUtils.incrementAutoResumeTrackCurrentRetryDefault(this);
|
||||
|
||||
if (autoResumeTrackTimeout == PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER) {
|
||||
if (autoResumeTrackTimeout == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
||||
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
|
||||
return false;
|
||||
} else if (autoResumeTrackTimeout == PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS) {
|
||||
}
|
||||
|
||||
if (autoResumeTrackTimeout == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
||||
Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
|
||||
return true;
|
||||
}
|
||||
@@ -508,13 +510,13 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
// Update shared preferences
|
||||
updateRecordingState(trackId, false);
|
||||
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, 0);
|
||||
PreferencesUtils.resetAutoResumeTrackCurrentRetryDefault(this);
|
||||
|
||||
// Update database
|
||||
track.setId(trackId);
|
||||
track.setName(TrackNameUtils.getTrackName(this, trackId, now));
|
||||
|
||||
String category = PreferencesUtils.getString(this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
|
||||
String category = PreferencesUtils.getDefaultActivity(this);
|
||||
track.setCategory(category);
|
||||
track.setIcon(TrackIconUtils.getIconValue(this, category));
|
||||
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
||||
@@ -594,7 +596,8 @@ public class TrackRecordingService extends Service {
|
||||
*/
|
||||
private void startRecording(boolean trackStarted) {
|
||||
// Update instance variables
|
||||
remoteSensorManager = RemoteSensorManagerFactory.getSystemSensorManager(this);
|
||||
remoteSensorManager = new BluetoothRemoteSensorManager(this);
|
||||
remoteSensorManager.start();
|
||||
lastLocation = null;
|
||||
currentSegmentHasLocation = false;
|
||||
isIdle = false;
|
||||
@@ -692,7 +695,7 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
// Update instance variables
|
||||
if (remoteSensorManager != null) {
|
||||
RemoteSensorManagerFactory.releaseSystemSensorManager();
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
lastLocation = null;
|
||||
|
||||
+6
-33
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -29,14 +28,13 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackEditActivity;
|
||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ServiceUtils;
|
||||
|
||||
/**
|
||||
* Wrapper for the track recording service.
|
||||
@@ -154,7 +152,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
return;
|
||||
}
|
||||
|
||||
if (!startIfNeeded && !isRecordingServiceRunning(context)) {
|
||||
if (!startIfNeeded && !ServiceUtils.isTrackRecordingServiceRunning(context)) {
|
||||
Log.d(TAG, "Service is not started. Not binding it.");
|
||||
return;
|
||||
}
|
||||
@@ -169,31 +167,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
context.bindService(new Intent(context, TrackRecordingService.class), this, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the recording service is running.
|
||||
*
|
||||
* @param context the current context
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isRecordingServiceRunning(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (activityManager == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO This approach is deprecated as of API level 26 and should be replaced.
|
||||
List<ActivityManager.RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||
|
||||
for (ActivityManager.RunningServiceInfo serviceInfo : services) {
|
||||
ComponentName componentName = serviceInfo.service;
|
||||
String serviceName = componentName.getClassName();
|
||||
if (TrackRecordingService.class.getName().equals(serviceName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the track recording service connection.
|
||||
*
|
||||
@@ -201,7 +174,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
*/
|
||||
public void startConnection(@NonNull Context context) {
|
||||
bindIfStarted();
|
||||
if (!isRecordingServiceRunning(context)) {
|
||||
if (!ServiceUtils.isTrackRecordingServiceRunning(context)) {
|
||||
resetRecordingState(context);
|
||||
}
|
||||
}
|
||||
@@ -230,9 +203,9 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
if (PreferencesUtils.isRecording(context)) {
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
boolean recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
boolean recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
if (!recordingTrackPaused) {
|
||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
PreferencesUtils.defaultRecordingTrackPaused(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +248,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
try {
|
||||
if (showEditor) {
|
||||
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
|
||||
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
||||
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
trackRecordingService.endCurrentTrack();
|
||||
if (PreferencesUtils.isRecording(context)) {
|
||||
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
|
||||
|
||||
+60
-50
@@ -23,9 +23,7 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -34,18 +32,18 @@ import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorState;
|
||||
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||
|
||||
/**
|
||||
* Manages connection to Bluetooth LE heart rate monitor.
|
||||
* TODO: shutdown connection.
|
||||
*/
|
||||
public class BluetoothConnectionManager {
|
||||
|
||||
// Message types sent to handler
|
||||
static final int MESSAGE_DEVICE_NAME = 1;
|
||||
static final int MESSAGE_READ = 2;
|
||||
|
||||
static final String KEY_DEVICE_NAME = "device_name";
|
||||
static final int MESSAGE_CONNECTING = 1;
|
||||
static final int MESSAGE_CONNECTED = 2;
|
||||
static final int MESSAGE_READ = 3;
|
||||
static final int MESSAGE_DISCONNECTED = 4;
|
||||
|
||||
private static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
|
||||
private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
||||
@@ -58,22 +56,36 @@ public class BluetoothConnectionManager {
|
||||
|
||||
private SensorState sensorState;
|
||||
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
private BluetoothDevice bluetoothDevice;
|
||||
|
||||
private BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
gatt.discoverServices();
|
||||
setState(SensorState.CONNECTED);
|
||||
switch (newState) {
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
Log.d(TAG, "Connecting to sensor: " + gatt.getDevice());
|
||||
setState(SensorState.CONNECTING);
|
||||
|
||||
//Inform about status change
|
||||
Message message = handler.obtainMessage(MESSAGE_DEVICE_NAME);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_DEVICE_NAME, gatt.getDevice().getName());
|
||||
message.setData(bundle);
|
||||
handler.sendMessage(message);
|
||||
return;
|
||||
handler.obtainMessage(MESSAGE_CONNECTING, gatt.getDevice().getName()).sendToTarget();
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
Log.d(TAG, "Connected to sensor: " + gatt.getDevice());
|
||||
setState(SensorState.CONNECTED);
|
||||
|
||||
gatt.discoverServices();
|
||||
|
||||
handler.obtainMessage(MESSAGE_CONNECTED, gatt.getDevice().getName()).sendToTarget();
|
||||
break;
|
||||
case BluetoothProfile.STATE_DISCONNECTING:
|
||||
Log.d(TAG, "Disconnecting from sensor: " + gatt.getDevice());
|
||||
setState(SensorState.DISCONNECTING);
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
|
||||
setState(SensorState.DISCONNECTED);
|
||||
|
||||
handler.obtainMessage(MESSAGE_DISCONNECTED, gatt.getDevice().getName()).sendToTarget();
|
||||
}
|
||||
Log.d(TAG, "Could not connect to bluetooth sensor: " + gatt.getDevice());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,10 +103,11 @@ public class BluetoothConnectionManager {
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||
int heartRate = BluetoothLEUtils.parseHeartRate(characteristic);
|
||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
String deviceName = gatt.getDevice().getName();
|
||||
|
||||
Log.d(TAG, "Received heart beat rate: " + heartRate);
|
||||
SensorDataSet sensorDataSet = new SensorDataSet(heartRate, gatt.getDevice().getName(), gatt.getDevice().getAddress());
|
||||
Log.d(TAG, "Received heart beat rate " + deviceName + ": " + heartRate);
|
||||
SensorDataSet sensorDataSet = new SensorDataSet(heartRate, deviceName, gatt.getDevice().getAddress());
|
||||
handler.obtainMessage(MESSAGE_READ, sensorDataSet).sendToTarget();
|
||||
}
|
||||
};
|
||||
@@ -104,45 +117,42 @@ public class BluetoothConnectionManager {
|
||||
*
|
||||
* @param handler a handler for sending messages back to the UI activity
|
||||
*/
|
||||
BluetoothConnectionManager(Context context, Handler handler) {
|
||||
BluetoothConnectionManager(@NonNull Context context, @NonNull BluetoothDevice bluetoothDevice, @NonNull Handler handler) {
|
||||
this.context = context;
|
||||
this.bluetoothDevice = bluetoothDevice;
|
||||
this.handler = handler;
|
||||
this.sensorState = SensorState.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sensor state.
|
||||
*/
|
||||
public synchronized void connect() {
|
||||
if (bluetoothGatt != null) {
|
||||
Log.w(TAG, "Already connected; ignoring.");
|
||||
}
|
||||
|
||||
Log.d(TAG, "Connecting to: " + bluetoothDevice);
|
||||
|
||||
setState(SensorState.CONNECTING);
|
||||
bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
|
||||
}
|
||||
|
||||
public synchronized void disconnect() {
|
||||
if (bluetoothGatt == null) {
|
||||
Log.w(TAG, "Cannot disconnect if not connected.");
|
||||
return;
|
||||
}
|
||||
bluetoothGatt.close();
|
||||
bluetoothGatt = null;
|
||||
}
|
||||
|
||||
public synchronized boolean isSameBluetoothDevice(String address) {
|
||||
return this.bluetoothDevice.getAddress().equals(address);
|
||||
}
|
||||
|
||||
synchronized SensorState getSensorState() {
|
||||
return sensorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sensor state.
|
||||
*
|
||||
* @param sensorState the sensor state
|
||||
*/
|
||||
private synchronized void setState(SensorState sensorState) {
|
||||
this.sensorState = sensorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the bluetooth connection manager.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
//TODO Disconnect
|
||||
setState(SensorState.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a bluetooth device.
|
||||
*
|
||||
* @param bluetoothDevice the bluetooth device
|
||||
*/
|
||||
public synchronized void connect(BluetoothDevice bluetoothDevice) {
|
||||
Log.d(TAG, "connect to: " + bluetoothDevice);
|
||||
|
||||
bluetoothDevice.connectGatt(this.context, false, this.connectCallback);
|
||||
setState(SensorState.CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package de.dennisguse.opentracks.services.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
final class BluetoothLEUtils {
|
||||
|
||||
private BluetoothLEUtils() {
|
||||
}
|
||||
|
||||
static int parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
||||
//DOCUMENTATION https://www.bluetooth.com/specifications/gatt/characteristics/
|
||||
byte[] raw = characteristic.getValue();
|
||||
int index = ((raw[0] & 0x1) == 1) ? 2 : 1;
|
||||
int format = (index == 1) ? BluetoothGattCharacteristic.FORMAT_UINT8 : BluetoothGattCharacteristic.FORMAT_UINT16;
|
||||
return characteristic.getIntValue(format, index);
|
||||
}
|
||||
}
|
||||
+86
-31
@@ -19,26 +19,29 @@ package de.dennisguse.opentracks.services.sensors;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorState;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* Bluetooth sensor manager.
|
||||
* TODO: Handle a BluetoothGatt.STATE_DISCONNECTED
|
||||
* Bluetooth LE sensor manager.
|
||||
* Should only be instantiated once!
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothRemoteSensorManager extends RemoteSensorManager {
|
||||
public class BluetoothRemoteSensorManager {
|
||||
|
||||
public static final long MAX_SENSOR_DATE_SET_AGE_MS = 5000;
|
||||
|
||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||
|
||||
@@ -46,17 +49,22 @@ public class BluetoothRemoteSensorManager extends RemoteSensorManager {
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final BluetoothConnectionManager bluetoothConnectionManager;
|
||||
|
||||
private SensorDataSet sensorDataSet = null;
|
||||
private final SharedPreferences sharedPreferences;
|
||||
|
||||
// Handler that gets information back from the bluetoothConnectionManager
|
||||
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
String toastMessage;
|
||||
switch (message.what) {
|
||||
case BluetoothConnectionManager.MESSAGE_DEVICE_NAME:
|
||||
String deviceName = message.getData().getString(BluetoothConnectionManager.KEY_DEVICE_NAME);
|
||||
case BluetoothConnectionManager.MESSAGE_CONNECTING:
|
||||
//Ignore for now.
|
||||
toastMessage = context.getString(R.string.settings_sensor_connecting, message.obj);
|
||||
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_CONNECTED:
|
||||
toastMessage = context.getString(R.string.settings_sensor_connected, message.obj);
|
||||
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_READ:
|
||||
if (!(message.obj instanceof SensorDataSet)) {
|
||||
@@ -66,18 +74,44 @@ public class BluetoothRemoteSensorManager extends RemoteSensorManager {
|
||||
sensorDataSet = (SensorDataSet) message.obj;
|
||||
}
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_DISCONNECTED:
|
||||
toastMessage = context.getString(R.string.settings_sensor_disconnected, message.obj);
|
||||
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Got an undefined case. Please check.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_sensor_key, key)) {
|
||||
String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (bluetoothConnectionManager.isSameBluetoothDevice(address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect();
|
||||
startCurrentSensor();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private SensorDataSet sensorDataSet = null;
|
||||
private BluetoothConnectionManager bluetoothConnectionManager;
|
||||
|
||||
/**
|
||||
* @param context the context
|
||||
*/
|
||||
BluetoothRemoteSensorManager(Context context) {
|
||||
public BluetoothRemoteSensorManager(Context context) {
|
||||
this.context = context;
|
||||
bluetoothConnectionManager = new BluetoothConnectionManager(context, messageHandler);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
}
|
||||
|
||||
private static BluetoothAdapter getDefaultBluetoothAdapter() {
|
||||
@@ -117,47 +151,68 @@ public class BluetoothRemoteSensorManager extends RemoteSensorManager {
|
||||
return adapters.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
startCurrentSensor();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
disconnect();
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
|
||||
public boolean isEnabled() {
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUpChannel() {
|
||||
public SensorDataSet getSensorDataSet() {
|
||||
return sensorDataSet;
|
||||
}
|
||||
|
||||
public boolean isSensorDataSetValid() {
|
||||
SensorDataSet sensorDataSet = getSensorDataSet();
|
||||
if (sensorDataSet == null) {
|
||||
return false;
|
||||
}
|
||||
return sensorDataSet.isRecent(MAX_SENSOR_DATE_SET_AGE_MS);
|
||||
}
|
||||
|
||||
private void startCurrentSensor() {
|
||||
if (!isEnabled()) {
|
||||
Log.w(TAG, "Bluetooth not enabled.");
|
||||
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_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
|
||||
Log.w(TAG, "No bluetooth address.");
|
||||
return;
|
||||
}
|
||||
Log.w(TAG, "Connecting to bluetooth address: " + address);
|
||||
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
||||
|
||||
BluetoothDevice device;
|
||||
try {
|
||||
device = bluetoothAdapter.getRemoteDevice(address);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(TAG, "Unable to get remote device for: " + address, e);
|
||||
Log.w(TAG, "Unable to get remote device for: " + address, e);
|
||||
|
||||
String toastMessage = context.getString(R.string.sensor_not_known, address);
|
||||
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show();
|
||||
|
||||
return;
|
||||
}
|
||||
bluetoothConnectionManager.connect(device);
|
||||
|
||||
disconnect();
|
||||
|
||||
bluetoothConnectionManager = new BluetoothConnectionManager(context, device, messageHandler);
|
||||
bluetoothConnectionManager.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDownChannel() {
|
||||
bluetoothConnectionManager.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SensorState getSensorState() {
|
||||
return bluetoothConnectionManager.getSensorState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SensorDataSet getSensorDataSet() {
|
||||
return sensorDataSet;
|
||||
private void disconnect() {
|
||||
if (bluetoothConnectionManager != null) {
|
||||
bluetoothConnectionManager.disconnect();
|
||||
bluetoothConnectionManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.services.sensors;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorState;
|
||||
|
||||
/**
|
||||
* Manage the connection to a remote sensor.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public abstract class RemoteSensorManager {
|
||||
|
||||
public static final long MAX_SENSOR_DATE_SET_AGE = 5000;
|
||||
public static final long MAX_SENSOR_STATE_AGE = 20000;
|
||||
|
||||
private static final String TAG = RemoteSensorManager.class.getSimpleName();
|
||||
private static final int RETRY_PERIOD = 20000;
|
||||
|
||||
private SensorState sensorState = SensorState.NONE;
|
||||
private long sensorStateTimestamp = System.currentTimeMillis();
|
||||
|
||||
private TimerTask timerTask;
|
||||
private Timer timer;
|
||||
|
||||
/**
|
||||
* Returns true if the sensor is enabled.
|
||||
*/
|
||||
public abstract boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Sets up the sensor channel.
|
||||
*/
|
||||
protected abstract void setUpChannel();
|
||||
|
||||
/**
|
||||
* Tears down the sensor channel.
|
||||
*/
|
||||
protected abstract void tearDownChannel();
|
||||
|
||||
/**
|
||||
* Gets the sensor data set.
|
||||
*/
|
||||
public abstract SensorDataSet getSensorDataSet();
|
||||
|
||||
/**
|
||||
* Starts the sensor.
|
||||
*/
|
||||
public void startSensor() {
|
||||
setUpChannel();
|
||||
timerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
switch (getSensorState()) {
|
||||
case CONNECTING:
|
||||
if (System.currentTimeMillis() - sensorStateTimestamp > MAX_SENSOR_STATE_AGE) {
|
||||
Log.i(TAG, "Retry setUpChannel");
|
||||
setUpChannel();
|
||||
}
|
||||
break;
|
||||
case DISCONNECTED:
|
||||
setUpChannel();
|
||||
break;
|
||||
default: // NONE, CONNECTED or SENDING
|
||||
}
|
||||
}
|
||||
};
|
||||
timer = new Timer(RemoteSensorManager.class.getSimpleName());
|
||||
timer.schedule(timerTask, RETRY_PERIOD, RETRY_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the sensor.
|
||||
*/
|
||||
public void stopSensor() {
|
||||
if (timerTask != null) {
|
||||
timerTask.cancel();
|
||||
timerTask = null;
|
||||
}
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
}
|
||||
tearDownChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sensor state.
|
||||
*/
|
||||
public SensorState getSensorState() {
|
||||
return sensorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sensor state.
|
||||
*
|
||||
* @param sensorState the sensor state
|
||||
*/
|
||||
public void setSensorState(SensorState sensorState) {
|
||||
sensorStateTimestamp = System.currentTimeMillis();
|
||||
this.sensorState = sensorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the sensor data set is valid.
|
||||
*/
|
||||
public boolean isSensorDataSetValid() {
|
||||
SensorDataSet sensorDataSet = getSensorDataSet();
|
||||
if (sensorDataSet == null) {
|
||||
return false;
|
||||
}
|
||||
return sensorDataSet.isRecent(MAX_SENSOR_DATE_SET_AGE);
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +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 RemoteSensorManager}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class RemoteSensorManagerFactory {
|
||||
|
||||
private static RemoteSensorManager remoteSensorManagerSystem = null;
|
||||
|
||||
//TODO Check if still needed? Is there a missing features?
|
||||
private static RemoteSensorManager remoteSensorManagerTemporary = null;
|
||||
|
||||
private RemoteSensorManagerFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the system sensor manager.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static RemoteSensorManager getSystemSensorManager(Context context) {
|
||||
releaseSensorManagerTemporary();
|
||||
releaseSystemSensorManager();
|
||||
remoteSensorManagerSystem = getSensorManager(context);
|
||||
remoteSensorManagerSystem.startSensor();
|
||||
return remoteSensorManagerSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the system sensor manager.
|
||||
*/
|
||||
public static void releaseSystemSensorManager() {
|
||||
if (remoteSensorManagerSystem != null) {
|
||||
remoteSensorManagerSystem.stopSensor();
|
||||
}
|
||||
remoteSensorManagerSystem = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the temp sensor manager.
|
||||
*/
|
||||
public static RemoteSensorManager getSensorManagerTemporary(Context context) {
|
||||
releaseSensorManagerTemporary();
|
||||
if (remoteSensorManagerSystem != null) {
|
||||
return null;
|
||||
}
|
||||
remoteSensorManagerTemporary = getSensorManager(context);
|
||||
remoteSensorManagerTemporary.startSensor();
|
||||
return remoteSensorManagerTemporary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the temp sensor manager.
|
||||
*/
|
||||
private static void releaseSensorManagerTemporary() {
|
||||
if (remoteSensorManagerTemporary != null) {
|
||||
remoteSensorManagerTemporary.stopSensor();
|
||||
}
|
||||
remoteSensorManagerTemporary = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sensor manager.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
private static RemoteSensorManager getSensorManager(Context context) {
|
||||
return new BluetoothRemoteSensorManager(context);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.util.HackUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
@@ -45,7 +46,7 @@ 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);
|
||||
String defaultActivity = PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
|
||||
return defaultActivity != null && !defaultActivity.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? defaultActivity : getContext().getString(R.string.value_unknown);
|
||||
}
|
||||
};
|
||||
@@ -57,8 +58,7 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
setNegativeButtonText(android.R.string.cancel);
|
||||
setDialogIcon(null);
|
||||
setPersistent(true);
|
||||
//TODO: for some reason the summary is not updated; after changing it. Enable summaryprovider, when fixed.
|
||||
// setSummaryProvider(summaryProvider);
|
||||
setSummaryProvider(summaryProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +87,7 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
final Context context = getActivity();
|
||||
|
||||
textView = view.findViewById(R.id.activity_type_preference_text_view);
|
||||
String category = PreferencesUtils.getString(context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
|
||||
String category = PreferencesUtils.getDefaultActivity(context);
|
||||
textView.setText(category);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
textView.setAdapter(adapter);
|
||||
@@ -132,7 +132,7 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
}
|
||||
|
||||
private void showIconSelectDialog() {
|
||||
String category = PreferencesUtils.getString(getActivity(), R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
|
||||
String category = PreferencesUtils.getDefaultActivity(getActivity());
|
||||
ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
|
||||
}
|
||||
|
||||
@@ -141,7 +141,8 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
if (positiveResult) {
|
||||
String newDefaultActivity = textView.getText().toString();
|
||||
if (getPreference().callChangeListener(newDefaultActivity)) {
|
||||
PreferencesUtils.setString(getActivity(), R.string.default_activity_key, newDefaultActivity);
|
||||
PreferencesUtils.setDefaultActivity(getActivity(), newDefaultActivity);
|
||||
HackUtils.invalidatePreference(getPreference());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.BluetoothDeviceUtils;
|
||||
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -168,15 +168,12 @@ final class PreferenceHelper {
|
||||
static void setAutoResumeTrackTimeoutOptions(Context context, String[] options, String[] values) {
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
int value = Integer.parseInt(values[i]);
|
||||
switch (value) {
|
||||
case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER:
|
||||
options[i] = context.getString(R.string.value_never);
|
||||
break;
|
||||
case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS:
|
||||
options[i] = context.getString(R.string.value_always);
|
||||
break;
|
||||
default:
|
||||
options[i] = context.getString(R.string.value_integer_minute, value);
|
||||
if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
||||
options[i] = context.getString(R.string.value_never);
|
||||
} else if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
||||
options[i] = context.getString(R.string.value_always);
|
||||
} else {
|
||||
options[i] = context.getString(R.string.value_integer_minute, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,18 +184,18 @@ final class PreferenceHelper {
|
||||
static void configureBluetoothSensorList(ListPreference preference) {
|
||||
Context context = preference.getContext();
|
||||
|
||||
String value = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||
String value = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||
List<String> devicesNameList = new ArrayList<>();
|
||||
List<String> devicesAddressList = new ArrayList<>();
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bluetoothAdapter != null) {
|
||||
BluetoothDeviceUtils.populateDeviceLists(bluetoothAdapter, devicesNameList, devicesAddressList);
|
||||
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);
|
||||
PreferencesUtils.setString(context, R.string.settings_sensor_bluetooth_sensor_key, value);
|
||||
}
|
||||
|
||||
devicesNameList.add(0, context.getString(R.string.value_none));
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.preference.DialogPreference;
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
@@ -17,6 +18,10 @@ public class ResetDialogPreference extends DialogPreference {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
interface ResetCallback {
|
||||
void onReset();
|
||||
}
|
||||
|
||||
public static class ResetPreferenceDialog extends PreferenceDialogFragmentCompat {
|
||||
|
||||
static PreferenceDialogFragmentCompat newInstance(String preferenceKey) {
|
||||
@@ -31,9 +36,14 @@ public class ResetDialogPreference extends DialogPreference {
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
if (positiveResult) {
|
||||
PreferencesUtils.resetPreferences(getActivity(), true);
|
||||
FragmentActivity activity = getActivity();
|
||||
|
||||
Toast.makeText(getActivity(), R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
|
||||
PreferencesUtils.resetPreferences(activity, true);
|
||||
Toast.makeText(activity, R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
|
||||
|
||||
if (activity instanceof ResetCallback) {
|
||||
((ResetCallback) activity).onReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.settings;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
@@ -12,12 +13,13 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.util.HackUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.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 PrefsFragment prefsFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -29,7 +31,7 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
toolbar.setTitle(R.string.menu_settings);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit();
|
||||
onReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,6 +39,12 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
prefsFragment.setDefaultActivity(iconValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset() {
|
||||
prefsFragment = new PrefsFragment();
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit();
|
||||
}
|
||||
|
||||
public static class PrefsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
/*
|
||||
@@ -53,7 +61,7 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
}
|
||||
});
|
||||
|
||||
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() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -69,7 +77,15 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||
try {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||
} catch (ClassCastException e) {
|
||||
// Some sharedPreference is broken: delete all and reset them; it is just a last resort...
|
||||
Toast.makeText(getContext(), R.string.settings_error_initial_values_restored, Toast.LENGTH_LONG).show();
|
||||
PreferencesUtils.resetPreferences(getContext(), true);
|
||||
|
||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||
}
|
||||
updateUnits();
|
||||
}
|
||||
|
||||
@@ -94,7 +110,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -155,13 +170,14 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
|
||||
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
ListPreference bluetoothPreference = findPreference(getString(R.string.bluetooth_sensor_key));
|
||||
ListPreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_sensor_key));
|
||||
PreferenceHelper.configureBluetoothSensorList(bluetoothPreference);
|
||||
}
|
||||
|
||||
private void updateUnits() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity());
|
||||
|
||||
//TODO Refactor this!
|
||||
configFrequencyPreference(R.string.voice_frequency_key, metricUnits);
|
||||
configFrequencyPreference(R.string.split_frequency_key, metricUnits);
|
||||
configListPreference(R.string.min_recording_interval_key, R.array.min_recording_interval_values, metricUnits);
|
||||
@@ -174,9 +190,8 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
final ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
|
||||
String[] options = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
|
||||
statsRatePreferences.setEntries(options);
|
||||
//TODO This is a hack!!! Need to manually updated the summary as otherwise it will only be updated after scrolling down and up again (bring the object out of view).
|
||||
//Check if this is still needed after upgraded PreferenceFragment.
|
||||
statsRatePreferences.setSummary(statsRatePreferences.getEntry());
|
||||
|
||||
HackUtils.invalidatePreference(statsRatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.util;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -27,9 +28,9 @@ import java.util.Set;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class BluetoothDeviceUtils {
|
||||
public class BluetoothUtils {
|
||||
|
||||
private BluetoothDeviceUtils() {
|
||||
private BluetoothUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,4 +54,13 @@ public class BluetoothDeviceUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
||||
//DOCUMENTATION https://www.bluetooth.com/specifications/gatt/characteristics/
|
||||
byte[] raw = characteristic.getValue();
|
||||
int index = ((raw[0] & 0x1) == 1) ? 2 : 1;
|
||||
int format = (index == 1) ? BluetoothGattCharacteristic.FORMAT_UINT8 : BluetoothGattCharacteristic.FORMAT_UINT16;
|
||||
return characteristic.getIntValue(format, index);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import androidx.core.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -70,18 +71,30 @@ public class IntentUtils {
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static Intent newShareFileIntent(Context context, long[] trackIds) {
|
||||
if (trackIds.length == 0) {
|
||||
throw new RuntimeException("Need to share at least one track.");
|
||||
}
|
||||
String trackDescription = "";
|
||||
if (trackIds.length == 1) {
|
||||
Track track = ContentProviderUtils.Factory.get(context).getTrack(trackIds[0]);
|
||||
trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, false);
|
||||
}
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
||||
|
||||
return new Intent(Intent.ACTION_SEND)
|
||||
.putExtra(Intent.EXTRA_STREAM, uriAndMime.first)
|
||||
String action = trackIds.length == 1 ? Intent.ACTION_SEND : Intent.ACTION_SEND_MULTIPLE;
|
||||
String mime = "";
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
for (long trackId : trackIds) {
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
||||
uris.add(uriAndMime.first);
|
||||
mime = uriAndMime.second;
|
||||
}
|
||||
|
||||
return new Intent(action)
|
||||
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
|
||||
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription))
|
||||
.setType(uriAndMime.second)
|
||||
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||
.setType(mime)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import de.dennisguse.opentracks.content.Track;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
//TODO Cleanup: rename variables
|
||||
public class LocationUtils {
|
||||
|
||||
private static final String TAG = LocationUtils.class.getSimpleName();
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -35,62 +36,36 @@ 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;
|
||||
@Deprecated
|
||||
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
|
||||
|
||||
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
|
||||
@Deprecated
|
||||
public static final int FREQUENCY_OFF = 0;
|
||||
|
||||
@Deprecated
|
||||
public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200;
|
||||
|
||||
// Values for min_recording_interval_key
|
||||
@Deprecated
|
||||
public static final int MIN_RECORDING_INTERVAL_ADAPT_ACCURACY = -1;
|
||||
@Deprecated
|
||||
public static final int MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = -2;
|
||||
@Deprecated
|
||||
public static final int MIN_RECORDING_INTERVAL_DEFAULT = 0;
|
||||
|
||||
@Deprecated
|
||||
public static final int RECORDING_DISTANCE_INTERVAL_DEFAULT = 10;
|
||||
|
||||
// Values for recording_gps_accuracy
|
||||
@Deprecated
|
||||
public static final int RECORDING_GPS_ACCURACY_DEFAULT = 50;
|
||||
@Deprecated
|
||||
public static final int RECORDING_GPS_ACCURACY_EXCELLENT = 10;
|
||||
@Deprecated
|
||||
public static final int RECORDING_GPS_ACCURACY_POOR = 2000;
|
||||
static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
|
||||
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
|
||||
|
||||
public static final int SPLIT_FREQUENCY_DEFAULT = 0;
|
||||
static final boolean STATS_SHOW_ELEVATION_DEFAULT = false;
|
||||
static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601";
|
||||
private static final String CHART_X_AXIS_DEFAULT = "DISTANCE";
|
||||
public static final String STATS_UNITS_DEFAULT = "METRIC";
|
||||
// Stats
|
||||
private static final String STATS_RATE_DEFAULT = "SPEED";
|
||||
|
||||
public static final boolean SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN = false;
|
||||
|
||||
// Track widget
|
||||
public static final int TRACK_WIDGET_ITEM1_DEFAULT = 3; // moving time
|
||||
public static final int TRACK_WIDGET_ITEM2_DEFAULT = 0; // distance
|
||||
public static final int TRACK_WIDGET_ITEM3_DEFAULT = 1; // total time
|
||||
public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed
|
||||
public static final int VOICE_FREQUENCY_DEFAULT = 0;
|
||||
|
||||
private PreferencesUtils() {
|
||||
}
|
||||
@@ -99,16 +74,63 @@ public class PreferencesUtils {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
|
||||
//NOTE: This is also used to recover from a reboot, but this data should not be exposed to the whole application.
|
||||
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
|
||||
|
||||
public static long getRecordingTrackId(Context context) {
|
||||
return PreferencesUtils.getLong(context, R.string.recording_track_id_key, RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
|
||||
|
||||
public static int getAutoResumeTrackCurrentRetryDefault(Context context) {
|
||||
return PreferencesUtils.getInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
|
||||
}
|
||||
|
||||
public static void resetAutoResumeTrackCurrentRetryDefault(Context context) {
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
|
||||
}
|
||||
|
||||
public static void incrementAutoResumeTrackCurrentRetryDefault(Context context) {
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, getAutoResumeTrackCurrentRetryDefault(context) + 1);
|
||||
}
|
||||
|
||||
|
||||
public static final String DEFAULT_ACTIVITY_DEFAULT = "";
|
||||
|
||||
public static String getDefaultActivity(Context context) {
|
||||
return PreferencesUtils.getString(context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
|
||||
}
|
||||
|
||||
public static void setDefaultActivity(Context context, String newDefaultActivity) {
|
||||
PreferencesUtils.setString(context, R.string.default_activity_key, newDefaultActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a preference key
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
*/
|
||||
public static String getKey(Context context, int keyId) {
|
||||
private static String getKey(Context context, int keyId) {
|
||||
return context.getString(keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares if keyId and key belong to the same shared preference key.
|
||||
*
|
||||
* @param keyId The resource id of the key
|
||||
* @param key
|
||||
* @return true if key == null or key belongs to keyId
|
||||
*/
|
||||
public static boolean isKey(Context context, int keyId, String key) {
|
||||
return key == null || key.equals(PreferencesUtils.getKey(context, keyId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean preference value.
|
||||
*
|
||||
@@ -180,9 +202,9 @@ public class PreferencesUtils {
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
*/
|
||||
public static long getLong(Context context, int keyId) {
|
||||
private static long getLong(Context context, int keyId, long defaultValue) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
return sharedPreferences.getLong(getKey(context, keyId), -1L);
|
||||
return sharedPreferences.getLong(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +253,8 @@ public class PreferencesUtils {
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isMetricUnits(Context context) {
|
||||
return STATS_UNITS_DEFAULT.equals(getString(context, R.string.stats_units_key, STATS_UNITS_DEFAULT));
|
||||
final String STATS_UNIT = context.getString(R.string.stats_units_default);
|
||||
return STATS_UNIT.equals(getString(context, R.string.stats_units_key, STATS_UNIT));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,20 +264,102 @@ public class PreferencesUtils {
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isReportSpeed(Context context) {
|
||||
final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
|
||||
return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if chart x axis is by distance, false if by time.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static int getAutoResumeTrackTimeout(Context context) {
|
||||
final int AUTO_RESUME_TRACK_TIMEOUT = Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default));
|
||||
return PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, AUTO_RESUME_TRACK_TIMEOUT);
|
||||
}
|
||||
|
||||
public static boolean isRecordingTrackPaused(Context context) {
|
||||
final boolean RECORDING_TRACK_PAUSED = context.getResources().getBoolean(R.bool.recording_track_paused_default);
|
||||
return PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
||||
}
|
||||
|
||||
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 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));
|
||||
}
|
||||
|
||||
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, RECORDING_DISTANCE_INTERVAL);
|
||||
}
|
||||
|
||||
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 getRecordingGPSAccuracy(Context context) {
|
||||
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||
return PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
|
||||
}
|
||||
|
||||
public static boolean isRecording(Context context) {
|
||||
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
||||
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
return recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
}
|
||||
|
||||
@@ -263,6 +368,10 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
public static void resetPreferences(Context context, boolean readAgain) {
|
||||
if (readAgain) {
|
||||
// We want to really clear settings now.
|
||||
getSharedPreferences(context).edit().clear().commit();
|
||||
}
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
|
||||
public class ServiceUtils {
|
||||
|
||||
private ServiceUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the recording service is running.
|
||||
*
|
||||
* @param context the current context
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isTrackRecordingServiceRunning(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (activityManager == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO This approach is deprecated as of API level 26 and should be replaced.
|
||||
List<ActivityManager.RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||
|
||||
for (ActivityManager.RunningServiceInfo serviceInfo : services) {
|
||||
ComponentName componentName = serviceInfo.service;
|
||||
String serviceName = componentName.getClassName();
|
||||
if (TrackRecordingService.class.getName().equals(serviceName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
@@ -26,7 +25,7 @@ import android.widget.TextView;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.RemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
/**
|
||||
@@ -71,13 +70,13 @@ public class StatsUtils {
|
||||
}
|
||||
|
||||
// Set elevation
|
||||
boolean showGradeElevation = PreferencesUtils.getBoolean(activity, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_ELEVATION_DEFAULT) && isRecording;
|
||||
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(activity);
|
||||
View elevationContainer = activity.findViewById(R.id.stats_elevation);
|
||||
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showGradeElevation) {
|
||||
double altitude = location != null && location.hasAltitude() ? location.getAltitude() : Double.NaN;
|
||||
Pair<String, String> parts = formatElevation(activity, altitude, metricUnits);
|
||||
Pair<String, String> parts = StringUtils.formatElevation(activity, altitude, metricUnits);
|
||||
|
||||
TextView elevationValue = activity.findViewById(R.id.stats_elevation_current_value);
|
||||
elevationValue.setText(parts.first);
|
||||
@@ -86,7 +85,7 @@ public class StatsUtils {
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = isRecording && PreferencesUtils.getBoolean(activity, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
|
||||
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(activity);
|
||||
|
||||
View coordinateSeparator = activity.findViewById(R.id.stats_coordinate_separator);
|
||||
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
@@ -109,7 +108,7 @@ public class StatsUtils {
|
||||
public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
// heart rate
|
||||
int isVisible = View.VISIBLE;
|
||||
if (!isRecording || PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(PreferencesUtils.getString(activity, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT))) {
|
||||
if (!isRecording || PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(PreferencesUtils.getString(activity, R.string.settings_sensor_bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT))) {
|
||||
isVisible = View.INVISIBLE;
|
||||
}
|
||||
activity.findViewById(R.id.stats_sensor_container).setVisibility(isVisible);
|
||||
@@ -119,9 +118,11 @@ public class StatsUtils {
|
||||
TextView heartRateSensor = activity.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||
String heartRate = activity.getString(R.string.value_unknown);
|
||||
String sensor = activity.getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.hasHeartRate() && sensorDataSet.isRecent(RemoteSensorManager.MAX_SENSOR_DATE_SET_AGE)) {
|
||||
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
|
||||
if (sensorDataSet != null && sensorDataSet.isRecent(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)) {
|
||||
sensor = sensorDataSet.getSensorName();
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
heartRateValue.setText(heartRate);
|
||||
@@ -150,6 +151,7 @@ public class StatsUtils {
|
||||
public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics, String trackIconValue) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
|
||||
boolean isRecording = PreferencesUtils.isRecording(activity);
|
||||
|
||||
// Set total distance
|
||||
{
|
||||
@@ -172,6 +174,7 @@ public class StatsUtils {
|
||||
if (trackIconValue != null) {
|
||||
TrackIconUtils.setIconSpinner(spinner, trackIconValue);
|
||||
}
|
||||
spinner.setEnabled(isRecording);
|
||||
}
|
||||
|
||||
// Set time
|
||||
@@ -240,7 +243,7 @@ public class StatsUtils {
|
||||
|
||||
// Set elevation
|
||||
{
|
||||
boolean showElevation = PreferencesUtils.getBoolean(activity, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_ELEVATION_DEFAULT);
|
||||
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(activity);
|
||||
View gradeElevationSeparator = activity.findViewById(R.id.stats_elevation_separator);
|
||||
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -250,7 +253,7 @@ public class StatsUtils {
|
||||
if (showElevation) {
|
||||
{
|
||||
double elevation = tripStatistics == null ? Double.NaN : tripStatistics.getMinElevation();
|
||||
Pair<String, String> parts = formatElevation(activity, elevation, metricUnits);
|
||||
Pair<String, String> parts = StringUtils.formatElevation(activity, elevation, metricUnits);
|
||||
|
||||
TextView elevationValue = activity.findViewById(R.id.stats_elevation_min_value);
|
||||
elevationValue.setText(parts.first);
|
||||
@@ -260,7 +263,7 @@ public class StatsUtils {
|
||||
|
||||
{
|
||||
double elevation = tripStatistics == null ? Double.NaN : tripStatistics.getMaxElevation();
|
||||
Pair<String, String> parts = formatElevation(activity, elevation, metricUnits);
|
||||
Pair<String, String> parts = StringUtils.formatElevation(activity, elevation, metricUnits);
|
||||
|
||||
TextView elevationValue = activity.findViewById(R.id.stats_elevation_max_value);
|
||||
elevationValue.setText(parts.first);
|
||||
@@ -270,26 +273,4 @@ public class StatsUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an elevation value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param elevation the elevation in meters
|
||||
* @param metricUnits true if metric units
|
||||
* @return the formatted elevation (or null) and it's unit as {@link Pair}
|
||||
*/
|
||||
// TODO Move to StringUtils
|
||||
private static Pair<String, String> formatElevation(Context context, double elevation, boolean metricUnits) {
|
||||
String value = context.getString(R.string.value_unknown);
|
||||
String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
||||
if (!Double.isNaN(elevation) && !Double.isInfinite(elevation)) {
|
||||
if (metricUnits) {
|
||||
value = StringUtils.formatDecimal(elevation);
|
||||
} else {
|
||||
value = StringUtils.formatDecimal(elevation * UnitConversions.M_TO_FT);
|
||||
}
|
||||
}
|
||||
return new Pair<>(value, unit);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
@@ -124,13 +125,20 @@ public class StringUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static String formatDecimal(double value) {
|
||||
private static String formatDecimal(double value) {
|
||||
return StringUtils.formatDecimal(value, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a decimal number while removing trailing zeros of the decimal part (if present).
|
||||
*/
|
||||
static String formatDecimal(double value, int decimalPlaces) {
|
||||
String result = String.format(Locale.getDefault(), "%1$,." + decimalPlaces + "f", value);
|
||||
return result.replaceAll("[0]*$", "").replaceAll("\\.$", "");
|
||||
if (decimalPlaces < 1) {
|
||||
return Long.toString(Math.round(value));
|
||||
}
|
||||
|
||||
String format = "#." + new String(new char[decimalPlaces]).replace("\0", "#");
|
||||
return new DecimalFormat(format).format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,4 +369,25 @@ public class StringUtils {
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an elevation value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param elevation the elevation in meters
|
||||
* @param metricUnits true if metric units
|
||||
* @return the formatted elevation (or null) and it's unit as {@link Pair}
|
||||
*/
|
||||
public static Pair<String, String> formatElevation(Context context, double elevation, boolean metricUnits) {
|
||||
String value = context.getString(R.string.value_unknown);
|
||||
String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
||||
if (!Double.isNaN(elevation) && !Double.isInfinite(elevation)) {
|
||||
if (metricUnits) {
|
||||
value = StringUtils.formatDecimal(elevation);
|
||||
} else {
|
||||
value = StringUtils.formatDecimal(elevation * UnitConversions.M_TO_FT);
|
||||
}
|
||||
}
|
||||
return new Pair<>(value, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.ShareActionProvider;
|
||||
import androidx.core.internal.view.SupportMenuItem;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
|
||||
public class ToolbarUtils {
|
||||
|
||||
private ToolbarUtils() {
|
||||
}
|
||||
|
||||
//TODO Compat is not working as the AbsListView.MultiChoiceModeListener; instantiating it manually using the non-compat is a workaround.
|
||||
@Deprecated
|
||||
public static void setupShareActionProvider(@NonNull Context context, @NonNull MenuItem shareMenuItem, @NonNull long[] trackIds) {
|
||||
android.widget.ShareActionProvider shareActionProvider = new android.widget.ShareActionProvider(context);
|
||||
shareActionProvider.setShareIntent(trackIds.length == 0 ? null : IntentUtils.newShareFileIntent(context, trackIds));
|
||||
shareMenuItem.setActionProvider(shareActionProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shareMenuItem Should be a {@link SupportMenuItem}.
|
||||
*/
|
||||
public static void setupShareActionProviderCompat(@NonNull Context context, @NonNull MenuItem shareMenuItem, @NonNull long[] trackIds) {
|
||||
ShareActionProvider shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareMenuItem);
|
||||
shareActionProvider.setShareIntent(trackIds.length == 0 ? null : IntentUtils.newShareFileIntent(context, trackIds));
|
||||
MenuItemCompat.setActionProvider(shareMenuItem, shareActionProvider);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class TrackNameUtils {
|
||||
* @param startTime the track start time
|
||||
*/
|
||||
public static String getTrackName(Context context, long trackId, long startTime) {
|
||||
String trackName = PreferencesUtils.getString(context, R.string.track_name_key, PreferencesUtils.TRACK_NAME_DEFAULT);
|
||||
String trackName = PreferencesUtils.getString(context, R.string.track_name_key, context.getString(R.string.track_name_default));
|
||||
|
||||
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
|
||||
return StringUtils.formatDateTime(context, startTime);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
|
||||
</vector>
|
||||
@@ -17,8 +17,8 @@ limitations under the License.
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/list_context_menu_share"
|
||||
android:icon="@drawable/ic_share_24dp"
|
||||
android:title="@string/menu_share"
|
||||
app:actionProviderClass="androidx.appcompat.widget.ShareActionProvider"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/list_context_menu_show_on_map"
|
||||
|
||||
@@ -34,7 +34,7 @@ limitations under the License.
|
||||
android:title="@string/menu_show_on_map" />
|
||||
<item
|
||||
android:id="@+id/track_detail_share"
|
||||
app:actionProviderClass="androidx.appcompat.widget.ShareActionProvider"
|
||||
android:icon="@drawable/ic_share_24dp"
|
||||
android:orderInCategory="4"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_share" />
|
||||
@@ -54,15 +54,6 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_delete_forever_24dp"
|
||||
android:orderInCategory="10"
|
||||
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
|
||||
android:id="@+id/track_detail_settings"
|
||||
android:icon="@drawable/ic_settings_24dp"
|
||||
|
||||
@@ -15,20 +15,68 @@ limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_web_url" translatable="false">https://github.com/dennisguse/opentracks</string>
|
||||
<string name="chart_x_axis_distance" translatable="false">DISTANCE</string>
|
||||
<string name="chart_x_axis_time" translatable="false">TIME</string>
|
||||
|
||||
<!-- List here all used libraries (will be shown in the about activity -->
|
||||
<string name="third_party_libraries" translatable="false"><a href="https://developer.android.com/topic/libraries/support-library/">Google\'s androidX support</a> (Apache License, Version 2.0)</string>
|
||||
|
||||
<string name="marker_statistics_icon_url" translatable="false">http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png</string>
|
||||
<string name="marker_waypoint_icon_url" translatable="false">http://maps.google.com/mapfiles/ms/micons/blue-pushpin.png</string>
|
||||
<string name="sensor_type_value_none" translatable="false">NONE</string>
|
||||
|
||||
<string name="sensor_unit_beats_per_minute" translatable="false">BPM</string>
|
||||
<string name="settings_recording_track_name_date_iso_8601_value" translatable="false">DATE_ISO_8601</string>
|
||||
<string name="settings_recording_track_name_date_local_value" translatable="false">DATE_LOCAL</string>
|
||||
<string name="settings_recording_track_name_number_value" translatable="false">NUMBER</string>
|
||||
<string name="stats_rate_pace" translatable="false">PACE</string>
|
||||
<string name="stats_rate_speed" translatable="false">SPEED</string>
|
||||
<string name="stats_units_imperial" translatable="false">IMPERIAL</string>
|
||||
<string name="stats_units_metric" translatable="false">METRIC</string>
|
||||
|
||||
<string name="value_unknown" translatable="false">-</string>
|
||||
|
||||
<string-array name="waypoint_types">
|
||||
<item>@string/waypoint_type_atm</item>
|
||||
<item>@string/waypoint_type_bank</item>
|
||||
<item>@string/waypoint_type_bar</item>
|
||||
<item>@string/waypoint_type_barbecue</item>
|
||||
<item>@string/waypoint_type_building</item>
|
||||
<item>@string/waypoint_type_bus_stop</item>
|
||||
<item>@string/waypoint_type_cafe</item>
|
||||
<item>@string/waypoint_type_campfire</item>
|
||||
<item>@string/waypoint_type_campground</item>
|
||||
<item>@string/waypoint_type_church</item>
|
||||
<item>@string/waypoint_type_coffee_house</item>
|
||||
<item>@string/waypoint_type_convenience_store</item>
|
||||
<item>@string/waypoint_type_drinking_water</item>
|
||||
<item>@string/waypoint_type_falling_rocks</item>
|
||||
<item>@string/waypoint_type_ferry</item>
|
||||
<item>@string/waypoint_type_fire_place</item>
|
||||
<item>@string/waypoint_type_fire_station</item>
|
||||
<item>@string/waypoint_type_fishing</item>
|
||||
<item>@string/waypoint_type_gas_station</item>
|
||||
<item>@string/waypoint_type_golf_course</item>
|
||||
<item>@string/waypoint_type_grocery_store</item>
|
||||
<item>@string/waypoint_type_hair_dresser</item>
|
||||
<item>@string/waypoint_type_harbor</item>
|
||||
<item>@string/waypoint_type_hiking_path</item>
|
||||
<item>@string/waypoint_type_hotel</item>
|
||||
<item>@string/waypoint_type_house</item>
|
||||
<item>@string/waypoint_type_information</item>
|
||||
<item>@string/waypoint_type_intersection</item>
|
||||
<item>@string/waypoint_type_mail_box</item>
|
||||
<item>@string/waypoint_type_market</item>
|
||||
<item>@string/waypoint_type_meeting_point</item>
|
||||
<item>@string/waypoint_type_opera</item>
|
||||
<item>@string/waypoint_type_parking</item>
|
||||
<item>@string/waypoint_type_police_station</item>
|
||||
<item>@string/waypoint_type_post_office</item>
|
||||
<item>@string/waypoint_type_pothole</item>
|
||||
<item>@string/waypoint_type_railway_crossing</item>
|
||||
<item>@string/waypoint_type_restaurant</item>
|
||||
<item>@string/waypoint_type_shopping</item>
|
||||
<item>@string/waypoint_type_sight</item>
|
||||
<item>@string/waypoint_type_store</item>
|
||||
<item>@string/waypoint_type_swimming_pool</item>
|
||||
<item>@string/waypoint_type_telephone</item>
|
||||
<item>@string/waypoint_type_theater</item>
|
||||
<item>@string/waypoint_type_toilet</item>
|
||||
<item>@string/waypoint_type_tourist_information</item>
|
||||
<item>@string/waypoint_type_tree</item>
|
||||
<item>@string/waypoint_type_tunnel</item>
|
||||
<item>@string/waypoint_type_waterfall</item>
|
||||
<item>@string/waypoint_type_water_fountain</item>
|
||||
<item>@string/waypoint_type_waypoint</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -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>
|
||||
+114
-101
@@ -1,20 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Do not translate any arrays in this file. -->
|
||||
<!-- Do not translate anything in this file directly. -->
|
||||
<string name="chart_show_cadence_key" translatable="false">chartShowCadence</string>
|
||||
<bool name="chart_show_cadence_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_elevation_key" translatable="false">chartShowElevation</string>
|
||||
<bool name="chart_show_elevation_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_heart_rate_key" translatable="false">chartShowHeartRate</string>
|
||||
<bool name="chart_show_heart_rate_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_power_key" translatable="false">chartShowPower</string>
|
||||
<bool name="chart_show_power_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_speed_key" translatable="false">chartShowSpeed</string>
|
||||
<bool name="chart_show_speed_default" translatable="false">true</bool>
|
||||
|
||||
<string name="stats_show_coordinate_key" translatable="false">statsShowCoordinate</string>
|
||||
<bool name="stats_show_coordinate_default" translatable="false">false</bool>
|
||||
|
||||
<string name="stats_show_grade_elevation_key" translatable="false">statsShowGradeElevation</string>
|
||||
<bool name="stats_show_grade_elevation_default" translatable="false">false</bool>
|
||||
|
||||
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
|
||||
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
|
||||
|
||||
<string name="settings_reset_key" translatable="false">settingsReset</string>
|
||||
|
||||
<string name="settings_sensor_bluetooth_sensor_key" translatable="false">bluetoothSensor</string>
|
||||
<string name="settings_sensor_bluetooth_pairing_key" translatable="false">settingsSensorBluetoothPairing</string>
|
||||
<string name="sensor_type_value_none" translatable="false">NONE</string>
|
||||
|
||||
<string name="default_activity_key" translatable="false">defaultActivity</string>
|
||||
<string-array name="activity_types">
|
||||
<item>@string/activity_type_airplane</item>
|
||||
<item>@string/activity_type_atv</item>
|
||||
@@ -83,23 +100,39 @@ limitations under the License.
|
||||
<item>@string/activity_type_water_skiing</item>
|
||||
<item>@string/activity_type_wind_surfing</item>
|
||||
</string-array>
|
||||
|
||||
<string name="auto_resume_track_timeout_key" translatable="false">autoResumeTrackTimeout</string>
|
||||
<string name="auto_resume_track_timeout_default" translatable="false">10</string>
|
||||
<string name="auto_resume_track_timeout_always" translatable="false">-1</string>
|
||||
<string name="auto_resume_track_timeout_never" translatable="false">0</string>
|
||||
<string-array name="auto_resume_track_timeout_values">
|
||||
<item>0</item>
|
||||
<item>@string/auto_resume_track_timeout_never</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>@string/auto_resume_track_timeout_default</item>
|
||||
<item>15</item>
|
||||
<item>-1</item>
|
||||
</string-array>
|
||||
<string-array name="chart_x_axis_options">
|
||||
<item>@string/settings_chart_by_distance</item>
|
||||
<item>@string/settings_chart_by_time</item>
|
||||
<item>@string/auto_resume_track_timeout_always</item>
|
||||
</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">
|
||||
<item>@string/chart_x_axis_distance</item>
|
||||
<item>@string/chart_x_axis_time</item>
|
||||
</string-array>
|
||||
<string name="chart_x_axis_time" translatable="false">TIME</string>
|
||||
<string name="chart_x_axis_distance" translatable="false">DISTANCE</string>
|
||||
<string-array name="chart_x_axis_options">
|
||||
<item>@string/settings_chart_by_distance</item>
|
||||
<item>@string/settings_chart_by_time</item>
|
||||
</string-array>
|
||||
|
||||
<string name="split_frequency_key" translatable="false">splitFrequency</string>
|
||||
<string name="split_frequency_default" translatable="false">@string/frequency_default</string>
|
||||
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
|
||||
<string name="voice_frequency_default" translatable="false">@string/frequency_default</string>
|
||||
<string name="frequency_default" translatable="false">0</string>
|
||||
<string-array name="frequency_values">
|
||||
<item>0</item>
|
||||
<item>@string/frequency_default</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>5</item>
|
||||
@@ -114,18 +147,38 @@ limitations under the License.
|
||||
<item>-50</item>
|
||||
<item>-100</item>
|
||||
</string-array>
|
||||
|
||||
<string name="recording_distance_interval_key" translatable="false">recordingDistanceInterval</string>
|
||||
<string name="recording_distance_interval_default" translatable="false">10</string>
|
||||
<string-array name="recording_distance_interval_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>5</item>
|
||||
<item>@string/recording_distance_interval_default</item>
|
||||
<item>20</item>
|
||||
<item>50</item>
|
||||
<item>100</item>
|
||||
</string-array>
|
||||
|
||||
<string name="max_recording_distance_key" translatable="false">maxRecordingDistance</string>
|
||||
<string name="max_recording_distance_default" translatable="false">200</string>
|
||||
<string-array name="max_recording_distance_values">
|
||||
<item>50</item>
|
||||
<item>100</item>
|
||||
<item>200</item>
|
||||
<item>@string/max_recording_distance_default</item>
|
||||
<item>500</item>
|
||||
<item>1000</item>
|
||||
<item>5000</item>
|
||||
</string-array>
|
||||
|
||||
<string name="min_recording_interval_key" translatable="false">minRecordingInterval</string>
|
||||
<string name="min_recording_interval_default" translatable="false">0</string>
|
||||
<string name="min_recording_interval_adapt_accuracy" translatable="false">-1</string>
|
||||
<string name="min_recording_interval_adapt_battery_life" translatable="false">-2</string>
|
||||
<string-array name="min_recording_interval_values">
|
||||
<item>-2</item>
|
||||
<item>-1</item>
|
||||
<item>0</item>
|
||||
<item>@string/min_recording_interval_adapt_battery_life</item>
|
||||
<item>@string/min_recording_interval_adapt_accuracy</item>
|
||||
<item>@string/min_recording_interval_default</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
@@ -140,26 +193,25 @@ limitations under the License.
|
||||
<item>900</item>
|
||||
<item>1800</item>
|
||||
</string-array>
|
||||
<string-array name="recording_distance_interval_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>20</item>
|
||||
<item>50</item>
|
||||
<item>100</item>
|
||||
</string-array>
|
||||
|
||||
<string name="recording_gps_accuracy_key" translatable="false">recordingGpsAccuracy</string>
|
||||
<string name="recording_gps_accuracy_default" translatable="false">50</string>
|
||||
<string name="recording_gps_accuracy_excellent" translatable="false">10</string>
|
||||
<string name="recording_gps_accuracy_poor" translatable="false">2000</string>
|
||||
<string-array name="recording_gps_accuracy_values">
|
||||
<item>10</item>
|
||||
<item>@string/recording_gps_accuracy_excellent</item>
|
||||
<item>20</item>
|
||||
<item>30</item>
|
||||
<item>50</item>
|
||||
<item>@string/recording_gps_accuracy_default</item>
|
||||
<item>100</item>
|
||||
<item>200</item>
|
||||
<item>500</item>
|
||||
<item>1000</item>
|
||||
<item>2000</item>
|
||||
<item>@string/recording_gps_accuracy_poor</item>
|
||||
</string-array>
|
||||
|
||||
<string name="stats_rate_key" translatable="false">statsRate</string>
|
||||
<string name="stats_rate_default" translatable="false">@string/stats_rate_speed</string>
|
||||
<string-array name="stats_rate_imperial_options">
|
||||
<item>@string/description_speed_imperial</item>
|
||||
<item>@string/description_pace_imperial</item>
|
||||
@@ -168,79 +220,40 @@ limitations under the License.
|
||||
<item>@string/description_speed_metric</item>
|
||||
<item>@string/description_pace_metric</item>
|
||||
</string-array>
|
||||
|
||||
<string name="stats_rate_pace" translatable="false">PACE</string>
|
||||
<string name="stats_rate_speed" translatable="false">SPEED</string>
|
||||
<string-array name="stats_rate_values">
|
||||
<item>@string/stats_rate_speed</item>
|
||||
<item>@string/stats_rate_pace</item>
|
||||
</string-array>
|
||||
|
||||
<string name="stats_units_key" translatable="false">statsUnits</string>
|
||||
<string name="stats_units_default" translatable="false">@string/stats_units_metric</string>
|
||||
<string-array name="stats_units_values">
|
||||
<item>@string/stats_units_metric</item>
|
||||
<item>@string/stats_units_imperial</item>
|
||||
</string-array>
|
||||
<string name="stats_units_metric" translatable="false">METRIC</string>
|
||||
<string name="stats_units_imperial" translatable="false">IMPERIAL</string>
|
||||
<string-array name="stats_units_options">
|
||||
<item>@string/settings_stats_units_metric</item>
|
||||
<item>@string/settings_stats_units_imperial</item>
|
||||
</string-array>
|
||||
<string-array name="stats_units_values">
|
||||
<item>@string/stats_units_metric</item>
|
||||
<item>@string/stats_units_imperial</item>
|
||||
|
||||
<string name="track_name_key" translatable="false">trackName</string>
|
||||
<string name="track_name_default" translatable="false">@string/settings_recording_track_name_date_iso_8601_value</string>
|
||||
<string name="settings_recording_track_name_date_iso_8601_value" translatable="false">DATE_ISO_8601</string>
|
||||
<string name="settings_recording_track_name_date_local_value" translatable="false">DATE_LOCAL</string>
|
||||
<string name="settings_recording_track_name_number_value" translatable="false">NUMBER</string>
|
||||
<string-array name="track_name_values">
|
||||
<item>@string/settings_recording_track_name_date_local_value</item>
|
||||
<item>@string/settings_recording_track_name_date_iso_8601_value</item>
|
||||
<item>@string/settings_recording_track_name_number_value</item>
|
||||
</string-array>
|
||||
<string-array name="track_name_options">
|
||||
<item>@string/settings_recording_track_name_date_local_option</item>
|
||||
<item>@string/settings_recording_track_name_date_iso_8601_option</item>
|
||||
<item>@string/settings_recording_track_name_number_option</item>
|
||||
</string-array>
|
||||
<string-array name="track_name_values">
|
||||
<item>@string/settings_recording_track_name_date_local_value</item>
|
||||
<item>@string/settings_recording_track_name_date_iso_8601_value</item>
|
||||
<item>@string/settings_recording_track_name_number_value</item>
|
||||
</string-array>
|
||||
<string-array name="waypoint_types">
|
||||
<item>@string/waypoint_type_atm</item>
|
||||
<item>@string/waypoint_type_bank</item>
|
||||
<item>@string/waypoint_type_bar</item>
|
||||
<item>@string/waypoint_type_barbecue</item>
|
||||
<item>@string/waypoint_type_building</item>
|
||||
<item>@string/waypoint_type_bus_stop</item>
|
||||
<item>@string/waypoint_type_cafe</item>
|
||||
<item>@string/waypoint_type_campfire</item>
|
||||
<item>@string/waypoint_type_campground</item>
|
||||
<item>@string/waypoint_type_church</item>
|
||||
<item>@string/waypoint_type_coffee_house</item>
|
||||
<item>@string/waypoint_type_convenience_store</item>
|
||||
<item>@string/waypoint_type_drinking_water</item>
|
||||
<item>@string/waypoint_type_falling_rocks</item>
|
||||
<item>@string/waypoint_type_ferry</item>
|
||||
<item>@string/waypoint_type_fire_place</item>
|
||||
<item>@string/waypoint_type_fire_station</item>
|
||||
<item>@string/waypoint_type_fishing</item>
|
||||
<item>@string/waypoint_type_gas_station</item>
|
||||
<item>@string/waypoint_type_golf_course</item>
|
||||
<item>@string/waypoint_type_grocery_store</item>
|
||||
<item>@string/waypoint_type_hair_dresser</item>
|
||||
<item>@string/waypoint_type_harbor</item>
|
||||
<item>@string/waypoint_type_hiking_path</item>
|
||||
<item>@string/waypoint_type_hotel</item>
|
||||
<item>@string/waypoint_type_house</item>
|
||||
<item>@string/waypoint_type_information</item>
|
||||
<item>@string/waypoint_type_intersection</item>
|
||||
<item>@string/waypoint_type_mail_box</item>
|
||||
<item>@string/waypoint_type_market</item>
|
||||
<item>@string/waypoint_type_meeting_point</item>
|
||||
<item>@string/waypoint_type_opera</item>
|
||||
<item>@string/waypoint_type_parking</item>
|
||||
<item>@string/waypoint_type_police_station</item>
|
||||
<item>@string/waypoint_type_post_office</item>
|
||||
<item>@string/waypoint_type_pothole</item>
|
||||
<item>@string/waypoint_type_railway_crossing</item>
|
||||
<item>@string/waypoint_type_restaurant</item>
|
||||
<item>@string/waypoint_type_shopping</item>
|
||||
<item>@string/waypoint_type_sight</item>
|
||||
<item>@string/waypoint_type_store</item>
|
||||
<item>@string/waypoint_type_swimming_pool</item>
|
||||
<item>@string/waypoint_type_telephone</item>
|
||||
<item>@string/waypoint_type_theater</item>
|
||||
<item>@string/waypoint_type_toilet</item>
|
||||
<item>@string/waypoint_type_tourist_information</item>
|
||||
<item>@string/waypoint_type_tree</item>
|
||||
<item>@string/waypoint_type_tunnel</item>
|
||||
<item>@string/waypoint_type_waterfall</item>
|
||||
<item>@string/waypoint_type_water_fountain</item>
|
||||
<item>@string/waypoint_type_waypoint</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -1054,7 +1054,9 @@ limitations under the License.
|
||||
sending data.">
|
||||
Sending
|
||||
</string>
|
||||
|
||||
<string name="sensor_not_known" translation_description="Error message if sensor is not available in Android (e.g., pairing was removed in Bluetooth Settings).">
|
||||
Sensor %1$s is not known. Please check the settings.
|
||||
</string>
|
||||
<!-- Settings -->
|
||||
<string name="settings_not_while_recording" translation_description="In settings, the summary for an option that is not available during a
|
||||
track recording.">
|
||||
@@ -1217,7 +1219,11 @@ limitations under the License.
|
||||
All settings have been reverted to the default values
|
||||
</string>
|
||||
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_error_initial_values_restored" translation_description="If the ">
|
||||
Sorry, but an error occurred: and the all settings were reset to there default values.
|
||||
</string>
|
||||
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor" translation_description="In the 'Settings' page, the option to change the sensor settings.
|
||||
|
||||
Character limit: ~26 (1 line)">
|
||||
@@ -1271,11 +1277,20 @@ limitations under the License.
|
||||
Character limit: ~23 (1 line)">
|
||||
Bluetooth sensor
|
||||
</string>
|
||||
<string name="settings_sensor_connected" translation_description="A message when a sensor is connected.
|
||||
<string name="settings_sensor_connecting" translation_description="A message when a sensor is connecting.
|
||||
|
||||
%1$s sensor name. E.g., 'Polar Bluetooth'.">
|
||||
Connected to %1$s
|
||||
</string>
|
||||
<string name="settings_sensor_connected" translation_description="A message when a sensor is connected.
|
||||
%1$s sensor name. E.g., 'Polar Bluetooth'.">
|
||||
Connected to %1$s
|
||||
</string>
|
||||
<string name="settings_sensor_disconnected" translation_description="A message when a sensor is disconnected.
|
||||
|
||||
%1$s sensor name. E.g., 'Polar Bluetooth'.">
|
||||
Disconnected from %1$s
|
||||
</string>
|
||||
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate" translation_description="In the 'Stats settings, the option to show latitude and longitude.
|
||||
|
||||
@@ -14,44 +14,45 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:title="@string/menu_settings">
|
||||
|
||||
<PreferenceCategory android:title="@string/track_detail_stats_tab">
|
||||
<ListPreference
|
||||
android:defaultValue="@string/stats_units_metric"
|
||||
android:defaultValue="@string/stats_units_default"
|
||||
android:entries="@array/stats_units_options"
|
||||
android:entryValues="@array/stats_units_values"
|
||||
android:key="@string/stats_units_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_stats_units_title" />
|
||||
android:title="@string/settings_stats_units_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/stats_rate_speed"
|
||||
android:defaultValue="@string/stats_rate_default"
|
||||
android:entries="@array/stats_rate_metric_options"
|
||||
android:entryValues="@array/stats_rate_values"
|
||||
android:key="@string/stats_rate_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_stats_rate_title" />
|
||||
android:title="@string/settings_stats_rate_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_show">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:defaultValue="@bool/stats_show_grade_elevation_default"
|
||||
android:key="@string/stats_show_grade_elevation_key"
|
||||
android:title="@string/settings_stats_grade_elevation" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:defaultValue="@bool/stats_show_coordinate_default"
|
||||
android:key="@string/stats_show_coordinate_key"
|
||||
android:title="@string/settings_stats_coordinate" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/track_detail_chart_tab">
|
||||
<ListPreference
|
||||
android:defaultValue="@string/chart_x_axis_distance"
|
||||
android:defaultValue="@string/chart_x_axis_default"
|
||||
android:entries="@array/chart_x_axis_options"
|
||||
android:entryValues="@array/chart_x_axis_values"
|
||||
android:key="@string/chart_x_axis_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_chart_x_axis" />
|
||||
android:title="@string/settings_chart_x_axis"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/chart_show_elevation_key"
|
||||
@@ -68,73 +69,72 @@ limitations under the License.
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_recording">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/trackdetail_show_on_lockscreen_while_recording_key"
|
||||
android:defaultValue="@bool/stats_show_on_lockscreen_while_recording_default"
|
||||
android:key="@string/stats_show_on_lockscreen_while_recording_key"
|
||||
android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary"
|
||||
android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" />
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:defaultValue="@string/voice_frequency_default"
|
||||
android:entryValues="@array/frequency_values"
|
||||
android:key="@string/voice_frequency_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/menu_voice_frequency" />
|
||||
android:title="@string/menu_voice_frequency"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:defaultValue="@string/split_frequency_default"
|
||||
android:entryValues="@array/frequency_values"
|
||||
android:key="@string/split_frequency_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/menu_split_frequency" />
|
||||
android:title="@string/menu_split_frequency"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/settings_recording_track_name_date_iso_8601_value"
|
||||
android:defaultValue="@string/track_name_default"
|
||||
android:entries="@array/track_name_options"
|
||||
android:entryValues="@array/track_name_values"
|
||||
android:key="@string/track_name_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_recording_track_name_title" />
|
||||
android:title="@string/settings_recording_track_name_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:entryValues="@array/min_recording_interval_values"
|
||||
android:key="@string/min_recording_interval_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_recording_min_recording_interval_title" />
|
||||
android:title="@string/settings_recording_min_recording_interval_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="10"
|
||||
android:defaultValue="@string/recording_distance_interval_default"
|
||||
android:entryValues="@array/recording_distance_interval_values"
|
||||
android:key="@string/recording_distance_interval_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_recording_min_recording_distance_title" />
|
||||
android:title="@string/settings_recording_min_recording_distance_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="200"
|
||||
android:defaultValue="@string/max_recording_distance_default"
|
||||
android:entryValues="@array/max_recording_distance_values"
|
||||
android:key="@string/max_recording_distance_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_recording_max_recording_distance_title" />
|
||||
android:title="@string/settings_recording_max_recording_distance_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="50"
|
||||
android:entryValues="@array/recording_gps_accuracy_values"
|
||||
android:key="@string/recording_gps_accuracy_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_recording_min_required_accuracy_title" />
|
||||
android:title="@string/settings_recording_min_required_accuracy_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="10"
|
||||
android:entries="@array/auto_resume_track_timeout_values"
|
||||
android:entryValues="@array/auto_resume_track_timeout_values"
|
||||
android:key="@string/auto_resume_track_timeout_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_recording_auto_resume_track_timeout_title" />
|
||||
android:title="@string/settings_recording_auto_resume_track_timeout_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<de.dennisguse.opentracks.settings.ActivityTypePreference
|
||||
android:defaultValue="@string/activity_type_running"
|
||||
android:key="@string/default_activity_key"
|
||||
android:title="@string/settings_recording_default_activity_title" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/settings_sensor">
|
||||
<PreferenceCategory android:title="@string/settings_sensor">
|
||||
<ListPreference
|
||||
android:defaultValue="@string/sensor_type_value_none"
|
||||
android:key="@string/bluetooth_sensor_key"
|
||||
android:summary="%s"
|
||||
android:title="@string/settings_sensor_bluetooth_sensor" />
|
||||
android:key="@string/settings_sensor_bluetooth_sensor_key"
|
||||
android:title="@string/settings_sensor_bluetooth_sensor"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<Preference
|
||||
android:key="@string/settings_sensor_bluetooth_pairing_key"
|
||||
android:persistent="false"
|
||||
@@ -159,8 +159,8 @@ limitations under the License.
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_reset">
|
||||
<de.dennisguse.opentracks.settings.ResetDialogPreference
|
||||
android:dialogTitle="@string/settings_reset_confirm_title"
|
||||
android:dialogMessage="@string/settings_reset_confirm_message"
|
||||
android:dialogTitle="@string/settings_reset_confirm_title"
|
||||
android:key="@string/settings_reset_key"
|
||||
android:negativeButtonText="@string/generic_cancel"
|
||||
android:persistent="false"
|
||||
|
||||
Reference in New Issue
Block a user