forked from upstream-mirrors/OpenTracks
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0672c41df | |||
| 4c6601fbc5 | |||
| b99c87d1bd | |||
| 66ffbfe951 | |||
| 8cb01a1027 | |||
| 193e5eccbb | |||
| bf7fc28bb0 | |||
| e35cbc343a | |||
| 1c7b053c8c | |||
| 88cdcd4b47 | |||
| 1432fed8f9 | |||
| a14307b9a4 | |||
| 3bdce5aa08 | |||
| d7526f93b8 | |||
| 8932578b90 | |||
| 631ac9881d | |||
| 78334ed797 | |||
| b06da89654 | |||
| d6c2e78993 | |||
| 67d1ac551d | |||
| dcb6be717d | |||
| f2c04eec1a | |||
| a08311cf43 | |||
| 1e28f3aa86 | |||
| 426af41f2c | |||
| fd08a219f9 | |||
| 25b6aec0e9 | |||
| 3e40209d15 | |||
| 54ff94f905 | |||
| 7a4faf6b0d | |||
| 8922b00a5f | |||
| 702efa8693 | |||
| e7a8c897a3 | |||
| c59d1d87bd | |||
| a706efb8b3 | |||
| e391e83a00 | |||
| 2940bf20d8 | |||
| 081db5a58b |
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Report issues of Opentracks features
|
about: Report issues of OpenTracks features
|
||||||
title: ''
|
title: ''
|
||||||
labels: potential bug
|
labels: potential bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Thanks for your contribution.
|
||||||
|
|
||||||
|
**Describe the pull request**
|
||||||
|
A clear and concise description of what the pull request changes/adds.
|
||||||
|
|
||||||
|
**Link to the the issue**
|
||||||
|
(If available): The link to the issue that this pull request solves.
|
||||||
|
|
||||||
|
**License agreement**
|
||||||
|
By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)).
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RenderSettings">
|
||||||
|
<option name="showDecorations" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -30,6 +30,14 @@ To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://pl
|
|||||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
|
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
<div>
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
||||||
|
</div>
|
||||||
|
|
||||||
## Project history
|
## Project history
|
||||||
|
|
||||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||||
|
|||||||
+7
-7
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +35,14 @@ def getVersionName = { ->
|
|||||||
try {
|
try {
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
commandLine 'git', 'describe', '--tags', '--dirty'
|
commandLine 'git', 'describe', '--tags'
|
||||||
standardOutput = stdout
|
standardOutput = stdout
|
||||||
}
|
}
|
||||||
return stdout.toString().trim()
|
exec {
|
||||||
|
commandLine 'git', 'log', '--format="%h"', '-n 1'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return stdout.toString().replace('\n', '-').replace('"', '').trim()
|
||||||
}
|
}
|
||||||
catch (ignored) {
|
catch (ignored) {
|
||||||
return null
|
return null
|
||||||
@@ -75,10 +79,6 @@ android {
|
|||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
variant.resValue "string", "applicationId", variant.applicationId
|
variant.resValue "string", "applicationId", variant.applicationId
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
disable "AppCompatResource" // TODO Disable complaint to use androidx's SearchView as it is null.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||||
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
||||||
|
|
||||||
Funktionen & Features:
|
Funktionen & Features:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v3.2.6: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Heart rate monitor: scan for available devices
|
||||||
|
- Remove shortcut to Bluetooth settings
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Preferences: moved default of preferences from code to xml
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.7: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Notification show GPS accuracy
|
||||||
|
- Show heads-up notification if GPS accuracy drops beyond threshold
|
||||||
|
- Always show chart by time as well as by distance
|
||||||
|
- Added help (in Settings)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- TrackDetailActivity: disabled non-working landscape mode (temporary)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.8: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Chart: zoom via gestures
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Export via KMZ did not export picture filenames correctly
|
||||||
|
- Sharing multiple tracks did not include KMZ files
|
||||||
|
- MarkerDetailActivity's UI was not shown
|
||||||
|
- Settings crashed if preference of heart rate was not initialized
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
||||||
|
|
||||||
|
Funciones:
|
||||||
|
* Tracker deportivo: haz un seguimiento de tus actividades al aire libre
|
||||||
|
* Marca lugares de interés, incluso agrega fotos a los marcadores
|
||||||
|
* Pulsómetro (compatible con dispositivos Bluetooth LE)
|
||||||
|
* Exporta tus rutas como KML o GPX
|
||||||
|
* Completamente offline, no necesitas internet
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
|
||||||
|
|
||||||
|
Fonctionnalités :
|
||||||
|
|
||||||
|
* Suivi d'activité : enregistre vos activités sportives et de plein air
|
||||||
|
* Photos et repères : ajout de photos et de repères pendant l'enregistrement de l'activité
|
||||||
|
* Rythme cardiaque : support des capteurs Bluetooth LE
|
||||||
|
* Export : export des traces au format KML ou GPX
|
||||||
|
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
|
||||||
+3
-1
@@ -13,7 +13,7 @@
|
|||||||
* License for the specific language governing permissions and limitations under
|
* License for the specific language governing permissions and limitations under
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks.chart;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@@ -23,6 +23,8 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link ChartValueSeries}.
|
* Tests {@link ChartValueSeries}.
|
||||||
*
|
*
|
||||||
+48
-50
@@ -35,21 +35,19 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A unit test for {@link ContentProviderUtilsImpl}.
|
* A unit test for {@link ContentProviderUtils}.
|
||||||
*
|
*
|
||||||
* @author Bartlomiej Niechwiej
|
* @author Bartlomiej Niechwiej
|
||||||
* @author Youtao Liu
|
* @author Youtao Liu
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class CustomContentProviderUtilsImplTest {
|
public class CustomContentProviderUtilsTest {
|
||||||
private static final String NAME_PREFIX = "test name";
|
private static final String NAME_PREFIX = "test name";
|
||||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||||
private static final String TEST_DESC = "Test Desc!";
|
private static final String TEST_DESC = "Test Desc!";
|
||||||
@@ -69,13 +67,13 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
providerUtils.deleteAllTracks(context);
|
providerUtils.deleteAllTracks(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noPoints() {
|
public void testLocationIterator_noPoints() {
|
||||||
testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 0, 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -105,36 +103,36 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noBatchAscending() {
|
public void testLocationIterator_noBatchAscending() {
|
||||||
testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 100, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 50, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noBatchDescending() {
|
public void testLocationIterator_noBatchDescending() {
|
||||||
testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 100, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 50, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_batchAscending() {
|
public void testLocationIterator_batchAscending() {
|
||||||
testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 11, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 25, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_batchDescending() {
|
public void testLocationIterator_batchDescending() {
|
||||||
testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 11, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 25, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_largeTrack() {
|
public void testLocationIterator_largeTrack() {
|
||||||
testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 20000, 2000, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
|
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
|
||||||
long lastPointId = initializeTrack(trackId, numPoints);
|
long lastPointId = initializeTrack(trackId, numPoints);
|
||||||
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
|
((ContentProviderUtils) providerUtils).setDefaultCursorBatchSize(batchSize);
|
||||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@@ -173,7 +171,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
// Load all inserted locations.
|
// Load all inserted locations.
|
||||||
long lastPointId = -1;
|
long lastPointId = -1;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
it.next();
|
||||||
lastPointId = it.getLocationId();
|
lastPointId = it.getLocationId();
|
||||||
@@ -189,7 +187,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createTrack(Cursor)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTrack() {
|
public void testCreateTrack() {
|
||||||
@@ -216,7 +214,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)}
|
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAllTracks() {
|
public void testDeleteAllTracks() {
|
||||||
@@ -245,7 +243,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}.
|
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteTrack() {
|
public void testDeleteTrack() {
|
||||||
@@ -280,7 +278,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getAllTracks()}
|
* Tests the method {@link ContentProviderUtils#getAllTracks()}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetAllTracks() {
|
public void testGetAllTracks() {
|
||||||
@@ -293,7 +291,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getLastTrack()}
|
* Tests the method {@link ContentProviderUtils#getLastTrack()}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetLastTrack() {
|
public void testGetLastTrack() {
|
||||||
@@ -303,7 +301,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrack(long)}
|
* Tests the method {@link ContentProviderUtils#getTrack(long)}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrack() {
|
public void testGetTrack() {
|
||||||
@@ -313,7 +311,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)}
|
* Tests the method {@link ContentProviderUtils#updateTrack(Track)}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateTrack() {
|
public void testUpdateTrack() {
|
||||||
@@ -330,7 +328,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}.
|
* Tests the method {@link ContentProviderUtils#createContentValues(Waypoint)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateContentValues_waypoint() {
|
public void testCreateContentValues_waypoint() {
|
||||||
@@ -368,7 +366,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
waypoint.setLocation(location);
|
waypoint.setLocation(location);
|
||||||
providerUtils.insertWaypoint(waypoint);
|
providerUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
||||||
|
|
||||||
long waypointId = System.currentTimeMillis();
|
long waypointId = System.currentTimeMillis();
|
||||||
waypoint.setId(waypointId);
|
waypoint.setId(waypointId);
|
||||||
@@ -381,7 +379,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createWaypoint(Cursor)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateWaypoint() {
|
public void testCreateWaypoint() {
|
||||||
@@ -416,7 +414,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||||
* when there is only one waypoint in the track.
|
* when there is only one waypoint in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -437,7 +435,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
return CustomContentProviderUtilsTest.MOCK_DESC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -452,7 +450,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||||
* when there is more than one waypoint in the track.
|
* when there is more than one waypoint in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -495,7 +493,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||||
@Override
|
@Override
|
||||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
return CustomContentProviderUtilsTest.MOCK_DESC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -508,12 +506,12 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
|
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
|
||||||
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
|
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
|
||||||
|
|
||||||
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
Assert.assertEquals(CustomContentProviderUtilsTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
||||||
.getDescription());
|
.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}.
|
* Tests the method {@link ContentProviderUtils#getFirstWaypointId(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetFirstWaypointId() {
|
public void testGetFirstWaypointId() {
|
||||||
@@ -535,7 +533,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}.
|
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long, WaypointType)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetNextWaypointNumber() {
|
public void testGetNextWaypointNumber() {
|
||||||
@@ -594,8 +592,8 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and
|
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
|
||||||
* {@link ContentProviderUtilsImpl#getWaypoint(long)}.
|
* {@link ContentProviderUtils#getWaypoint(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInsertAndGetWaypoint() {
|
public void testInsertAndGetWaypoint() {
|
||||||
@@ -612,7 +610,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}.
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint() {
|
public void testUpdateWaypoint() {
|
||||||
@@ -634,7 +632,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], int, long)}.
|
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(Location[], int, long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testBulkInsertTrackPoint() {
|
public void testBulkInsertTrackPoint() {
|
||||||
@@ -650,7 +648,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTrackPoint() {
|
public void testCreateTrackPoint() {
|
||||||
@@ -710,7 +708,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}.
|
* {@link ContentProviderUtils#insertTrackPoint(Location, long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInsertTrackPoint() {
|
public void testInsertTrackPoint() {
|
||||||
@@ -724,7 +722,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}.
|
* Tests the method {@link ContentProviderUtils#getFirstTrackPointId(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetFirstTrackPointId() {
|
public void testGetFirstTrackPointId() {
|
||||||
@@ -737,7 +735,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}.
|
* Tests the method {@link ContentProviderUtils#getLastTrackPointId(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetLastTrackPointId() {
|
public void testGetLastTrackPointId() {
|
||||||
@@ -750,7 +748,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}.
|
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetLastValidTrackPoint() {
|
public void testGetLastValidTrackPoint() {
|
||||||
@@ -764,7 +762,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in descending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_desc() {
|
public void testGetTrackPointCursor_desc() {
|
||||||
@@ -783,7 +781,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_asc() {
|
public void testGetTrackPointCursor_asc() {
|
||||||
@@ -802,7 +800,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_desc() {
|
public void testGetTrackPointLocationIterator_desc() {
|
||||||
@@ -818,7 +816,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
long startTrackPointId = trackpointIds[9];
|
long startTrackPointId = trackpointIds[9];
|
||||||
|
|
||||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
Assert.assertTrue(locationIterator.hasNext());
|
Assert.assertTrue(locationIterator.hasNext());
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
@@ -829,7 +827,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_asc() {
|
public void testGetTrackPointLocationIterator_asc() {
|
||||||
@@ -845,7 +843,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
long startTrackPointId = trackpointIds[0];
|
long startTrackPointId = trackpointIds[0];
|
||||||
|
|
||||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
Assert.assertTrue(locationIterator.hasNext());
|
Assert.assertTrue(locationIterator.hasNext());
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
@@ -892,7 +890,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
/**
|
/**
|
||||||
* Checks the value of a location.
|
* Checks the value of a location.
|
||||||
*
|
*
|
||||||
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsTest#getTrack(long, int)}
|
||||||
* @param location the location to be checked
|
* @param location the location to be checked
|
||||||
*/
|
*/
|
||||||
private void checkLocation(int i, Location location) {
|
private void checkLocation(int i, Location location) {
|
||||||
@@ -58,7 +58,7 @@ public class SearchEngineTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
engine = new SearchEngine(providerUtils);
|
engine = new SearchEngine(providerUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,20 @@
|
|||||||
package de.dennisguse.opentracks.fragments;
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.ChartView;
|
|
||||||
import de.dennisguse.opentracks.TrackStubUtils;
|
import de.dennisguse.opentracks.TrackStubUtils;
|
||||||
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
@@ -44,10 +47,23 @@ public class ChartFragmentTest {
|
|||||||
|
|
||||||
private ChartFragment chartFragment;
|
private ChartFragment chartFragment;
|
||||||
|
|
||||||
|
@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
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
chartFragment = new ChartFragment();
|
boolean chartByDistance = false;
|
||||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext()));
|
chartFragment = new ChartFragment(chartByDistance) {
|
||||||
|
};
|
||||||
|
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||||
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class TrackRecordingServiceNotificationManagerTest {
|
||||||
|
|
||||||
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Location locationMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NotificationCompat.Builder notificationCompatBuilder;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||||
|
when(locationMock.hasAccuracy()).thenReturn(true);
|
||||||
|
when(locationMock.getAccuracy()).thenReturn(999f);
|
||||||
|
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
|
||||||
|
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
|
||||||
|
|
||||||
|
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||||
|
|
||||||
|
// when
|
||||||
|
subject.updateLocation(context, locationMock, 100);
|
||||||
|
subject.updateLocation(context, locationMock, 100);
|
||||||
|
subject.updateLocation(context, locationMock, 1000);
|
||||||
|
subject.updateLocation(context, locationMock, 100);
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||||
|
verify(notificationCompatBuilder, times(2)).setOnlyAlertOnce(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -89,7 +89,7 @@ public class TrackRecordingServiceTest {
|
|||||||
};
|
};
|
||||||
customContentProvider.attachInfo(context, null);
|
customContentProvider.attachInfo(context, null);
|
||||||
|
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
|
|||||||
+5
-3
@@ -68,7 +68,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
};
|
};
|
||||||
customContentProvider.attachInfo(context, null);
|
customContentProvider.attachInfo(context, null);
|
||||||
|
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
@@ -139,7 +139,8 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +203,8 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,10 +60,15 @@ public class FileUtilsTest extends TestCase {
|
|||||||
*/
|
*/
|
||||||
public void testSanitizeFileName() {
|
public void testSanitizeFileName() {
|
||||||
String name = "Swim\10ming-^across:/the/ pacific (ocean).";
|
String name = "Swim\10ming-^across:/the/ pacific (ocean).";
|
||||||
String expected = "Swim_ming-^across_the_ pacific (ocean)_";
|
String expected = "Swim_ming-^across_the_ pacific (ocean).";
|
||||||
assertEquals(expected, FileUtils.sanitizeFileName(name));
|
assertEquals(expected, FileUtils.sanitizeFileName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testSanitizeFileNameWithSuffix() {
|
||||||
|
String name = "FileName.jpeg";
|
||||||
|
assertEquals(name, FileUtils.sanitizeFileName(name));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
|
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
|
||||||
* Verifies that they are allowed.
|
* Verifies that they are allowed.
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ limitations under the License.
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="3123"
|
android:versionCode="3162"
|
||||||
android:versionName="v3.2.5"> <!-- For FDroid. -->
|
android:versionName="v3.2.8"> <!-- For FDroid. -->
|
||||||
<!-- Permission to initialize services -->
|
<!-- Permission to initialize services -->
|
||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
@@ -32,6 +32,10 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name=".DeleteActivity" />
|
<activity android:name=".DeleteActivity" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".HelpActivity"
|
||||||
|
android:label="@string/menu_help" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".io.file.importer.ImportActivity"
|
android:name=".io.file.importer.ImportActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent">
|
android:theme="@style/ThemeCustomTransparent">
|
||||||
@@ -50,10 +54,10 @@ limitations under the License.
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".MarkerEditActivity" />
|
|
||||||
|
|
||||||
<activity android:name=".MarkerDetailActivity" />
|
<activity android:name=".MarkerDetailActivity" />
|
||||||
|
|
||||||
|
<activity android:name=".MarkerEditActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MarkerListActivity"
|
android:name=".MarkerListActivity"
|
||||||
android:label="@string/menu_markers">
|
android:label="@string/menu_markers">
|
||||||
@@ -79,7 +83,9 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name=".TrackEditActivity" />
|
<activity android:name=".TrackEditActivity" />
|
||||||
|
|
||||||
<activity android:name=".TrackListActivity">
|
<activity
|
||||||
|
android:name=".TrackListActivity"
|
||||||
|
android:screenOrientation="userPortrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
@@ -17,11 +17,12 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.util.TTSUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
@@ -32,7 +33,7 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Set volume control stream for text to speech
|
// Set volume control stream for text to speech
|
||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
setVolumeControlStream(TTSUtils.getTTSStream());
|
||||||
|
|
||||||
setContentView(getLayoutResId());
|
setContentView(getLayoutResId());
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||||
contentProviderUtils.deleteAllTracks(context);
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
public class HelpActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLayoutResId() {
|
||||||
|
return R.layout.help;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||||
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
||||||
|
|
||||||
markerIds = new ArrayList<>();
|
markerIds = new ArrayList<>();
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
if (newMarker) {
|
if (newMarker) {
|
||||||
statisticsSection.setVisibility(View.GONE);
|
statisticsSection.setVisibility(View.GONE);
|
||||||
waypointSection.setVisibility(View.VISIBLE);
|
waypointSection.setVisibility(View.VISIBLE);
|
||||||
int nextWaypointNumber = trackId == -1L ? -1 : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
||||||
if (nextWaypointNumber == -1) {
|
if (nextWaypointNumber == -1) {
|
||||||
nextWaypointNumber = 0;
|
nextWaypointNumber = 0;
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointMarkerType.setText("");
|
waypointMarkerType.setText("");
|
||||||
waypointDescription.setText("");
|
waypointDescription.setText("");
|
||||||
} else {
|
} else {
|
||||||
waypoint = ContentProviderUtils.Factory.get(this).getWaypoint(markerId);
|
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
|
||||||
if (waypoint == null) {
|
if (waypoint == null) {
|
||||||
Log.d(TAG, "waypoint is null");
|
Log.d(TAG, "waypoint is null");
|
||||||
finish();
|
finish();
|
||||||
@@ -182,6 +182,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypoint.setCategory(waypointMarkerType.getText().toString());
|
waypoint.setCategory(waypointMarkerType.getText().toString());
|
||||||
waypoint.setDescription(waypointDescription.getText().toString());
|
waypoint.setDescription(waypointDescription.getText().toString());
|
||||||
}
|
}
|
||||||
ContentProviderUtils.Factory.get(this).updateWaypoint(waypoint);
|
new ContentProviderUtils(this).updateWaypoint(waypoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
/*
|
/*
|
||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||||
* class. Anonymous inner class will get garbage collected.
|
* class. Anonymous inner class will get garbage collected.
|
||||||
@@ -128,7 +128,9 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
|
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
|
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
// Callback when an item is selected in the contextual action mode
|
// Callback when an item is selected in the contextual action mode
|
||||||
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||||
@@ -168,7 +168,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||||
searchEngine = new SearchEngine(contentProviderUtils);
|
searchEngine = new SearchEngine(contentProviderUtils);
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ import de.dennisguse.opentracks.content.TrackDataHub;
|
|||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.ChartTimeFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||||
@@ -54,6 +55,7 @@ import de.dennisguse.opentracks.settings.SettingsActivity;
|
|||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StatsUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackUtils;
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
@@ -93,7 +95,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -120,8 +122,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||||
* class. Anonymous inner class will get garbage collected.
|
* class. Anonymous inner class will get garbage collected.
|
||||||
*/
|
*/
|
||||||
private final OnSharedPreferenceChangeListener
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
@@ -182,11 +183,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
||||||
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
||||||
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
@@ -203,8 +206,11 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
|
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
|
||||||
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
|
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
|
||||||
|
|
||||||
TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab));
|
TabSpec chartTimeTabSpec = tabHost.newTabSpec(ChartTimeFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_time));
|
||||||
tabsAdapter.addTab(chartTabSpec, ChartFragment.class, null);
|
tabsAdapter.addTab(chartTimeTabSpec, ChartTimeFragment.class, null);
|
||||||
|
|
||||||
|
TabSpec chartDistanceTabSpec = tabHost.newTabSpec(ChartDistanceFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_distance));
|
||||||
|
tabsAdapter.addTab(chartDistanceTabSpec, ChartDistanceFragment.class, null);
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
|
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
|
||||||
@@ -410,10 +416,16 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
/**
|
/**
|
||||||
* Gets the track id.
|
* Gets the track id.
|
||||||
*/
|
*/
|
||||||
|
// TODO Seems to be no used.
|
||||||
|
@Deprecated
|
||||||
public long getTrackId() {
|
public long getTrackId() {
|
||||||
return trackId;
|
return trackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setBluetoothHeartRateSensorName(String sensorName) {
|
||||||
|
StatsUtils.setBluetoothHeartRateSensorName(this, sensorName, isRecording());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the data in the intent.
|
* Handles the data in the intent.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
track = contentProviderUtils.getTrack(trackId);
|
track = contentProviderUtils.getTrack(trackId);
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
Log.e(TAG, "No track for " + trackId);
|
Log.e(TAG, "No track for " + trackId);
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
|
||||||
@@ -225,8 +225,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Not recording -> Recording
|
// Not recording -> Recording
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
startRecording();
|
startRecording();
|
||||||
} else {
|
} else if (recordingTrackPaused) {
|
||||||
if (recordingTrackPaused) {
|
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
trackRecordingServiceConnection.resumeTrack();
|
trackRecordingServiceConnection.resumeTrack();
|
||||||
@@ -237,7 +236,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
trackRecordingServiceConnection.pauseTrack();
|
trackRecordingServiceConnection.pauseTrack();
|
||||||
trackController.update(true, true);
|
trackController.update(true, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,7 +243,9 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks.chart;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
+88
-97
@@ -14,7 +14,7 @@
|
|||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks.chart;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -27,15 +27,19 @@ import android.graphics.Paint.Style;
|
|||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.VelocityTracker;
|
import android.view.ScaleGestureDetector;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
import android.widget.Scroller;
|
import android.widget.Scroller;
|
||||||
|
|
||||||
|
import androidx.core.view.GestureDetectorCompat;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.MarkerDetailActivity;
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
@@ -44,6 +48,7 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Visualization of the chart.
|
* Visualization of the chart.
|
||||||
|
* Provides support for zooming (via pinch), scrolling, flinging, and selecting shown markers (single touch).
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
@@ -91,8 +96,6 @@ public class ChartView extends View {
|
|||||||
private final int markerHeight;
|
private final int markerHeight;
|
||||||
private final Scroller scroller;
|
private final Scroller scroller;
|
||||||
private double maxX = 1.0;
|
private double maxX = 1.0;
|
||||||
private VelocityTracker velocityTracker = null;
|
|
||||||
private float lastMotionEventX = -1;
|
|
||||||
private int zoomLevel = 1;
|
private int zoomLevel = 1;
|
||||||
|
|
||||||
private int leftBorder = BORDER;
|
private int leftBorder = BORDER;
|
||||||
@@ -107,18 +110,84 @@ public class ChartView extends View {
|
|||||||
private int effectiveWidth = 0;
|
private int effectiveWidth = 0;
|
||||||
private int effectiveHeight = 0;
|
private int effectiveHeight = 0;
|
||||||
|
|
||||||
private boolean chartByDistance = true;
|
private boolean chartByDistance;
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
private boolean reportSpeed = true;
|
private boolean reportSpeed = true;
|
||||||
private boolean showPointer = false;
|
private boolean showPointer = false;
|
||||||
|
|
||||||
/**
|
private GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||||
* Constructor.
|
|
||||||
*
|
@Override
|
||||||
* @param context the context
|
public boolean onDown(MotionEvent e) {
|
||||||
*/
|
if (!scroller.isFinished()) {
|
||||||
public ChartView(Context context) {
|
scroller.abortAnimation();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||||
|
if (Math.abs(distanceX) > 0) {
|
||||||
|
int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
|
||||||
|
if (availableToScroll > 0) {
|
||||||
|
scrollBy(Math.min(availableToScroll, (int) distanceX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||||
|
fling((int) -velocityX);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapConfirmed(MotionEvent event) {
|
||||||
|
// Check if the y event is within markerHeight of the marker center
|
||||||
|
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) {
|
||||||
|
int minDistance = Integer.MAX_VALUE;
|
||||||
|
Waypoint nearestWaypoint = null;
|
||||||
|
synchronized (waypoints) {
|
||||||
|
for (Waypoint waypoint : waypoints) {
|
||||||
|
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
nearestWaypoint = waypoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nearestWaypoint != null && minDistance < markerWidth) {
|
||||||
|
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
|
||||||
|
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId());
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScale(ScaleGestureDetector detector) {
|
||||||
|
float scaleFactor = detector.getScaleFactor();
|
||||||
|
if (scaleFactor >= 1.1f) {
|
||||||
|
zoomIn();
|
||||||
|
return true;
|
||||||
|
} else if (scaleFactor <= 0.9) {
|
||||||
|
zoomOut();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public ChartView(Context context, boolean chartByDistance) {
|
||||||
super(context);
|
super(context);
|
||||||
|
this.chartByDistance = chartByDistance;
|
||||||
|
|
||||||
series[ELEVATION_SERIES] = new ChartValueSeries(context,
|
series[ELEVATION_SERIES] = new ChartValueSeries(context,
|
||||||
Integer.MIN_VALUE,
|
Integer.MIN_VALUE,
|
||||||
@@ -222,16 +291,6 @@ public class ChartView extends View {
|
|||||||
series[index].setEnabled(enabled);
|
series[index].setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets chart by distance.
|
|
||||||
* It is expected that after changing this value, data will be reloaded.
|
|
||||||
*
|
|
||||||
* @param value true for by distance, false for by time
|
|
||||||
*/
|
|
||||||
public void setChartByDistance(boolean value) {
|
|
||||||
chartByDistance = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets metric units.
|
* Sets metric units.
|
||||||
*
|
*
|
||||||
@@ -410,79 +469,14 @@ public class ChartView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
if (velocityTracker == null) {
|
boolean isZoom = detectorZoom.onTouchEvent(event);
|
||||||
velocityTracker = VelocityTracker.obtain();
|
boolean isScrollTab = detectorScrollFlingTab.onTouchEvent(event);
|
||||||
}
|
return isZoom || isScrollTab;
|
||||||
velocityTracker.addMovement(event);
|
|
||||||
float x = event.getX();
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
// Stop the fling
|
|
||||||
if (!scroller.isFinished()) {
|
|
||||||
scroller.abortAnimation();
|
|
||||||
}
|
|
||||||
lastMotionEventX = x;
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
if (lastMotionEventX == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Scroll to follow the motion event
|
|
||||||
int deltaX = (int) (lastMotionEventX - x);
|
|
||||||
lastMotionEventX = x;
|
|
||||||
if (deltaX < 0) {
|
|
||||||
if (getScrollX() > 0) {
|
|
||||||
scrollBy(deltaX);
|
|
||||||
}
|
|
||||||
} else if (deltaX > 0) {
|
|
||||||
int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
|
|
||||||
if (availableToScroll > 0) {
|
|
||||||
scrollBy(Math.min(availableToScroll, deltaX));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
// Check if the y event is within markerHeight of the marker center
|
|
||||||
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) {
|
|
||||||
int minDistance = Integer.MAX_VALUE;
|
|
||||||
Waypoint nearestWaypoint = null;
|
|
||||||
synchronized (waypoints) {
|
|
||||||
for (int i = 0; i < waypoints.size(); i++) {
|
|
||||||
Waypoint waypoint = waypoints.get(i);
|
|
||||||
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
|
||||||
if (distance < minDistance) {
|
|
||||||
minDistance = distance;
|
|
||||||
nearestWaypoint = waypoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nearestWaypoint != null && minDistance < markerWidth) {
|
|
||||||
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
|
|
||||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId());
|
|
||||||
getContext().startActivity(intent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VelocityTracker myVelocityTracker = velocityTracker;
|
|
||||||
myVelocityTracker.computeCurrentVelocity(1000);
|
|
||||||
int initialVelocity = (int) myVelocityTracker.getXVelocity();
|
|
||||||
if (Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) {
|
|
||||||
fling(-initialVelocity);
|
|
||||||
}
|
|
||||||
if (velocityTracker != null) {
|
|
||||||
velocityTracker.recycle();
|
|
||||||
velocityTracker = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
updateEffectiveDimensionsIfChanged(
|
updateEffectiveDimensionsIfChanged(View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec));
|
||||||
View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec));
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,8 +626,7 @@ public class ChartView extends View {
|
|||||||
private String getXAxisLabel() {
|
private String getXAxisLabel() {
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
return metricUnits ? context.getString(R.string.unit_kilometer)
|
return metricUnits ? context.getString(R.string.unit_kilometer) : context.getString(R.string.unit_mile);
|
||||||
: context.getString(R.string.unit_mile);
|
|
||||||
} else {
|
} else {
|
||||||
return context.getString(R.string.description_time);
|
return context.getString(R.string.description_time);
|
||||||
}
|
}
|
||||||
@@ -733,8 +726,7 @@ public class ChartView extends View {
|
|||||||
* @param yValue the y value
|
* @param yValue the y value
|
||||||
* @return the marker width.
|
* @return the marker width.
|
||||||
*/
|
*/
|
||||||
private float drawYAxisMarker(
|
private float drawYAxisMarker(ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) {
|
||||||
ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) {
|
|
||||||
String marker = chartValueSeries.formatMarker(yValue);
|
String marker = chartValueSeries.formatMarker(yValue);
|
||||||
Paint paint = chartValueSeries.getMarkerPaint();
|
Paint paint = chartValueSeries.getMarkerPaint();
|
||||||
Rect rect = getRect(paint, marker);
|
Rect rect = getRect(paint, marker);
|
||||||
@@ -869,8 +861,7 @@ public class ChartView extends View {
|
|||||||
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
||||||
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
||||||
// border + x axis marker + spacer + .5 x axis label
|
// border + x axis marker + spacer + .5 x axis label
|
||||||
bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer
|
bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer + (xAxisLabelRect.height() / 2));
|
||||||
+ (xAxisLabelRect.height() / 2));
|
|
||||||
rightBorder = (int) (density * BORDER + xAxisLabelRect.width() + spacer);
|
rightBorder = (int) (density * BORDER + xAxisLabelRect.width() + spacer);
|
||||||
updateEffectiveDimensions();
|
updateEffectiveDimensions();
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link SensorDataSetLocation}.
|
||||||
|
* An implementation can create new instances or reuse existing instances for optimization.
|
||||||
|
*/
|
||||||
|
public class LocationFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default {@link LocationFactory} which creates a location each time.
|
||||||
|
*/
|
||||||
|
public static LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory();
|
||||||
|
|
||||||
|
public Location createLocation() {
|
||||||
|
return new SensorDataSetLocation(LocationManager.GPS_PROVIDER);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
|
||||||
|
*/
|
||||||
|
public interface LocationIterator extends Iterator<Location>, AutoCloseable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the most recently retrieved track point id by {@link #next()}.
|
||||||
|
*/
|
||||||
|
long getLocationId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the iterator.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
}
|
||||||
@@ -198,7 +198,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtilsImpl(this);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||||
|
|
||||||
long[] trackIds = parseURI(uri);
|
long[] trackIds = parseURI(uri);
|
||||||
final Track[] tracks = new Track[trackIds.length];
|
final Track[] tracks = new Track[trackIds.length];
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import java.util.EnumSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
* Target number of track points displayed by the map overlay.
|
* Target number of track points displayed by the map overlay.
|
||||||
* We may display more than this number of points.
|
* We may display more than this number of points.
|
||||||
*/
|
*/
|
||||||
static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
|
private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of waypoints to displayed.
|
* Maximum number of waypoints to displayed.
|
||||||
@@ -97,8 +96,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static TrackDataHub newInstance(Context context) {
|
public synchronized static TrackDataHub newInstance(Context context) {
|
||||||
return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get(
|
return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
|
||||||
context), TARGET_DISPLAYED_TRACK_POINTS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
@@ -448,7 +446,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
int samplingFrequency = -1;
|
int samplingFrequency = -1;
|
||||||
boolean includeNextPoint = false;
|
boolean includeNextPoint = false;
|
||||||
|
|
||||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
|
|
||||||
while (locationIterator.hasNext()) {
|
while (locationIterator.hasNext()) {
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
|
public class ChartDistanceFragment extends ChartFragment {
|
||||||
|
|
||||||
|
public static final String CHART_FRAGMENT_TAG = ChartDistanceFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
public ChartDistanceFragment() {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,9 +22,9 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
import android.widget.ZoomControls;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
@@ -32,9 +32,9 @@ import androidx.fragment.app.FragmentActivity;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.ChartView;
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||||
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
@@ -54,9 +54,9 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class ChartFragment extends Fragment implements TrackDataListener {
|
public abstract class ChartFragment extends Fragment implements TrackDataListener {
|
||||||
|
|
||||||
public static final String CHART_FRAGMENT_TAG = "chartFragment";
|
private static final String STATE_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
|
||||||
|
|
||||||
private final ArrayList<double[]> pendingPoints = new ArrayList<>();
|
private final ArrayList<double[]> pendingPoints = new ArrayList<>();
|
||||||
|
|
||||||
@@ -68,18 +68,17 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
private boolean reportSpeed = true;
|
private boolean reportSpeed = true;
|
||||||
private int recordingDistanceInterval = PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT;
|
private int recordingDistanceInterval;
|
||||||
|
|
||||||
// Modes of operation
|
// Modes of operation
|
||||||
private boolean chartByDistance = true;
|
private boolean chartByDistance;
|
||||||
private boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
|
private boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
|
||||||
|
|
||||||
// UI elements
|
// UI elements
|
||||||
private ChartView chartView;
|
private ChartView chartView;
|
||||||
private ZoomControls zoomControls;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable that will enable/disable zoom controls and orange pointer as appropriate and redraw.
|
* A runnable that will set the orange pointer as appropriate and redraw.
|
||||||
*/
|
*/
|
||||||
private final Runnable updateChart = new Runnable() {
|
private final Runnable updateChart = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -88,46 +87,34 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
|
||||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
|
||||||
chartView.setShowPointer(isSelectedTrackRecording());
|
chartView.setShowPointer(isSelectedTrackRecording());
|
||||||
chartView.invalidate();
|
chartView.invalidate();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public ChartFragment(boolean chartByDistance) {
|
||||||
|
this.chartByDistance = chartByDistance;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
/*
|
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
|
||||||
* Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
|
|
||||||
*/
|
// Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
|
||||||
chartView = new ChartView(getActivity());
|
chartView = new ChartView(getActivity(), chartByDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.chart, container, false);
|
return inflater.inflate(R.layout.chart, container, false);
|
||||||
zoomControls = view.findViewById(R.id.chart_zoom_controls);
|
|
||||||
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
zoomIn();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
zoomOut();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
|
ViewGroup layout = getView().findViewById(R.id.chart_view_layout);
|
||||||
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||||
layout.addView(chartView, layoutParams);
|
layout.addView(chartView, layoutParams);
|
||||||
}
|
}
|
||||||
@@ -149,10 +136,24 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
|
ViewGroup layout = getView().findViewById(R.id.chart_view_layout);
|
||||||
layout.removeView(chartView);
|
layout.removeView(chartView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewStateRestored(savedInstanceState);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
chartByDistance = savedInstanceState.getBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(Track track) {
|
public void onTrackUpdated(Track track) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -302,12 +303,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
*/
|
*/
|
||||||
private void checkChartSettings() {
|
private void checkChartSettings() {
|
||||||
boolean needUpdate = false;
|
boolean needUpdate = false;
|
||||||
if (chartByDistance != PreferencesUtils.isChartByDistance(getActivity())) {
|
|
||||||
chartByDistance = !chartByDistance;
|
|
||||||
chartView.setChartByDistance(chartByDistance);
|
|
||||||
reloadTrackDataHub();
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.shouldChartShowElevation(getActivity()))) {
|
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.shouldChartShowElevation(getActivity()))) {
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
@@ -388,18 +383,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void zoomIn() {
|
|
||||||
chartView.zoomIn();
|
|
||||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
|
||||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void zoomOut() {
|
|
||||||
chartView.zoomOut();
|
|
||||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
|
||||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs a runnable on the UI thread if possible.
|
* Runs a runnable on the UI thread if possible.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
|
public class ChartTimeFragment extends ChartFragment {
|
||||||
|
|
||||||
|
public static final String CHART_FRAGMENT_TAG = ChartTimeFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
public ChartTimeFragment() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,7 +81,7 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
|||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(fragmentActivity);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||||
for (long markerId : markerIds) {
|
for (long markerId : markerIds) {
|
||||||
contentProviderUtils.deleteWaypoint(fragmentActivity,
|
contentProviderUtils.deleteWaypoint(fragmentActivity,
|
||||||
markerId, new DescriptionGeneratorImpl(fragmentActivity));
|
markerId, new DescriptionGeneratorImpl(fragmentActivity));
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
getFragmentManager().popBackStack();
|
getFragmentManager().popBackStack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(getActivity());
|
contentProviderUtils = new ContentProviderUtils(getActivity());
|
||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import de.dennisguse.opentracks.services.ITrackRecordingService;
|
|||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.StatsUtils;
|
import de.dennisguse.opentracks.util.StatsUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
@@ -81,7 +80,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
private String category = "";
|
private String category = "";
|
||||||
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT;
|
private int recordingGpsAccuracy;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
@@ -91,6 +90,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
recordingGpsAccuracy = Integer.parseInt(getContext().getResources().getString(R.string.recording_gps_accuracy_default));
|
||||||
|
|
||||||
handlerUpdateUI = new Handler();
|
handlerUpdateUI = new Handler();
|
||||||
|
|
||||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||||
|
|||||||
@@ -173,10 +173,12 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1);
|
|
||||||
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||||
|
|
||||||
|
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,12 +186,10 @@ public enum TrackFileFormat {
|
|||||||
*/
|
*/
|
||||||
public abstract String getMimeType();
|
public abstract String getMimeType();
|
||||||
|
|
||||||
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1);
|
||||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||||
|
|
||||||
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
this.trackFileFormat = trackFileFormat;
|
this.trackFileFormat = trackFileFormat;
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
context = exportActivity.getApplicationContext();
|
context = exportActivity.getApplicationContext();
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
completed = false;
|
completed = false;
|
||||||
processedTrackCount = 0;
|
processedTrackCount = 0;
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import androidx.annotation.NonNull;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
import de.dennisguse.opentracks.content.LocationFactory;
|
||||||
|
import de.dennisguse.opentracks.content.LocationIterator;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
@@ -63,7 +64,7 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean writeTrack(Context context, @NonNull OutputStream outputStream) {
|
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) {
|
||||||
try {
|
try {
|
||||||
trackWriter.prepare(outputStream);
|
trackWriter.prepare(outputStream);
|
||||||
trackWriter.writeHeader(tracks);
|
trackWriter.writeHeader(tracks);
|
||||||
@@ -209,7 +210,7 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
private class TrackWriterLocationFactory implements ContentProviderUtils.LocationFactory {
|
private class TrackWriterLocationFactory extends LocationFactory {
|
||||||
Location currentLocation;
|
Location currentLocation;
|
||||||
Location lastLocation;
|
Location lastLocation;
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
this.exportSensorData = exportSensorData;
|
this.exportSensorData = exportSensorData;
|
||||||
this.exportPhotos = exportPhotos;
|
this.exportPhotos = exportPhotos;
|
||||||
this.descriptionGenerator = new DescriptionGeneratorImpl(context);
|
this.descriptionGenerator = new DescriptionGeneratorImpl(context);
|
||||||
this.contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
this.contentProviderUtils = new ContentProviderUtils(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package de.dennisguse.opentracks.io.file.exporter;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@@ -37,5 +36,5 @@ public interface TrackExporter {
|
|||||||
* @param context the context
|
* @param context the context
|
||||||
* @param outputStream the output stream
|
* @param outputStream the output stream
|
||||||
*/
|
*/
|
||||||
boolean writeTrack(@Nullable Context context, @NonNull OutputStream outputStream);
|
boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -39,8 +39,9 @@ import javax.xml.parsers.SAXParserFactory;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
||||||
|
import de.dennisguse.opentracks.content.LocationFactory;
|
||||||
|
import de.dennisguse.opentracks.content.LocationIterator;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
@@ -165,7 +166,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
||||||
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
||||||
|
|
||||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (waypoint == null) {
|
if (waypoint == null) {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
* @param context the context
|
* @param context the context
|
||||||
*/
|
*/
|
||||||
public GpxFileTrackImporter(Context context) {
|
public GpxFileTrackImporter(Context context) {
|
||||||
this(context, ContentProviderUtils.Factory.get(context));
|
this(context, new ContentProviderUtils(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
||||||
private final TrackFileFormat trackFileFormat;
|
private final TrackFileFormat trackFileFormat;
|
||||||
private final String path;
|
private final String path;
|
||||||
private final Context context;
|
// TODO Can be removed, or?
|
||||||
|
private Context context;
|
||||||
|
// TODO Use weak reference
|
||||||
private ImportActivity importActivity;
|
private ImportActivity importActivity;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
@@ -100,7 +102,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
try {
|
try {
|
||||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
Thread.currentThread().setPriority(Thread.MAX_PRIORITY); // TODO Should be set to previous level at the end of this method.
|
||||||
// Get the wake lock if not recording or paused
|
// Get the wake lock if not recording or paused
|
||||||
boolean isRecording = PreferencesUtils.isRecording(importActivity);
|
boolean isRecording = PreferencesUtils.isRecording(importActivity);
|
||||||
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
|
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
|
||||||
@@ -149,9 +151,11 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCancelled() {
|
protected void onCancelled() {
|
||||||
|
context = null;
|
||||||
completed = true;
|
completed = true;
|
||||||
if (importActivity != null) {
|
if (importActivity != null) {
|
||||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||||
|
importActivity = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,17 +167,17 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private boolean importFile(final File file) {
|
private boolean importFile(final File file) {
|
||||||
TrackImporter trackImporter;
|
TrackImporter trackImporter;
|
||||||
if (trackFileFormat == TrackFileFormat.GPX) {
|
if (trackFileFormat == TrackFileFormat.GPX) {
|
||||||
trackImporter = new GpxFileTrackImporter(context);
|
trackImporter = new GpxFileTrackImporter(importActivity);
|
||||||
} else { //KML or KMZ
|
} else { //KML or KMZ
|
||||||
String extension = FileUtils.getExtension(file.getName());
|
String extension = FileUtils.getExtension(file.getName());
|
||||||
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
|
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
|
||||||
trackImporter = new KmlFileTrackImporter(context, -1L);
|
trackImporter = new KmlFileTrackImporter(importActivity, -1L);
|
||||||
} else {
|
} else {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(importActivity);
|
||||||
Uri uri = contentProviderUtils.insertTrack(new Track());
|
Uri uri = contentProviderUtils.insertTrack(new Track());
|
||||||
long newId = Long.parseLong(uri.getLastPathSegment());
|
long newId = Long.parseLong(uri.getLastPathSegment());
|
||||||
|
|
||||||
trackImporter = new KmzTrackImporter(context, newId);
|
trackImporter = new KmzTrackImporter(importActivity, newId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
* @param importTrackId track id to import to. -1L to import to a new track.
|
* @param importTrackId track id to import to. -1L to import to a new track.
|
||||||
*/
|
*/
|
||||||
public KmlFileTrackImporter(Context context, long importTrackId) {
|
public KmlFileTrackImporter(Context context, long importTrackId) {
|
||||||
this(context, importTrackId, ContentProviderUtils.Factory.get(context));
|
this(context, importTrackId, new ContentProviderUtils(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
return -1L;
|
return -1L;
|
||||||
}
|
}
|
||||||
String fileName = zipEntry.getName();
|
String fileName = zipEntry.getName();
|
||||||
if (fileName.equals(KmzTrackExporter.KMZ_KML_FILE)) {
|
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
|
||||||
trackId = parseKml(zipInputStream);
|
trackId = parseKml(zipInputStream);
|
||||||
if (trackId == -1L) {
|
if (trackId == -1L) {
|
||||||
Log.d(TAG, "Unable to parse kml in kmz");
|
Log.d(TAG, "Unable to parse kml in kmz");
|
||||||
@@ -102,7 +102,7 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
*/
|
*/
|
||||||
private void cleanImport(long trackId) {
|
private void cleanImport(long trackId) {
|
||||||
if (PreferencesUtils.isRecording(trackId)) {
|
if (PreferencesUtils.isRecording(trackId)) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
contentProviderUtils.deleteTrack(context, trackId);
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,15 +125,10 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* @return the imported track id or -1L
|
* @return the imported track id or -1L
|
||||||
*/
|
*/
|
||||||
private long parseKml(ZipInputStream zipInputStream) throws IOException {
|
private long parseKml(ZipInputStream zipInputStream) throws IOException {
|
||||||
ByteArrayInputStream byteArrayInputStream = null;
|
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
|
||||||
try {
|
|
||||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
|
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
|
||||||
byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream));
|
|
||||||
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
||||||
} finally {
|
|
||||||
if (byteArrayInputStream != null) {
|
|
||||||
byteArrayInputStream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,12 +155,10 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* @param fileName the file name
|
* @param fileName the file name
|
||||||
*/
|
*/
|
||||||
private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
|
private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
|
||||||
if (importTrackId == -1L) {
|
if (importTrackId == -1L || fileName.equals("")) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fileName.equals("")) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File dir = FileUtils.getPhotoDir(importTrackId);
|
File dir = FileUtils.getPhotoDir(importTrackId);
|
||||||
FileUtils.ensureDirectoryExists(dir);
|
FileUtils.ensureDirectoryExists(dir);
|
||||||
File file = new File(dir, fileName);
|
File file = new File(dir, fileName);
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ public class LocationManagerConnector {
|
|||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final LocationManager locationManager;
|
private final LocationManager locationManager;
|
||||||
|
|
||||||
private LocationListener requestLocationUpdates;
|
|
||||||
private float requestLocationUpdatesDistance;
|
|
||||||
private long requestLocationUpdatesTime;
|
|
||||||
|
|
||||||
public LocationManagerConnector(Context context, Looper looper) {
|
public LocationManagerConnector(Context context, Looper looper) {
|
||||||
this.handler = new Handler(looper);
|
this.handler = new Handler(looper);
|
||||||
|
|
||||||
@@ -55,10 +51,7 @@ public class LocationManagerConnector {
|
|||||||
@Override
|
@Override
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
public void run() {
|
public void run() {
|
||||||
requestLocationUpdatesTime = minTime;
|
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime, minDistance, locationListener);
|
||||||
requestLocationUpdatesDistance = minDistance;
|
|
||||||
requestLocationUpdates = locationListener;
|
|
||||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, requestLocationUpdatesTime, requestLocationUpdatesDistance, locationListener);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -70,6 +63,5 @@ public class LocationManagerConnector {
|
|||||||
*/
|
*/
|
||||||
public void removeLocationUpdates(final LocationListener locationListener) {
|
public void removeLocationUpdates(final LocationListener locationListener) {
|
||||||
locationManager.removeUpdates(locationListener);
|
locationManager.removeUpdates(locationListener);
|
||||||
requestLocationUpdates = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.services;
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -30,7 +27,6 @@ import android.location.Location;
|
|||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -38,7 +34,6 @@ import android.os.PowerManager.WakeLock;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.app.TaskStackBuilder;
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -48,9 +43,10 @@ import de.dennisguse.opentracks.R;
|
|||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||||
import de.dennisguse.opentracks.TrackListActivity;
|
import de.dennisguse.opentracks.TrackListActivity;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.content.CustomContentProvider;
|
import de.dennisguse.opentracks.content.CustomContentProvider;
|
||||||
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
||||||
|
import de.dennisguse.opentracks.content.LocationFactory;
|
||||||
|
import de.dennisguse.opentracks.content.LocationIterator;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
@@ -80,13 +76,13 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
public class TrackRecordingService extends Service {
|
public class TrackRecordingService extends Service {
|
||||||
|
|
||||||
// The name of extra intent property to indicate whether we want to resume a previously recorded track.
|
// The name of extra intent property to indicate whether we want to resume a previously recorded track.
|
||||||
public static final String RESUME_TRACK_EXTRA_NAME = "de.dennisguse.opentracks.RESUME_TRACK";
|
public static final String RESUME_TRACK_EXTRA_NAME = "RESUME_TRACK";
|
||||||
|
private static final int NOTIFICATION_ID = 123;
|
||||||
|
|
||||||
public static final double PAUSE_LATITUDE = 100.0;
|
public static final double PAUSE_LATITUDE = 100.0;
|
||||||
public static final double RESUME_LATITUDE = 200.0;
|
public static final double RESUME_LATITUDE = 200.0;
|
||||||
|
|
||||||
// Anything faster than that (in meters per second) will be considered moving.
|
// Anything faster than that (in meters per second) will be considered moving.
|
||||||
|
|
||||||
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
||||||
@@ -95,13 +91,13 @@ public class TrackRecordingService extends Service {
|
|||||||
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||||
// The following variables are set in onCreate:
|
// The following variables are set in onCreate:
|
||||||
private ExecutorService executorService;
|
private ExecutorService executorService;
|
||||||
private Context context;
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
private LocationManagerConnector locationManagerConnector;
|
private LocationManagerConnector locationManagerConnector;
|
||||||
private PeriodicTaskExecutor voiceExecutor;
|
private PeriodicTaskExecutor voiceExecutor;
|
||||||
private PeriodicTaskExecutor splitExecutor;
|
private PeriodicTaskExecutor splitExecutor;
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
private TrackRecordingServiceNotificationManager notificationManager;
|
||||||
private long recordingTrackId;
|
private long recordingTrackId;
|
||||||
private boolean recordingTrackPaused;
|
private boolean recordingTrackPaused;
|
||||||
private LocationListenerPolicy locationListenerPolicy;
|
private LocationListenerPolicy locationListenerPolicy;
|
||||||
@@ -117,10 +113,11 @@ public class TrackRecordingService extends Service {
|
|||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
Context context = TrackRecordingService.this;
|
||||||
|
if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) {
|
||||||
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
|
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
|
||||||
if (PreferencesUtils.isRecording(context)) {
|
if (PreferencesUtils.isRecording(TrackRecordingService.this)) {
|
||||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingService.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||||
@@ -139,17 +136,14 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||||
switch (minRecordingInterval) {
|
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
|
// Choose battery life over moving time accuracy.
|
||||||
// Choose battery life over moving time accuracy.
|
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5);
|
||||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5);
|
} else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
|
||||||
break;
|
// Get all the updates.
|
||||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY:
|
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0);
|
||||||
// Get all the updates.
|
} else {
|
||||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0);
|
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||||
@@ -221,14 +215,14 @@ public class TrackRecordingService extends Service {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
executorService = Executors.newSingleThreadExecutor();
|
executorService = Executors.newSingleThreadExecutor();
|
||||||
context = this;
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
|
||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
|
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
|
||||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
||||||
splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory());
|
splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory());
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||||
|
|
||||||
// onSharedPreferenceChanged might not set recordingTrackId.
|
// onSharedPreferenceChanged might not set recordingTrackId.
|
||||||
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
@@ -270,7 +264,7 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reverse order from onCreate
|
// Reverse order from onCreate
|
||||||
showNotification(false);
|
showNotification(false); //TODO Why?
|
||||||
|
|
||||||
handler.removeCallbacks(registerLocationRunnable);
|
handler.removeCallbacks(registerLocationRunnable);
|
||||||
unregisterLocationListener();
|
unregisterLocationListener();
|
||||||
@@ -403,40 +397,6 @@ public class TrackRecordingService extends Service {
|
|||||||
return Long.parseLong(uri.getLastPathSegment());
|
return Long.parseLong(uri.getLastPathSegment());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the service as a foreground service.
|
|
||||||
*
|
|
||||||
* @param pendingIntent the notification pending intent
|
|
||||||
* @param messageId the notification message id
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
protected void startForegroundService(PendingIntent pendingIntent, int messageId) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
NotificationChannel notificationChannel = new NotificationChannel(getString(R.string.app_name), getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT);
|
|
||||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
if (manager != null) {
|
|
||||||
manager.createNotificationChannel(notificationChannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.app_name))
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
.setContentText(getString(messageId))
|
|
||||||
.setContentTitle(getString(R.string.app_name))
|
|
||||||
.setOngoing(true)
|
|
||||||
.setSmallIcon(R.drawable.ic_logo_color_24dp)
|
|
||||||
.setCategory(Notification.CATEGORY_SERVICE)
|
|
||||||
.setWhen(System.currentTimeMillis());
|
|
||||||
startForeground(1, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the service as a foreground service.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
protected void stopForegroundService() {
|
|
||||||
stopForeground(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles start command.
|
* Handles start command.
|
||||||
*
|
*
|
||||||
@@ -471,12 +431,12 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
PreferencesUtils.incrementAutoResumeTrackCurrentRetryDefault(this);
|
PreferencesUtils.incrementAutoResumeTrackCurrentRetryDefault(this);
|
||||||
|
|
||||||
if (autoResumeTrackTimeout == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
||||||
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
|
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoResumeTrackTimeout == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
||||||
Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
|
Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -547,7 +507,7 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime);
|
markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime);
|
||||||
|
|
||||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
|
|
||||||
while (locationIterator.hasNext()) {
|
while (locationIterator.hasNext()) {
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
@@ -680,6 +640,8 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
endRecording(false, recordingTrackId);
|
endRecording(false, recordingTrackId);
|
||||||
|
|
||||||
|
notificationManager.updateContent(getString(R.string.generic_paused));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -766,6 +728,8 @@ public class TrackRecordingService extends Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notificationManager.updateLocation(this, location, recordingGpsAccuracy);
|
||||||
|
|
||||||
if (!location.hasAccuracy() || location.getAccuracy() >= recordingGpsAccuracy) {
|
if (!location.hasAccuracy() || location.getAccuracy() >= recordingGpsAccuracy) {
|
||||||
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
|
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
|
||||||
return;
|
return;
|
||||||
@@ -801,10 +765,7 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!LocationUtils.isValidLocation(lastValidTrackPoint)) {
|
if (!LocationUtils.isValidLocation(lastValidTrackPoint)) {
|
||||||
/*
|
// Should not happen. The current segment should have a location. Just insert the current location.
|
||||||
* Should not happen. The current segment should have a location. Just
|
|
||||||
* insert the current location.
|
|
||||||
*/
|
|
||||||
insertLocation(track, location, null);
|
insertLocation(track, location, null);
|
||||||
lastLocation = location;
|
lastLocation = location;
|
||||||
return;
|
return;
|
||||||
@@ -951,33 +912,33 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the notification.
|
|
||||||
*
|
|
||||||
* @param isGpsStarted true if GPS is started
|
|
||||||
*/
|
|
||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
if (isRecording()) {
|
if ((isRecording() && isPaused()) || (!isRecording() && !isGpsStarted)) {
|
||||||
if (isPaused()) {
|
stopForeground(true);
|
||||||
stopForegroundService();
|
}
|
||||||
} else {
|
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
if (isRecording() && !isPaused()) {
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||||
.addParentStack(TrackDetailActivity.class).addNextIntent(intent)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
.addParentStack(TrackDetailActivity.class)
|
||||||
startForegroundService(pendingIntent, R.string.track_record_notification);
|
.addNextIntent(intent)
|
||||||
}
|
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
} else {
|
|
||||||
// Not recording
|
notificationManager.updatePendingIntent(pendingIntent);
|
||||||
if (isGpsStarted) {
|
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
startForeground(NOTIFICATION_ID, notificationManager.getNotification());
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
}
|
||||||
.addNextIntent(intent).getPendingIntent(0, 0);
|
if (!isRecording() && isGpsStarted) {
|
||||||
startForegroundService(pendingIntent, R.string.gps_starting);
|
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
||||||
} else {
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
stopForegroundService();
|
.addNextIntent(intent)
|
||||||
}
|
.getPendingIntent(0, 0);
|
||||||
|
|
||||||
|
notificationManager.updatePendingIntent(pendingIntent);
|
||||||
|
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||||
|
|
||||||
|
startForeground(NOTIFICATION_ID, notificationManager.getNotification());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the content of the notification shown by {@link TrackRecordingService}.
|
||||||
|
*/
|
||||||
|
class TrackRecordingServiceNotificationManager {
|
||||||
|
|
||||||
|
private final static int NOTIFICATION_ID = 123;
|
||||||
|
|
||||||
|
private final static String CHANNEL_ID = TrackRecordingServiceNotificationManager.class.getSimpleName();
|
||||||
|
|
||||||
|
private NotificationCompat.Builder notificationBuilder;
|
||||||
|
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
|
private boolean previousLocationWasAccurate = true;
|
||||||
|
|
||||||
|
TrackRecordingServiceNotificationManager(Context context) {
|
||||||
|
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
notificationChannel.setAllowBubbles(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(notificationChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_ID);
|
||||||
|
notificationBuilder
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
|
.setContentTitle(context.getString(R.string.app_name))
|
||||||
|
.setSmallIcon(R.drawable.ic_logo_color_24dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
TrackRecordingServiceNotificationManager(NotificationManager notificationManager, NotificationCompat.Builder notificationBuilder) {
|
||||||
|
this.notificationManager = notificationManager;
|
||||||
|
this.notificationBuilder = notificationBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateContent(String content) {
|
||||||
|
notificationBuilder.setContentText(content);
|
||||||
|
updateNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateLocation(Context context, Location location, int recordingGpsAccuracy) {
|
||||||
|
String formattedAccuracy = context.getString(R.string.value_none);
|
||||||
|
if (location.hasAccuracy()) {
|
||||||
|
formattedAccuracy = StringUtils.formatDistance(context, location.getAccuracy(), PreferencesUtils.isMetricUnits(context));
|
||||||
|
|
||||||
|
boolean currentLocationWasAccurate = location.getAccuracy() < recordingGpsAccuracy;
|
||||||
|
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
|
||||||
|
notificationBuilder.setOnlyAlertOnce(!shouldAlert);
|
||||||
|
previousLocationWasAccurate = currentLocationWasAccurate;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBuilder.setContentText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
|
||||||
|
updateNotification();
|
||||||
|
notificationBuilder.setOnlyAlertOnce(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePendingIntent(PendingIntent pendingIntent) {
|
||||||
|
notificationBuilder.setContentIntent(pendingIntent);
|
||||||
|
updateNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification getNotification() {
|
||||||
|
return notificationBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateNotification() {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, getNotification());
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-5
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothGatt;
|
|||||||
import android.bluetooth.BluetoothGattCallback;
|
import android.bluetooth.BluetoothGattCallback;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.bluetooth.BluetoothGattDescriptor;
|
import android.bluetooth.BluetoothGattDescriptor;
|
||||||
|
import android.bluetooth.BluetoothGattService;
|
||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -45,7 +46,6 @@ public class BluetoothConnectionManager {
|
|||||||
static final int MESSAGE_READ = 3;
|
static final int MESSAGE_READ = 3;
|
||||||
static final int MESSAGE_DISCONNECTED = 4;
|
static final int MESSAGE_DISCONNECTED = 4;
|
||||||
|
|
||||||
private static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
|
|
||||||
private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
||||||
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
|
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
|
||||||
|
|
||||||
@@ -90,10 +90,18 @@ public class BluetoothConnectionManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
||||||
BluetoothGattCharacteristic characteristic = gatt
|
BluetoothGattService service = gatt.getService(BluetoothUtils.HEART_RATE_SERVICE_UUID);
|
||||||
.getService(HEART_RATE_SERVICE_UUID)
|
if (service == null) {
|
||||||
.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);
|
Log.e(TAG, "Could not get heart rate service for " + gatt.getDevice().getAddress());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic characteristic = service.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);
|
||||||
|
if (characteristic == null) {
|
||||||
|
Log.e(TAG, "Could not get BluetoothCharacteristic for " + gatt.getDevice().getAddress());
|
||||||
|
return;
|
||||||
|
}
|
||||||
gatt.setCharacteristicNotification(characteristic, true);
|
gatt.setCharacteristicNotification(characteristic, true);
|
||||||
|
|
||||||
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
|
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
|
||||||
@@ -131,8 +139,9 @@ public class BluetoothConnectionManager {
|
|||||||
|
|
||||||
Log.d(TAG, "Connecting to: " + bluetoothDevice);
|
Log.d(TAG, "Connecting to: " + bluetoothDevice);
|
||||||
|
|
||||||
setState(SensorState.CONNECTING);
|
|
||||||
bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
|
bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
|
||||||
|
|
||||||
|
setState(SensorState.CONNECTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void disconnect() {
|
public synchronized void disconnect() {
|
||||||
|
|||||||
+6
-44
@@ -26,12 +26,10 @@ import android.os.Message;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
|
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bluetooth LE sensor manager.
|
* Bluetooth LE sensor manager.
|
||||||
@@ -45,7 +43,7 @@ public class BluetoothRemoteSensorManager {
|
|||||||
|
|
||||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||||
|
|
||||||
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
|
private static final BluetoothAdapter bluetoothAdapter = BluetoothUtils.getDefaultBluetoothAdapter(TAG);
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@@ -87,8 +85,8 @@ public class BluetoothRemoteSensorManager {
|
|||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_sensor_key, key)) {
|
if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
|
||||||
String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||||
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
|
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
|
||||||
stop();
|
stop();
|
||||||
return;
|
return;
|
||||||
@@ -114,43 +112,6 @@ public class BluetoothRemoteSensorManager {
|
|||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BluetoothAdapter getDefaultBluetoothAdapter() {
|
|
||||||
// If from the main application thread, return directly
|
|
||||||
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
|
|
||||||
return BluetoothAdapter.getDefaultAdapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the default adapter from the main application thread
|
|
||||||
final ArrayList<BluetoothAdapter> adapters = new ArrayList<>(1);
|
|
||||||
final Object mutex = new Object();
|
|
||||||
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
handler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
adapters.add(BluetoothAdapter.getDefaultAdapter());
|
|
||||||
synchronized (mutex) {
|
|
||||||
mutex.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
while (adapters.isEmpty()) {
|
|
||||||
synchronized (mutex) {
|
|
||||||
try {
|
|
||||||
mutex.wait(UnitConversions.ONE_SECOND);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adapters.get(0) == null) {
|
|
||||||
Log.w(TAG, "No bluetooth adapter found.");
|
|
||||||
}
|
|
||||||
return adapters.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
startCurrentSensor();
|
startCurrentSensor();
|
||||||
@@ -184,7 +145,8 @@ public class BluetoothRemoteSensorManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
// String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||||
|
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||||
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
|
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
|
||||||
Log.w(TAG, "No bluetooth address.");
|
Log.w(TAG, "No bluetooth address.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
|
|||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TTSUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,7 +194,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
* @param announcement the announcement
|
* @param announcement the announcement
|
||||||
*/
|
*/
|
||||||
private void speakAnnouncement(String announcement) {
|
private void speakAnnouncement(String announcement) {
|
||||||
int result = audioManager.requestAudioFocus(null, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
int result = audioManager.requestAudioFocus(null, TTSUtils.getTTSStream(), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||||
Log.w(TAG, "Failed to request audio focus.");
|
Log.w(TAG, "Failed to request audio focus.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ package de.dennisguse.opentracks.services.tasks;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,13 +35,14 @@ public class PeriodicTaskExecutor {
|
|||||||
private final TrackRecordingService trackRecordingService;
|
private final TrackRecordingService trackRecordingService;
|
||||||
private final PeriodicTaskFactory periodicTaskFactory;
|
private final PeriodicTaskFactory periodicTaskFactory;
|
||||||
|
|
||||||
|
private final int TASK_FREQUENCY_OFF;
|
||||||
/**
|
/**
|
||||||
* The task frequency.
|
* The task frequency.
|
||||||
* A positive value is a time frequency (minutes).
|
* A positive value is a time frequency (minutes).
|
||||||
* A negative value is a distance frequency (km or mi).
|
* A negative value is a distance frequency (km or mi).
|
||||||
* A zero value is to turn off periodic task.
|
* A zero value is to turn off periodic task.
|
||||||
*/
|
*/
|
||||||
private int taskFrequency = PreferencesUtils.FREQUENCY_OFF;
|
private int taskFrequency;
|
||||||
|
|
||||||
private PeriodicTask periodicTask;
|
private PeriodicTask periodicTask;
|
||||||
|
|
||||||
@@ -56,6 +57,9 @@ public class PeriodicTaskExecutor {
|
|||||||
public PeriodicTaskExecutor(TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
|
public PeriodicTaskExecutor(TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
|
||||||
this.trackRecordingService = trackRecordingService;
|
this.trackRecordingService = trackRecordingService;
|
||||||
this.periodicTaskFactory = periodicTaskFactory;
|
this.periodicTaskFactory = periodicTaskFactory;
|
||||||
|
|
||||||
|
TASK_FREQUENCY_OFF = Integer.parseInt(trackRecordingService.getBaseContext().getResources().getString(R.string.frequency_off));
|
||||||
|
taskFrequency = TASK_FREQUENCY_OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +75,7 @@ public class PeriodicTaskExecutor {
|
|||||||
timerTaskExecutor.shutdown();
|
timerTaskExecutor.shutdown();
|
||||||
timerTaskExecutor = null;
|
timerTaskExecutor = null;
|
||||||
}
|
}
|
||||||
if (taskFrequency == PreferencesUtils.FREQUENCY_OFF) {
|
if (taskFrequency == TASK_FREQUENCY_OFF) {
|
||||||
Log.d(TAG, "Task frequency is off.");
|
Log.d(TAG, "Task frequency is off.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,14 +43,6 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
|||||||
*/
|
*/
|
||||||
public class ActivityTypePreference extends DialogPreference {
|
public class ActivityTypePreference extends DialogPreference {
|
||||||
|
|
||||||
private SummaryProvider<DialogPreference> summaryProvider = new SummaryProvider<DialogPreference>() {
|
|
||||||
@Override
|
|
||||||
public CharSequence provideSummary(DialogPreference preference) {
|
|
||||||
String defaultActivity = PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
|
|
||||||
return defaultActivity != null && !defaultActivity.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? defaultActivity : getContext().getString(R.string.value_unknown);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public ActivityTypePreference(Context context, AttributeSet attrs) {
|
public ActivityTypePreference(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
setDialogLayoutResource(R.layout.preference_activity_type);
|
setDialogLayoutResource(R.layout.preference_activity_type);
|
||||||
@@ -58,6 +50,13 @@ public class ActivityTypePreference extends DialogPreference {
|
|||||||
setNegativeButtonText(android.R.string.cancel);
|
setNegativeButtonText(android.R.string.cancel);
|
||||||
setDialogIcon(null);
|
setDialogIcon(null);
|
||||||
setPersistent(true);
|
setPersistent(true);
|
||||||
|
|
||||||
|
SummaryProvider<DialogPreference> summaryProvider = new SummaryProvider<DialogPreference>() {
|
||||||
|
@Override
|
||||||
|
public CharSequence provideSummary(DialogPreference preference) {
|
||||||
|
return PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
|
||||||
|
}
|
||||||
|
};
|
||||||
setSummaryProvider(summaryProvider);
|
setSummaryProvider(summaryProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package de.dennisguse.opentracks.settings;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class BluetoothLeAdapter extends BaseAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains a unique list (by address) of devices.
|
||||||
|
*/
|
||||||
|
private final List<Device> devices = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return devices.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return devices.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return devices.get(position).hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View currentView = convertView;
|
||||||
|
if (convertView == null) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
|
//TODO Check if there is a better way to achieve identical look and feel to ListPreference.
|
||||||
|
//Use material design single choice; for old style use: android.R.layout.select_dialog_singlechoice
|
||||||
|
currentView = inflater.inflate(androidx.appcompat.R.layout.select_dialog_singlechoice_material, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Device device = devices.get(position);
|
||||||
|
TextView textView = currentView.findViewById(android.R.id.text1);
|
||||||
|
textView.setText(device.getName());
|
||||||
|
|
||||||
|
return currentView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String name, String address) {
|
||||||
|
Device device = new Device(name, address);
|
||||||
|
if (!devices.contains(device)) {
|
||||||
|
devices.add(new Device(name, address));
|
||||||
|
} else {
|
||||||
|
for (Device currentDevice : devices) {
|
||||||
|
if (currentDevice.getAddress().equals(address)) {
|
||||||
|
currentDevice.setName(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(BluetoothDevice bluetoothDevice) {
|
||||||
|
add(bluetoothDevice.getName(), bluetoothDevice.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Device get(int index) {
|
||||||
|
return devices.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Device {
|
||||||
|
private String name;
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
public Device(String name, String address) {
|
||||||
|
this.name = name;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the address is identical.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Device)) return false;
|
||||||
|
Device device = (Device) o;
|
||||||
|
return address.equals(device.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package de.dennisguse.opentracks.settings;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.le.BluetoothLeScanner;
|
||||||
|
import android.bluetooth.le.ScanCallback;
|
||||||
|
import android.bluetooth.le.ScanFilter;
|
||||||
|
import android.bluetooth.le.ScanResult;
|
||||||
|
import android.bluetooth.le.ScanSettings;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.ParcelUuid;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.preference.DialogPreference;
|
||||||
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference to select a discoverable Bluetooth LE device.
|
||||||
|
* Based upon ListPreference.
|
||||||
|
*/
|
||||||
|
public class BluetoothLePreference extends DialogPreference {
|
||||||
|
|
||||||
|
private final static String TAG = BluetoothLePreference.class.getSimpleName();
|
||||||
|
|
||||||
|
public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLePreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLePreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private boolean valueSet = false;
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
final boolean changed = !TextUtils.equals(this.value, value);
|
||||||
|
if (changed || !valueSet) {
|
||||||
|
this.value = value;
|
||||||
|
valueSet = true;
|
||||||
|
persistString(value);
|
||||||
|
if (changed) {
|
||||||
|
notifyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSetInitialValue(Object defaultValue) {
|
||||||
|
setValue(getPersistedString((String) defaultValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getSummary() {
|
||||||
|
return getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BluetoothLePreferenceDialog extends PreferenceDialogFragmentCompat {
|
||||||
|
|
||||||
|
private int selectedEntryIndex;
|
||||||
|
private BluetoothLeAdapter listAdapter = new BluetoothLeAdapter();
|
||||||
|
|
||||||
|
private BluetoothLeScanner scanner = null;
|
||||||
|
private ScanCallback scanCallback = new ScanCallback() {
|
||||||
|
@Override
|
||||||
|
public void onScanResult(int callbackType, ScanResult result) {
|
||||||
|
super.onScanResult(callbackType, result);
|
||||||
|
Log.d(TAG, "Found device " + result.getDevice().getName() + " " + result);
|
||||||
|
|
||||||
|
listAdapter.add(result.getDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBatchScanResults(List<ScanResult> results) {
|
||||||
|
for (ScanResult scanResult : results) {
|
||||||
|
listAdapter.add(scanResult.getDevice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScanFailed(int errorCode) {
|
||||||
|
super.onScanFailed(errorCode);
|
||||||
|
Log.e(TAG, "Bluetooth scan failed with errorCode " + errorCode);
|
||||||
|
Toast.makeText(getContext(), R.string.sensor_could_not_scan, Toast.LENGTH_LONG).show();
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static BluetoothLePreferenceDialog newInstance(String key) {
|
||||||
|
final BluetoothLePreferenceDialog fragment = new BluetoothLePreferenceDialog();
|
||||||
|
final Bundle b = new Bundle(1);
|
||||||
|
b.putString(ARG_KEY, key);
|
||||||
|
fragment.setArguments(b);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getDefaultBluetoothAdapter(TAG);
|
||||||
|
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
|
||||||
|
Log.w(TAG, "Bluetooth adapter is present or not enabled.");
|
||||||
|
Toast.makeText(getContext(), R.string.bluetooth_disabled, Toast.LENGTH_SHORT).show();
|
||||||
|
dismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bluetoothAdapter.isDiscovering()) {
|
||||||
|
Log.i(TAG, "Cancelling ongoing bluetooth discovery.");
|
||||||
|
bluetoothAdapter.cancelDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner = bluetoothAdapter.getBluetoothLeScanner();
|
||||||
|
if (scanner == null) {
|
||||||
|
Log.e(TAG, "BluetoothLeScanner is null.");
|
||||||
|
dismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String deviceNone = getContext().getString(R.string.sensor_type_value_none);
|
||||||
|
listAdapter.add(getContext().getString(R.string.value_none), deviceNone);
|
||||||
|
selectedEntryIndex = 0;
|
||||||
|
|
||||||
|
BluetoothLePreference preference = (BluetoothLePreference) getPreference();
|
||||||
|
String deviceSelected = preference.getValue();
|
||||||
|
if (deviceSelected != null && !deviceNone.equals(deviceSelected)) {
|
||||||
|
listAdapter.add(preference.getValue(), preference.getValue());
|
||||||
|
selectedEntryIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(BluetoothUtils.HEART_RATE_SERVICE_UUID));
|
||||||
|
List<ScanFilter> scanFilter = new ArrayList<>();
|
||||||
|
scanFilter.add(scanFilterBuilder.build());
|
||||||
|
|
||||||
|
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
|
||||||
|
scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
|
||||||
|
|
||||||
|
scanner.startScan(scanFilter, scanSettingsBuilder.build(), scanCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Behave like ListPreferenceDialogFragmentCompat, but uses a custom listAdapter.
|
||||||
|
@Override
|
||||||
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
|
super.onPrepareDialogBuilder(builder);
|
||||||
|
|
||||||
|
builder.setSingleChoiceItems(listAdapter, selectedEntryIndex,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
selectedEntryIndex = which;
|
||||||
|
|
||||||
|
BluetoothLePreferenceDialog.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setIcon(android.R.drawable.stat_sys_data_bluetooth);
|
||||||
|
builder.setPositiveButton(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDialogClosed(boolean positiveResult) {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.stopScan(scanCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positiveResult && selectedEntryIndex >= 0) {
|
||||||
|
String value = listAdapter.get(selectedEntryIndex).getAddress();
|
||||||
|
BluetoothLePreference preference = (BluetoothLePreference) getPreference();
|
||||||
|
if (preference.callChangeListener(value)) {
|
||||||
|
preference.setValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,210 +1,163 @@
|
|||||||
package de.dennisguse.opentracks.settings;
|
package de.dennisguse.opentracks.settings;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.preference.ListPreference;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.util.BluetoothUtils;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
final class PreferenceHelper {
|
final class PreferenceHelper {
|
||||||
|
|
||||||
/**
|
static String[] getMinRecordingIntervalEntries(Context context) {
|
||||||
* Sets the min recording interval options.
|
String[] entryValues = context.getResources().getStringArray(R.array.min_recording_interval_values);
|
||||||
*
|
String[] entries = new String[entryValues.length];
|
||||||
* @param options the options
|
for (int i = 0; i < entryValues.length; i++) {
|
||||||
* @param values the values
|
int value = Integer.parseInt(entryValues[i]);
|
||||||
*/
|
|
||||||
static void setMinRecordingIntervalOptions(Context context, String[] options, String[] values) {
|
if (value == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
|
||||||
for (int i = 0; i < values.length; i++) {
|
entries[i] = context.getString(R.string.value_adapt_accuracy);
|
||||||
int value = Integer.parseInt(values[i]);
|
} else if (value == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||||
switch (value) {
|
entries[i] = context.getString(R.string.value_adapt_battery_life);
|
||||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
|
} else if (value == PreferencesUtils.getMinRecordingIntervalDefault(context)) {
|
||||||
options[i] = context.getString(R.string.value_adapt_battery_life);
|
entries[i] = context.getString(R.string.value_smallest_recommended);
|
||||||
break;
|
} else {
|
||||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY:
|
entries[i] = value < 60 ? context.getString(R.string.value_integer_second, value) : context.getString(R.string.value_integer_minute, value / 60);
|
||||||
options[i] = context.getString(R.string.value_adapt_accuracy);
|
|
||||||
break;
|
|
||||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT:
|
|
||||||
options[i] = context.getString(R.string.value_smallest_recommended);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
options[i] = value < 60 ? context.getString(R.string.value_integer_second, value) : context.getString(R.string.value_integer_minute, value / 60);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static String[] getRecordingDistanceIntervalEntries(Context context, boolean metricUnits) {
|
||||||
* Sets the recording distance interval options.
|
String[] entryValues = context.getResources().getStringArray(R.array.recording_distance_interval_values);
|
||||||
*
|
String[] entries = new String[entryValues.length];
|
||||||
* @param options the options
|
|
||||||
* @param values the values
|
final int recordingDistanceIntervalDefault = PreferencesUtils.getRecordingDistanceIntervalDefault(context);
|
||||||
* @param metricUnits true for metric units
|
|
||||||
*/
|
for (int i = 0; i < entryValues.length; i++) {
|
||||||
static void setRecordingDistanceIntervalOptions(Context context, String[] options, String[] values, boolean metricUnits) {
|
int value = Integer.parseInt(entryValues[i]);
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
int value = Integer.parseInt(values[i]);
|
|
||||||
String displayValue;
|
String displayValue;
|
||||||
if (metricUnits) {
|
if (metricUnits) {
|
||||||
displayValue = context.getString(R.string.value_integer_meter, value);
|
displayValue = context.getString(R.string.value_integer_meter, value);
|
||||||
if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) {
|
if (value == recordingDistanceIntervalDefault) {
|
||||||
options[i] = context.getString(R.string.value_integer_meter_recommended, value);
|
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
|
||||||
} else {
|
} else {
|
||||||
options[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int feet = (int) (value * UnitConversions.M_TO_FT);
|
int feet = (int) (value * UnitConversions.M_TO_FT);
|
||||||
displayValue = context.getString(R.string.value_integer_feet, feet);
|
displayValue = context.getString(R.string.value_integer_feet, feet);
|
||||||
if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) {
|
if (value == recordingDistanceIntervalDefault) {
|
||||||
options[i] = context.getString(R.string.value_integer_feet_recommended, feet);
|
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
|
||||||
} else {
|
} else {
|
||||||
options[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static String[] getMaxRecordingDistanceEntries(Context context, boolean metricUnits) {
|
||||||
* Sets the max recording distance options.
|
String[] entryValues = context.getResources().getStringArray(R.array.max_recording_distance_values);
|
||||||
*
|
String[] entries = new String[entryValues.length];
|
||||||
* @param options the options
|
|
||||||
* @param values the values
|
final int maxRecordingDistanceDefault = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
|
||||||
* @param metricUnits true for metric units
|
|
||||||
*/
|
for (int i = 0; i < entryValues.length; i++) {
|
||||||
static void setMaxRecordingDistanceOptions(Context context, String[] options, String[] values, boolean metricUnits) {
|
int value = Integer.parseInt(entryValues[i]);
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
int value = Integer.parseInt(values[i]);
|
|
||||||
String displayValue;
|
String displayValue;
|
||||||
if (metricUnits) {
|
if (metricUnits) {
|
||||||
displayValue = context.getString(R.string.value_integer_meter, value);
|
displayValue = context.getString(R.string.value_integer_meter, value);
|
||||||
if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) {
|
if (value == maxRecordingDistanceDefault) {
|
||||||
options[i] = context.getString(R.string.value_integer_meter_recommended, value);
|
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
|
||||||
} else {
|
} else {
|
||||||
options[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int feet = (int) (value * UnitConversions.M_TO_FT);
|
int feet = (int) (value * UnitConversions.M_TO_FT);
|
||||||
if (feet < 2000) {
|
if (feet < 2000) {
|
||||||
displayValue = context.getString(R.string.value_integer_feet, feet);
|
displayValue = context.getString(R.string.value_integer_feet, feet);
|
||||||
if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) {
|
if (value == maxRecordingDistanceDefault) {
|
||||||
options[i] = context.getString(R.string.value_integer_feet_recommended, feet);
|
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
|
||||||
} else {
|
} else {
|
||||||
options[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
double mile = feet * UnitConversions.FT_TO_MI;
|
double mile = feet * UnitConversions.FT_TO_MI;
|
||||||
displayValue = context.getString(R.string.value_float_mile, mile);
|
displayValue = context.getString(R.string.value_float_mile, mile);
|
||||||
options[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static String[] getRecordingGpsAccuracyEntries(Context context, boolean metricUnits) {
|
||||||
* Sets the recording gps accuracy options.
|
String[] entryValues = context.getResources().getStringArray(R.array.recording_gps_accuracy_values);
|
||||||
*
|
String[] entries = new String[entryValues.length];
|
||||||
* @param options the options
|
|
||||||
* @param values the values
|
final int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||||
* @param metricUnits true for metric units
|
final int recordingGPSAccuracyExcellent = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_excellent));
|
||||||
*/
|
final int recordingGPSAccuracyPoor = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_poor));
|
||||||
static void setRecordingGpsAccuracyOptions(Context context, String[] options, String[] values, boolean metricUnits) {
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
for (int i = 0; i < entryValues.length; i++) {
|
||||||
int value = Integer.parseInt(values[i]);
|
int value = Integer.parseInt(entryValues[i]);
|
||||||
String displayValue;
|
String displayValue;
|
||||||
if (metricUnits) {
|
if (metricUnits) {
|
||||||
displayValue = context.getString(R.string.value_integer_meter, value);
|
displayValue = context.getString(R.string.value_integer_meter, value);
|
||||||
switch (value) {
|
if (value == recordingGPSAccuracyDefault) {
|
||||||
case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT:
|
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
|
||||||
options[i] = context.getString(R.string.value_integer_meter_recommended, value);
|
} else if (value == recordingGPSAccuracyExcellent) {
|
||||||
break;
|
entries[i] = context.getString(R.string.value_integer_meter_excellent_gps, value);
|
||||||
case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT:
|
} else if (value == recordingGPSAccuracyPoor) {
|
||||||
options[i] = context.getString(R.string.value_integer_meter_excellent_gps, value);
|
entries[i] = context.getString(R.string.value_integer_meter_poor_gps, value);
|
||||||
break;
|
} else {
|
||||||
case PreferencesUtils.RECORDING_GPS_ACCURACY_POOR:
|
entries[i] = displayValue;
|
||||||
options[i] = context.getString(R.string.value_integer_meter_poor_gps, value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
options[i] = displayValue;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int feet = (int) (value * UnitConversions.M_TO_FT);
|
int feet = (int) (value * UnitConversions.M_TO_FT);
|
||||||
if (feet < 2000) {
|
if (feet < 2000) {
|
||||||
displayValue = context.getString(R.string.value_integer_feet, feet);
|
displayValue = context.getString(R.string.value_integer_feet, feet);
|
||||||
switch (value) {
|
|
||||||
case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT:
|
if (value == recordingGPSAccuracyDefault) {
|
||||||
options[i] = context.getString(R.string.value_integer_feet_recommended, feet);
|
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
|
||||||
break;
|
} else if (value == recordingGPSAccuracyExcellent) {
|
||||||
case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT:
|
entries[i] = context.getString(R.string.value_integer_feet_excellent_gps, feet);
|
||||||
options[i] = context.getString(R.string.value_integer_feet_excellent_gps, feet);
|
} else {
|
||||||
break;
|
entries[i] = displayValue;
|
||||||
default:
|
|
||||||
options[i] = displayValue;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
double mile = feet * UnitConversions.FT_TO_MI;
|
double mile = feet * UnitConversions.FT_TO_MI;
|
||||||
displayValue = context.getString(R.string.value_float_mile, mile);
|
displayValue = context.getString(R.string.value_float_mile, mile);
|
||||||
if (value == PreferencesUtils.RECORDING_GPS_ACCURACY_POOR) {
|
if (value == recordingGPSAccuracyPoor) {
|
||||||
options[i] = context.getString(R.string.value_float_mile_poor_gps, mile);
|
entries[i] = context.getString(R.string.value_float_mile_poor_gps, mile);
|
||||||
} else {
|
} else {
|
||||||
options[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static String[] getAutoResumeTrackTimeoutEntries(Context context) {
|
||||||
* Sets the auto resume track timeout options.
|
String[] entryValues = context.getResources().getStringArray(R.array.auto_resume_track_timeout_values);
|
||||||
*
|
String[] entries = new String[entryValues.length];
|
||||||
* @param options the options
|
|
||||||
* @param values the values
|
for (int i = 0; i < entryValues.length; i++) {
|
||||||
*/
|
int value = Integer.parseInt(entryValues[i]);
|
||||||
static void setAutoResumeTrackTimeoutOptions(Context context, String[] options, String[] values) {
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
int value = Integer.parseInt(values[i]);
|
|
||||||
if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
||||||
options[i] = context.getString(R.string.value_never);
|
entries[i] = context.getString(R.string.value_never);
|
||||||
} else if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
} else if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
||||||
options[i] = context.getString(R.string.value_always);
|
entries[i] = context.getString(R.string.value_always);
|
||||||
} else {
|
} else {
|
||||||
options[i] = context.getString(R.string.value_integer_minute, value);
|
entries[i] = context.getString(R.string.value_integer_minute, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return entries;
|
||||||
* Configures the bluetooth sensor.
|
|
||||||
*/
|
|
||||||
static void configureBluetoothSensorList(ListPreference preference) {
|
|
||||||
Context context = preference.getContext();
|
|
||||||
|
|
||||||
String value = PreferencesUtils.getString(context, R.string.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) {
|
|
||||||
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.settings_sensor_bluetooth_sensor_key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
devicesNameList.add(0, context.getString(R.string.value_none));
|
|
||||||
devicesAddressList.add(0, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
|
||||||
|
|
||||||
String[] values = devicesAddressList.toArray(new String[0]);
|
|
||||||
preference.setEntryValues(values);
|
|
||||||
|
|
||||||
String[] options = devicesNameList.toArray(new String[0]);
|
|
||||||
preference.setEntries(options);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,15 @@ import androidx.preference.PreferenceFragmentCompat;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||||
import de.dennisguse.opentracks.util.HackUtils;
|
import de.dennisguse.opentracks.util.HackUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ResetDialogPreference.ResetCallback {
|
public class SettingsActivity extends AppCompatActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ResetDialogPreference.ResetCallback {
|
||||||
|
|
||||||
|
private static final String TAG = SettingsActivity.class.getSimpleName();
|
||||||
|
|
||||||
private PrefsFragment prefsFragment;
|
private PrefsFragment prefsFragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,13 +57,14 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
|
||||||
@Override
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
@Override
|
||||||
updateUI();
|
public void run() {
|
||||||
}
|
updateReset();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -69,6 +73,14 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (PreferencesUtils.isKey(getActivity(), R.string.stats_rate_key, key)) {
|
||||||
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateSpeed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,14 +98,16 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
|
|
||||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||||
}
|
}
|
||||||
updateUnits();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
updateUI();
|
updateSpeed();
|
||||||
|
updateUnits();
|
||||||
|
updateReset();
|
||||||
|
updateBluetooth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,40 +116,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
PreferencesUtils.getSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.getSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configFrequencyPreference(int key, boolean metricUnits) {
|
|
||||||
ListPreference preference = findPreference(getString(key));
|
|
||||||
|
|
||||||
String[] options = StringUtils.getFrequencyOptions(getActivity(), metricUnits);
|
|
||||||
preference.setEntries(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configListPreference(int key, int valueArray, boolean metricUnits) {
|
|
||||||
String[] values = getResources().getStringArray(valueArray);
|
|
||||||
final String[] options = new String[values.length];
|
|
||||||
switch (key) {
|
|
||||||
case R.string.min_recording_interval_key:
|
|
||||||
PreferenceHelper.setMinRecordingIntervalOptions(getActivity(), options, values);
|
|
||||||
break;
|
|
||||||
case R.string.recording_distance_interval_key:
|
|
||||||
PreferenceHelper.setRecordingDistanceIntervalOptions(getActivity(), options, values, metricUnits);
|
|
||||||
break;
|
|
||||||
case R.string.max_recording_distance_key:
|
|
||||||
PreferenceHelper.setMaxRecordingDistanceOptions(getActivity(), options, values, metricUnits);
|
|
||||||
break;
|
|
||||||
case R.string.recording_gps_accuracy_key:
|
|
||||||
PreferenceHelper.setRecordingGpsAccuracyOptions(getActivity(), options, values, metricUnits);
|
|
||||||
break;
|
|
||||||
case R.string.auto_resume_track_timeout_key:
|
|
||||||
PreferenceHelper.setAutoResumeTrackTimeoutOptions(getActivity(), options, values);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ListPreference listPreference = findPreference(getString(key));
|
|
||||||
listPreference.setEntries(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayPreferenceDialog(Preference preference) {
|
public void onDisplayPreferenceDialog(Preference preference) {
|
||||||
DialogFragment dialogFragment = null;
|
DialogFragment dialogFragment = null;
|
||||||
@@ -144,6 +124,8 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
} else if (preference instanceof ActivityTypePreference) {
|
} else if (preference instanceof ActivityTypePreference) {
|
||||||
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
|
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
|
||||||
dialogFragment = activityPreferenceDialog;
|
dialogFragment = activityPreferenceDialog;
|
||||||
|
} else if (preference instanceof BluetoothLePreference) {
|
||||||
|
dialogFragment = BluetoothLePreference.BluetoothLePreferenceDialog.newInstance(preference.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialogFragment != null) {
|
if (dialogFragment != null) {
|
||||||
@@ -161,35 +143,51 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUI() {
|
private void updateReset() {
|
||||||
final boolean isRecording = PreferencesUtils.isRecording(getActivity());
|
final boolean isRecording = PreferencesUtils.isRecording(getActivity());
|
||||||
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
|
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
|
||||||
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
|
||||||
resetPreference.setEnabled(!isRecording);
|
resetPreference.setEnabled(!isRecording);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSpeed() {
|
||||||
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
|
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
|
||||||
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace);
|
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace);
|
||||||
|
}
|
||||||
|
|
||||||
ListPreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_sensor_key));
|
private void updateBluetooth() {
|
||||||
PreferenceHelper.configureBluetoothSensorList(bluetoothPreference);
|
// Disable Bluetooth preference if device does not have Bluetooth
|
||||||
|
BluetoothLePreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key));
|
||||||
|
bluetoothPreference.setVisible(BluetoothUtils.hasBluetooth(TAG));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUnits() {
|
private void updateUnits() {
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity());
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getActivity());
|
||||||
|
|
||||||
//TODO Refactor this!
|
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
|
||||||
configFrequencyPreference(R.string.voice_frequency_key, metricUnits);
|
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
|
||||||
configFrequencyPreference(R.string.split_frequency_key, metricUnits);
|
|
||||||
configListPreference(R.string.min_recording_interval_key, R.array.min_recording_interval_values, metricUnits);
|
|
||||||
configListPreference(R.string.recording_distance_interval_key, R.array.recording_distance_interval_values, metricUnits);
|
|
||||||
configListPreference(R.string.max_recording_distance_key, R.array.max_recording_distance_values, metricUnits);
|
|
||||||
configListPreference(R.string.recording_gps_accuracy_key, R.array.recording_gps_accuracy_values, metricUnits);
|
|
||||||
configListPreference(R.string.auto_resume_track_timeout_key, R.array.auto_resume_track_timeout_values, metricUnits);
|
|
||||||
configListPreference(R.string.auto_resume_track_timeout_key, R.array.auto_resume_track_timeout_values, metricUnits);
|
|
||||||
|
|
||||||
final ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
|
ListPreference splitFrequency = findPreference(getString(R.string.split_frequency_key));
|
||||||
String[] options = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
|
splitFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
|
||||||
statsRatePreferences.setEntries(options);
|
|
||||||
|
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
|
||||||
|
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
|
||||||
|
|
||||||
|
ListPreference recordingDistanceInterval = findPreference(getString(R.string.recording_distance_interval_key));
|
||||||
|
recordingDistanceInterval.setEntries(PreferenceHelper.getRecordingDistanceIntervalEntries(getActivity(), metricUnits));
|
||||||
|
|
||||||
|
ListPreference maxRecordingDistance = findPreference(getString(R.string.max_recording_distance_key));
|
||||||
|
maxRecordingDistance.setEntries(PreferenceHelper.getMaxRecordingDistanceEntries(getActivity(), metricUnits));
|
||||||
|
|
||||||
|
ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key));
|
||||||
|
recordingGpsAccuracy.setEntries(PreferenceHelper.getRecordingGpsAccuracyEntries(getActivity(), metricUnits));
|
||||||
|
|
||||||
|
ListPreference autoresumeTrack = findPreference(getString(R.string.auto_resume_track_timeout_key));
|
||||||
|
autoresumeTrack.setEntries(PreferenceHelper.getAutoResumeTrackTimeoutEntries(getActivity()));
|
||||||
|
|
||||||
|
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
|
||||||
|
String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
|
||||||
|
statsRatePreferences.setEntries(entries);
|
||||||
|
|
||||||
HackUtils.invalidatePreference(statsRatePreferences);
|
HackUtils.invalidatePreference(statsRatePreferences);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,13 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothClass;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.ArrayList;
|
||||||
import java.util.Set;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for dealing with bluetooth devices.
|
* Utilities for dealing with bluetooth devices.
|
||||||
@@ -30,29 +31,50 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public class BluetoothUtils {
|
public class BluetoothUtils {
|
||||||
|
|
||||||
|
public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
|
||||||
|
|
||||||
private BluetoothUtils() {
|
private BluetoothUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static BluetoothAdapter getDefaultBluetoothAdapter(final String TAG) {
|
||||||
* Populates the device names and the device addresses with all the suitable
|
// If from the main application thread, return directly
|
||||||
* bluetooth devices.
|
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
|
||||||
*
|
return BluetoothAdapter.getDefaultAdapter();
|
||||||
* @param bluetoothAdapter the bluetooth adapter
|
}
|
||||||
* @param deviceNames list of device names
|
|
||||||
* @param deviceAddresses list of device addresses
|
|
||||||
*/
|
|
||||||
public static void populateDeviceLists(BluetoothAdapter bluetoothAdapter, List<String> deviceNames, List<String> deviceAddresses) {
|
|
||||||
// Ensure the bluetooth adapter is not in discovery mode.
|
|
||||||
bluetoothAdapter.cancelDiscovery();
|
|
||||||
|
|
||||||
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
// Get the default adapter from the main application thread
|
||||||
for (BluetoothDevice device : pairedDevices) {
|
final ArrayList<BluetoothAdapter> adapters = new ArrayList<>(1);
|
||||||
BluetoothClass bluetoothClass = device.getBluetoothClass();
|
final Object mutex = new Object();
|
||||||
if (bluetoothClass != null && (bluetoothClass.getDeviceClass() & BluetoothClass.Device.HEALTH_PULSE_RATE) != 0) {
|
|
||||||
deviceAddresses.add(device.getAddress());
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
deviceNames.add(device.getName());
|
handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adapters.add(BluetoothAdapter.getDefaultAdapter());
|
||||||
|
synchronized (mutex) {
|
||||||
|
mutex.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while (adapters.isEmpty()) {
|
||||||
|
synchronized (mutex) {
|
||||||
|
try {
|
||||||
|
mutex.wait(UnitConversions.ONE_SECOND);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (adapters.get(0) == null) {
|
||||||
|
Log.w(TAG, "No bluetooth adapter found.");
|
||||||
|
}
|
||||||
|
return adapters.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasBluetooth(final String TAG) {
|
||||||
|
return BluetoothUtils.getDefaultBluetoothAdapter(TAG) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
public static int parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
||||||
|
|||||||
@@ -204,12 +204,13 @@ public class FileUtils {
|
|||||||
*
|
*
|
||||||
* @param name name
|
* @param name name
|
||||||
*/
|
*/
|
||||||
|
// TODO Check if this function is still needed.
|
||||||
public static String sanitizeFileName(String name) {
|
public static String sanitizeFileName(String name) {
|
||||||
StringBuilder builder = new StringBuilder(name.length());
|
StringBuilder builder = new StringBuilder(name.length());
|
||||||
for (int i = 0; i < name.length(); i++) {
|
for (int i = 0; i < name.length(); i++) {
|
||||||
int codePoint = name.codePointAt(i);
|
int codePoint = name.codePointAt(i);
|
||||||
char character = name.charAt(i);
|
char character = name.charAt(i);
|
||||||
if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) {
|
if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character) || character == '.') {
|
||||||
builder.appendCodePoint(codePoint);
|
builder.appendCodePoint(codePoint);
|
||||||
} else {
|
} else {
|
||||||
builder.append("_");
|
builder.append("_");
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class IntentUtils {
|
|||||||
}
|
}
|
||||||
String trackDescription = "";
|
String trackDescription = "";
|
||||||
if (trackIds.length == 1) {
|
if (trackIds.length == 1) {
|
||||||
Track track = ContentProviderUtils.Factory.get(context).getTrack(trackIds[0]);
|
Track track = new ContentProviderUtils(context).getTrack(trackIds[0]);
|
||||||
trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, false);
|
trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,13 +89,13 @@ public class IntentUtils {
|
|||||||
uris.add(uriAndMime.first);
|
uris.add(uriAndMime.first);
|
||||||
mime = uriAndMime.second;
|
mime = uriAndMime.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Intent(action)
|
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))
|
|
||||||
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
|
||||||
.setType(mime)
|
.setType(mime)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
.setAction(Intent.ACTION_SEND_MULTIPLE)
|
||||||
|
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.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));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
|
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ public class LocationUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given location is a valid (i.e. physically possible) locationon Earth.
|
* Checks if a given location is a valid (i.e. physically possible) location on Earth.
|
||||||
* Note: The special separator locations (which have latitude = 100) will not qualify as valid.
|
* Note: The special separator locations (which have latitude = 100) will not qualify as valid.
|
||||||
* Neither will locations with lat=0 and lng=0 as these are most likely "bad" measurements which often cause trouble.
|
* Neither will locations with lat=0 and lng=0 as these are most likely "bad" measurements which often cause trouble.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -32,41 +32,8 @@ import de.dennisguse.opentracks.R;
|
|||||||
*/
|
*/
|
||||||
public class PreferencesUtils {
|
public class PreferencesUtils {
|
||||||
|
|
||||||
/*
|
|
||||||
* Preferences values.
|
|
||||||
* The defaults need to match the defaults in the xml files.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
|
|
||||||
|
|
||||||
public static final String BLUETOOTH_SENSOR_DEFAULT = "";
|
public static final String BLUETOOTH_SENSOR_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;
|
|
||||||
|
|
||||||
private PreferencesUtils() {
|
private PreferencesUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,11 +66,8 @@ public class PreferencesUtils {
|
|||||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, getAutoResumeTrackCurrentRetryDefault(context) + 1);
|
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) {
|
public static String getDefaultActivity(Context context) {
|
||||||
return PreferencesUtils.getString(context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
|
return PreferencesUtils.getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setDefaultActivity(Context context, String newDefaultActivity) {
|
public static void setDefaultActivity(Context context, String newDefaultActivity) {
|
||||||
@@ -273,9 +237,13 @@ public class PreferencesUtils {
|
|||||||
return PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, AUTO_RESUME_TRACK_TIMEOUT);
|
return PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, AUTO_RESUME_TRACK_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean isRecordingTrackPaused(Context context) {
|
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, isRecordingTrackPausedDefault(context));
|
||||||
return PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
}
|
||||||
|
|
||||||
|
public static boolean isRecordingTrackPausedDefault(Context context) {
|
||||||
|
return context.getResources().getBoolean(R.bool.recording_track_paused_default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void defaultRecordingTrackPaused(Context context) {
|
public static void defaultRecordingTrackPaused(Context context) {
|
||||||
@@ -283,9 +251,12 @@ public class PreferencesUtils {
|
|||||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isChartByDistance(Context context) {
|
public static boolean isBluetoothHeartRateSensorAddressDefault(Context context) {
|
||||||
final String CHART_X_AXIS_DEFAULT = context.getString(R.string.chart_x_axis_default);
|
return PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(getBluetoothHeartRateSensorAddress(context));
|
||||||
return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_DEFAULT));
|
}
|
||||||
|
|
||||||
|
public static String getBluetoothHeartRateSensorAddress(Context context) {
|
||||||
|
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean shouldChartShowCadence(Context context) {
|
public static boolean shouldChartShowCadence(Context context) {
|
||||||
@@ -340,7 +311,11 @@ public class PreferencesUtils {
|
|||||||
|
|
||||||
public static int getRecordingDistanceInterval(Context context) {
|
public static int getRecordingDistanceInterval(Context context) {
|
||||||
final int RECORDING_DISTANCE_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
|
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);
|
return PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getRecordingDistanceIntervalDefault(Context context) {
|
||||||
|
return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getMaxRecordingDistance(Context context) {
|
public static int getMaxRecordingDistance(Context context) {
|
||||||
@@ -353,6 +328,22 @@ public class PreferencesUtils {
|
|||||||
return PreferencesUtils.getInt(context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
|
return PreferencesUtils.getInt(context, R.string.min_recording_interval_key, MIN_RECORDING_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getMinRecordingIntervalAdaptAccuracy(Context context) {
|
||||||
|
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_adapt_accuracy));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getMinRecordingIntervalAdaptBatteryLife(Context context) {
|
||||||
|
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_adapt_battery_life));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getMinRecordingIntervalDefault(Context context) {
|
||||||
|
return Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static int getRecordingGPSAccuracy(Context context) {
|
public static int getRecordingGPSAccuracy(Context context) {
|
||||||
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
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);
|
return PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class StatsUtils {
|
|||||||
public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
|
public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
|
||||||
// heart rate
|
// heart rate
|
||||||
int isVisible = View.VISIBLE;
|
int isVisible = View.VISIBLE;
|
||||||
if (!isRecording || PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(PreferencesUtils.getString(activity, R.string.settings_sensor_bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT))) {
|
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(activity)) {
|
||||||
isVisible = View.INVISIBLE;
|
isVisible = View.INVISIBLE;
|
||||||
}
|
}
|
||||||
activity.findViewById(R.id.stats_sensor_container).setVisibility(isVisible);
|
activity.findViewById(R.id.stats_sensor_container).setVisibility(isVisible);
|
||||||
@@ -125,11 +125,18 @@ public class StatsUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
heartRateValue.setText(heartRate);
|
setBluetoothHeartRateSensorName(activity, sensor, isRecording);
|
||||||
heartRateSensor.setText(sensor);
|
heartRateSensor.setText(sensor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setBluetoothHeartRateSensorName(Activity activity, String sensorName, boolean isRecording) {
|
||||||
|
if (isRecording) {
|
||||||
|
TextView heartRateSensor = activity.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||||
|
heartRateSensor.setText(sensorName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the total time value.
|
* Sets the total time value.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ public class StringUtils {
|
|||||||
String[] options = new String[values.length];
|
String[] options = new String[values.length];
|
||||||
for (int i = 0; i < values.length; i++) {
|
for (int i = 0; i < values.length; i++) {
|
||||||
int value = Integer.parseInt(values[i]);
|
int value = Integer.parseInt(values[i]);
|
||||||
if (value == PreferencesUtils.FREQUENCY_OFF) {
|
if (context.getString(R.string.frequency_off).equals(values[i])) {
|
||||||
options[i] = context.getString(R.string.value_off);
|
options[i] = context.getString(R.string.value_off);
|
||||||
} else if (value < 0) {
|
} else if (value < 0) {
|
||||||
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, Math.abs(value));
|
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, Math.abs(value));
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
|
|
||||||
|
public class TTSUtils {
|
||||||
|
|
||||||
|
private TTSUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getTTSStream() {
|
||||||
|
return TextToSpeech.Engine.DEFAULT_STREAM;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,22 +14,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/chart_view_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:orientation="vertical">
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical" />
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/chart_view_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical" />
|
|
||||||
|
|
||||||
<ZoomControls
|
|
||||||
android:id="@+id/chart_zoom_controls"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/white"
|
|
||||||
android:gravity="right"
|
|
||||||
android:paddingRight="8dp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/toolbar" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_feature_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_feature_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_track_map_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:text="@string/help_track_map_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_data_statistics_customized_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_data_statistics_customized_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_recording_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_recording_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_gps_imprecise_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_gps_imprecise_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_heart_rate_sensors_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_heart_rate_sensors_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_storage_pictures_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_storage_pictures_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_track_export_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_track_export_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_data_upload_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/generic_no" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_data_google_play_services_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/generic_no" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_data_online_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/generic_no" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_data_storage_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/help_data_storage_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -15,7 +15,8 @@ limitations under the License.
|
|||||||
-->
|
-->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<include layout="@layout/toolbar" />
|
<include layout="@layout/toolbar" />
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ limitations under the License.
|
|||||||
android:id="@+id/track_detail_markers"
|
android:id="@+id/track_detail_markers"
|
||||||
android:icon="@drawable/ic_marker_show_24dp"
|
android:icon="@drawable/ic_marker_show_24dp"
|
||||||
android:orderInCategory="5"
|
android:orderInCategory="5"
|
||||||
android:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:title="@string/menu_markers" />
|
android:title="@string/menu_markers" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_detail_edit"
|
android:id="@+id/track_detail_edit"
|
||||||
|
|||||||
@@ -27,12 +27,14 @@
|
|||||||
|
|
||||||
<string name="settings_reset_key" translatable="false">settingsReset</string>
|
<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_heart_rate_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="sensor_type_value_none" translatable="false">NONE</string>
|
||||||
|
|
||||||
<string name="default_activity_key" translatable="false">defaultActivity</string>
|
<string name="default_activity_key" translatable="false">defaultActivity</string>
|
||||||
|
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
|
||||||
<string-array name="activity_types">
|
<string-array name="activity_types">
|
||||||
|
<item>@string/default_activity_default</item>
|
||||||
|
|
||||||
<item>@string/activity_type_airplane</item>
|
<item>@string/activity_type_airplane</item>
|
||||||
<item>@string/activity_type_atv</item>
|
<item>@string/activity_type_atv</item>
|
||||||
<item>@string/activity_type_biking</item>
|
<item>@string/activity_type_biking</item>
|
||||||
@@ -94,7 +96,6 @@
|
|||||||
<item>@string/activity_type_trail_running</item>
|
<item>@string/activity_type_trail_running</item>
|
||||||
<item>@string/activity_type_train</item>
|
<item>@string/activity_type_train</item>
|
||||||
<item>@string/activity_type_ultimate_frisbee</item>
|
<item>@string/activity_type_ultimate_frisbee</item>
|
||||||
<item>@string/activity_type_unknown</item>
|
|
||||||
<item>@string/activity_type_wakeboarding</item>
|
<item>@string/activity_type_wakeboarding</item>
|
||||||
<item>@string/activity_type_walking</item>
|
<item>@string/activity_type_walking</item>
|
||||||
<item>@string/activity_type_water_skiing</item>
|
<item>@string/activity_type_water_skiing</item>
|
||||||
@@ -113,26 +114,13 @@
|
|||||||
<item>@string/auto_resume_track_timeout_always</item>
|
<item>@string/auto_resume_track_timeout_always</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string name="chart_x_axis_key" translatable="false">chartXAxis</string>
|
|
||||||
<string name="chart_x_axis_default" translatable="false">@string/chart_x_axis_distance</string>
|
|
||||||
<string-array name="chart_x_axis_values">
|
|
||||||
<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_key" translatable="false">splitFrequency</string>
|
||||||
<string name="split_frequency_default" translatable="false">@string/frequency_default</string>
|
<string name="split_frequency_default" translatable="false">@string/frequency_off</string>
|
||||||
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
|
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
|
||||||
<string name="voice_frequency_default" translatable="false">@string/frequency_default</string>
|
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
|
||||||
<string name="frequency_default" translatable="false">0</string>
|
<string name="frequency_off" translatable="false">0</string>
|
||||||
<string-array name="frequency_values">
|
<string-array name="frequency_values">
|
||||||
<item>@string/frequency_default</item>
|
<item>@string/frequency_off</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
<item>2</item>
|
<item>2</item>
|
||||||
<item>5</item>
|
<item>5</item>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- This file contains old and now unused settings. -->
|
||||||
|
<!-- It is recommended not to reuse the keys. -->
|
||||||
|
|
||||||
|
<string name="chart_x_axis_key" translatable="false">chartXAxis</string>
|
||||||
|
<string name="chart_x_axis_default" translatable="false">@string/chart_x_axis_distance</string>
|
||||||
|
<string-array name="chart_x_axis_values">
|
||||||
|
<item>@string/chart_x_axis_distance</item>
|
||||||
|
<item>@string/chart_x_axis_time</item>
|
||||||
|
</string-array>
|
||||||
|
<string name="chart_x_axis_time" translatable="false">TIME</string>
|
||||||
|
<string name="chart_x_axis_distance" translatable="false">DISTANCE</string>
|
||||||
|
<string-array name="chart_x_axis_options">
|
||||||
|
<item>@string/settings_chart_by_distance</item>
|
||||||
|
<item>@string/settings_chart_by_time</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
@@ -38,6 +38,95 @@ limitations under the License.
|
|||||||
Libraries
|
Libraries
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<!-- Help -->
|
||||||
|
<string name="help_feature_title">
|
||||||
|
OpenTracks has the following features:
|
||||||
|
</string>
|
||||||
|
<string name="help_feature_content">
|
||||||
|
* Create markers (incl. pictures)\n
|
||||||
|
* Heart rate sensor (Bluetooth LE)\n
|
||||||
|
* Voice announcements by time or distance\n
|
||||||
|
* While recording, show on lock screen\n
|
||||||
|
* Share tracks
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_data_statistics_customized_title">
|
||||||
|
Can the statistics view be customized?
|
||||||
|
</string>
|
||||||
|
<string name="help_data_statistics_customized_content">
|
||||||
|
Yes, it can be customized in the Settings.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_recording_title">
|
||||||
|
How does OpenTracks record data?
|
||||||
|
</string>
|
||||||
|
<string name="help_recording_content">
|
||||||
|
OpenTracks uses the GPS sensor of the device to record the location continuously.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_gps_imprecise_title">
|
||||||
|
Statistics (e.g., speed) reported by OpenTracks are imprecise?
|
||||||
|
</string>
|
||||||
|
<string name="help_gps_imprecise_content">
|
||||||
|
OpenTracks directly uses the data provided by the GPS of the mobile device.
|
||||||
|
If the GPS device reports inaccurate locations, OpenTracks cannot do anything about it.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_heart_rate_sensors_title">
|
||||||
|
Which heart rate sensors does OpenTracks support?
|
||||||
|
</string>
|
||||||
|
<string name="help_heart_rate_sensors_content">
|
||||||
|
OpenTracks should work with any Bluetooth LE heart sensor that support streaming the data in real-time.
|
||||||
|
Smartwatches that store the heart rate data are not supported.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_track_map_title">
|
||||||
|
How to show a track on a map?
|
||||||
|
</string>
|
||||||
|
<string name="help_track_map_content">
|
||||||
|
This requires a 3rd party application to be installed that can consume KMZ-encoded geo data.
|
||||||
|
This works well with <a href="http://osmand.net/">OsmAnd~</a> and <a href="https://maps.me/">Maps.ME</a>.
|
||||||
|
In fact, an App needs to support <i>ACTION_VIEW</i> with MIME <i>application/vnd.google-earth.kmz</i>.
|
||||||
|
At the moment, the recorded locations as well as the timestamps are send to an App while pictures and sensor data are not shared.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_storage_pictures_title">
|
||||||
|
How does OpenTracks store pictures that were taken as markers?
|
||||||
|
</string>
|
||||||
|
<string name="help_storage_pictures_content">
|
||||||
|
These are stored directly on the file system.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_track_export_title">
|
||||||
|
How can I export/share recorded tracks?
|
||||||
|
</string>
|
||||||
|
<string name="help_track_export_content">
|
||||||
|
Tracks can be exported from the Settings as KMZ, KML, or GPX.
|
||||||
|
KMZ contains all data incl. images, while KML contains only the sensor data, and GPX only the location.
|
||||||
|
Tracks shared via the <i>share icon</i> are shared as KMZ and any App can be used to send the data.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_data_upload_title">
|
||||||
|
Can OpenTracks upload the data automatically?
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_data_google_play_services_title">
|
||||||
|
Does OpenTracks require/use Google Play Service?
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_data_online_title">
|
||||||
|
Does OpenTracks use online services?
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_data_storage_title">
|
||||||
|
How does OpenTracks store sensor data?
|
||||||
|
</string>
|
||||||
|
<string name="help_data_storage_content">
|
||||||
|
OpenTracks stores sensor data per recorded location.
|
||||||
|
In terms of a heart rate sensor this means that only <i>one</i> heart rate measurement is stored per location.
|
||||||
|
If you are standing still or are indoors, no sensor data will be recorded (but it will be shown).
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Activity Type -->
|
<!-- Activity Type -->
|
||||||
<string name="activity_type_airplane" translation_description="When entering an 'Activity type', in the auto-complete text box, the
|
<string name="activity_type_airplane" translation_description="When entering an 'Activity type', in the auto-complete text box, the
|
||||||
completion suggestion for airplane.">
|
completion suggestion for airplane.">
|
||||||
@@ -1055,7 +1144,13 @@ limitations under the License.
|
|||||||
Sending
|
Sending
|
||||||
</string>
|
</string>
|
||||||
<string name="sensor_not_known" translation_description="Error message if sensor is not available in Android (e.g., pairing was removed in Bluetooth Settings).">
|
<string name="sensor_not_known" translation_description="Error message if sensor is not available in Android (e.g., pairing was removed in Bluetooth Settings).">
|
||||||
Sensor %1$s is not known. Please check the settings.
|
Sensor %1$s is unknown. Please check the settings.
|
||||||
|
</string>
|
||||||
|
<string name="sensor_could_not_scan">
|
||||||
|
Could not scan for Bluetooth devices (error %1$i).
|
||||||
|
</string>
|
||||||
|
<string name="bluetooth_disabled">
|
||||||
|
Please enable Bluetooth.
|
||||||
</string>
|
</string>
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="settings_not_while_recording" translation_description="In settings, the summary for an option that is not available during a
|
<string name="settings_not_while_recording" translation_description="In settings, the summary for an option that is not available during a
|
||||||
@@ -1220,7 +1315,7 @@ limitations under the License.
|
|||||||
</string>
|
</string>
|
||||||
|
|
||||||
<string name="settings_error_initial_values_restored" translation_description="If the ">
|
<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.
|
Sorry, but an error occurred. All settings have been reverted to the default values.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Settings Sensor -->
|
<!-- Settings Sensor -->
|
||||||
@@ -1419,6 +1514,9 @@ limitations under the License.
|
|||||||
<string name="track_record_notification" translation_description="When recording a track, the message on the OpenTracks notification
|
<string name="track_record_notification" translation_description="When recording a track, the message on the OpenTracks notification
|
||||||
informing the user that OpenTracks is actively recording his track.">
|
informing the user that OpenTracks is actively recording his track.">
|
||||||
Recording your track…
|
Recording your track…
|
||||||
|
</string>
|
||||||
|
<string name="track_recording_notification_accuracy">
|
||||||
|
Location accuracy: %1$s
|
||||||
</string>
|
</string>
|
||||||
<string name="track_markers" translation_description="Title indicating a particular track's markers.
|
<string name="track_markers" translation_description="Title indicating a particular track's markers.
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ limitations under the License.
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:title="@string/menu_settings">
|
android:title="@string/menu_settings">
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="@string/menu_help">
|
||||||
|
<Preference android:title="@string/menu_help">
|
||||||
|
<intent
|
||||||
|
android:targetClass="de.dennisguse.opentracks.HelpActivity"
|
||||||
|
android:targetPackage="@string/applicationId" />
|
||||||
|
</Preference>
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/track_detail_stats_tab">
|
<PreferenceCategory android:title="@string/track_detail_stats_tab">
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="@string/stats_units_default"
|
android:defaultValue="@string/stats_units_default"
|
||||||
@@ -46,13 +54,6 @@ limitations under the License.
|
|||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/track_detail_chart_tab">
|
<PreferenceCategory android:title="@string/track_detail_chart_tab">
|
||||||
<ListPreference
|
|
||||||
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:title="@string/settings_chart_x_axis"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="@string/chart_show_elevation_key"
|
android:key="@string/chart_show_elevation_key"
|
||||||
@@ -93,7 +94,7 @@ limitations under the License.
|
|||||||
android:title="@string/settings_recording_track_name_title"
|
android:title="@string/settings_recording_track_name_title"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="0"
|
android:defaultValue="@string/min_recording_interval_default"
|
||||||
android:entryValues="@array/min_recording_interval_values"
|
android:entryValues="@array/min_recording_interval_values"
|
||||||
android:key="@string/min_recording_interval_key"
|
android:key="@string/min_recording_interval_key"
|
||||||
android:title="@string/settings_recording_min_recording_interval_title"
|
android:title="@string/settings_recording_min_recording_interval_title"
|
||||||
@@ -111,37 +112,29 @@ limitations under the License.
|
|||||||
android:title="@string/settings_recording_max_recording_distance_title"
|
android:title="@string/settings_recording_max_recording_distance_title"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="50"
|
android:defaultValue="@string/recording_gps_accuracy_default"
|
||||||
android:entryValues="@array/recording_gps_accuracy_values"
|
android:entryValues="@array/recording_gps_accuracy_values"
|
||||||
android:key="@string/recording_gps_accuracy_key"
|
android:key="@string/recording_gps_accuracy_key"
|
||||||
android:title="@string/settings_recording_min_required_accuracy_title"
|
android:title="@string/settings_recording_min_required_accuracy_title"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="10"
|
android:defaultValue="@string/auto_resume_track_timeout_default"
|
||||||
android:entries="@array/auto_resume_track_timeout_values"
|
android:entries="@array/auto_resume_track_timeout_values"
|
||||||
android:entryValues="@array/auto_resume_track_timeout_values"
|
android:entryValues="@array/auto_resume_track_timeout_values"
|
||||||
android:key="@string/auto_resume_track_timeout_key"
|
android:key="@string/auto_resume_track_timeout_key"
|
||||||
android:title="@string/settings_recording_auto_resume_track_timeout_title"
|
android:title="@string/settings_recording_auto_resume_track_timeout_title"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
<de.dennisguse.opentracks.settings.ActivityTypePreference
|
<de.dennisguse.opentracks.settings.ActivityTypePreference
|
||||||
android:defaultValue="@string/activity_type_running"
|
android:defaultValue="@string/default_activity_default"
|
||||||
android:key="@string/default_activity_key"
|
android:key="@string/default_activity_key"
|
||||||
android:title="@string/settings_recording_default_activity_title" />
|
android:title="@string/settings_recording_default_activity_title" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/settings_sensor">
|
<PreferenceCategory android:title="@string/settings_sensor_bluetooth">
|
||||||
<ListPreference
|
<de.dennisguse.opentracks.settings.BluetoothLePreference
|
||||||
android:defaultValue="@string/sensor_type_value_none"
|
android:defaultValue="@string/sensor_type_value_none"
|
||||||
android:key="@string/settings_sensor_bluetooth_sensor_key"
|
android:key="@string/settings_sensor_bluetooth_heart_rate_key"
|
||||||
android:title="@string/settings_sensor_bluetooth_sensor"
|
android:title="@string/sensor_state_heart_rate" />
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
<Preference
|
|
||||||
android:key="@string/settings_sensor_bluetooth_pairing_key"
|
|
||||||
android:persistent="false"
|
|
||||||
android:summary="@string/settings_sensor_bluetooth_pairing_summary"
|
|
||||||
android:title="@string/settings_sensor_bluetooth_pairing">
|
|
||||||
<intent android:action="android.settings.BLUETOOTH_SETTINGS" />
|
|
||||||
</Preference>
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/generic_tracks">
|
<PreferenceCategory android:title="@string/generic_tracks">
|
||||||
|
|||||||
Reference in New Issue
Block a user