Compare commits

...

26 Commits

Author SHA1 Message Date
Dennis Guse 9bafc7d4a1 Release v3.2.9 2019-12-04 19:57:27 +01:00
Dennis Guse be58b48d20 Bugfix: StatsFragment (onResume and track was recording) overwrote in the total time UI until next UI update. 2019-12-03 22:21:45 +01:00
Dennis Guse 303abac980 Removed READ_PHONE_STATE permission and use AudioManager.MODE_IN_CALL instead. 2019-12-03 22:01:01 +01:00
Dennis Guse d1a459efc2 Get audio focus onStart. 2019-12-03 20:28:22 +01:00
Dennis Guse 96069e6f09 Cleanup. 2019-12-03 20:24:57 +01:00
Dennis Guse 301a721329 Settings: localize no heart rate sensor selected. 2019-12-03 19:53:14 +01:00
Dennis Guse e8c6801a32 Refactor StatsFragment. 2019-12-03 19:19:19 +01:00
Dennis Guse 355f3ef614 Added "screen always on"-feature to TrackDetailActivity.
And the "show on lockscreen"-feature is only used while recording.
2019-12-03 16:57:56 +01:00
Dennis Guse eaa6083586 Bugfix: Heart rate was not shown in stats. 2019-12-03 07:31:29 +01:00
Dennis Guse a0c5d02c51 Fixed and removed device features (AndroidManifest). 2019-12-03 07:31:29 +01:00
Dennis Guse 76f9f5fc82 MarkerDetailFragment now shows the blue pin as default image. 2019-12-03 07:31:29 +01:00
Dennis Guse 8e2598e93b Removed split markers. 2019-12-03 07:31:24 +01:00
Dennis Guse c0672c41df Bugfix: crash if heart rate sensor was null (i.e., preference not initialized). 2019-11-29 23:18:49 +01:00
Dennis Guse 4c6601fbc5 Release v3.2.8 2019-11-29 22:58:22 +01:00
edern-appere b99c87d1bd Added French translation for Fdroid. 2019-11-29 22:46:43 +01:00
Dennis Guse 66ffbfe951 Fixed ChartFragment tests.
Initialization of looper was missing.
2019-11-29 22:43:51 +01:00
Dennis Guse 8cb01a1027 Bugfix: sharing a track was not adding the file(s).
While migrating to enable sharing multiple tracks a configuration option was missed.
2019-11-29 22:39:36 +01:00
Dennis Guse 193e5eccbb Cleanup. 2019-11-29 22:39:36 +01:00
Dennis Guse bf7fc28bb0 Bugfix: picture name suffixes were malformed on export and thus could not be re-imported. 2019-11-29 22:39:31 +01:00
Dennis Guse e35cbc343a Add pull request template. 2019-11-29 07:54:39 +01:00
Dennis Guse 1c7b053c8c Fixed MarkerDetailActivity.
The orientation of the layout was not set and thus adding the toolbar broke the UI.
2019-11-28 18:09:11 +01:00
Dennis Guse 88cdcd4b47 Use GestureDetector rather than doing it on our own. 2019-11-27 21:42:05 +01:00
Dennis Guse 1432fed8f9 Removed zoom controls for ChartFragment. 2019-11-27 21:41:53 +01:00
Dennis Guse a14307b9a4 Moved Chart* into own package. 2019-11-27 20:19:12 +01:00
Dennis Guse 3bdce5aa08 Cleanup: use autocloseable. 2019-11-25 23:09:08 +01:00
Dennis Guse d7526f93b8 Cleanup: removed ContentProviderUtils interface. 2019-11-25 23:02:48 +01:00
70 changed files with 1842 additions and 2201 deletions
+1 -1
View File
@@ -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: ''
+10
View File
@@ -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)).
@@ -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,10 @@
v3.2.9: OpenTracks
Changes:
- Option to keep screen on while recording
- Markers: removed split markers completely
- Voice announcement now work without READ_PHONE_STATE permission
Bugfixes:
- Statistics: heart rate was not shown
- Statistics: time was briefly reset to 0s
@@ -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.
@@ -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}.
* *
@@ -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,6 +47,17 @@ 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() {
boolean chartByDistance = false; boolean chartByDistance = false;
@@ -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);
@@ -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);
@@ -151,27 +151,6 @@ public class TrackRecordingServiceTestLooper {
fullRecordingSession(); fullRecordingSession();
} }
@MediumTest
@Test
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.split_frequency_key, Integer.parseInt(context.getResources().getString(R.string.split_frequency_default)));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_splitFrequencyByDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_splitFrequencyByTime() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
fullRecordingSession();
}
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException { public void testWithProperties_metricUnitsDefault() throws TimeoutException {
@@ -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.
+3 -23
View File
@@ -16,8 +16,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks" package="de.dennisguse.opentracks"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="3149" android:versionCode="3175"
android:versionName="v3.2.7"> <!-- For FDroid. --> android:versionName="v3.2.9"> <!-- For FDroid. -->
<!-- Permission to initialize services --> <!-- Permission to initialize services -->
<application <application
android:name=".Startup" android:name=".Startup"
@@ -145,34 +145,17 @@ limitations under the License.
android:name="android.hardware.location.gps" android:name="android.hardware.location.gps"
android:required="true" /> android:required="true" />
<uses-feature <uses-feature
android:name="android.hardware.bluetooth" android:name="android.hardware.android.hardware.bluetooth_le"
android:required="false" /> android:required="false" />
<!-- Permissions to access sensors -->
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false" /> android:required="false" />
<uses-feature
android:name="android.hardware.faketouch"
android:required="false" />
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
<uses-feature <uses-feature
android:name="android.hardware.sensor.compass" android:name="android.hardware.sensor.compass"
android:required="false" /> android:required="false" />
<!-- Permission to read/write external storage -->
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony.gsm"
android:required="false" />
<!-- Required features -->
<uses-feature <uses-feature
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false" />
<!-- Optional features -->
<uses-feature
android:name="android.hardware.touchscreen.multitouch"
android:required="false" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -180,9 +163,6 @@ limitations under the License.
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest> </manifest>
@@ -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);
@@ -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<>();
@@ -71,8 +71,7 @@ public class MarkerEditActivity extends AbstractActivity {
waypointSection = findViewById(R.id.marker_edit_waypoint_section); waypointSection = findViewById(R.id.marker_edit_waypoint_section);
waypointName = findViewById(R.id.marker_edit_waypoint_name); waypointName = findViewById(R.id.marker_edit_waypoint_name);
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type); waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource( ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
waypointMarkerType.setAdapter(adapter); waypointMarkerType.setAdapter(adapter);
waypointDescription = findViewById(R.id.marker_edit_waypoint_description); waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
@@ -127,7 +126,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 +135,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 +181,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);
} }
} }
@@ -39,13 +39,13 @@ import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointsColumns; import de.dennisguse.opentracks.content.WaypointsColumns;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils; import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
/** /**
@@ -130,7 +130,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(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);
@@ -152,7 +152,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) { resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE);
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME); int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME); int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY); int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
@@ -161,12 +160,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE); int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE); int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)] == WaypointType.STATISTICS; int iconId = MarkerUtils.ICON_ID;
int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin;
String name = cursor.getString(nameIndex); String name = cursor.getString(nameIndex);
long time = cursor.getLong(timeIndex); long time = cursor.getLong(timeIndex);
String category = statistics ? null : cursor.getString(categoryIndex); String category = cursor.getString(categoryIndex);
String description = statistics ? null : cursor.getString(descriptionIndex); String description = cursor.getString(descriptionIndex);
String photoUrl = cursor.getString(photoUrlIndex); String photoUrl = cursor.getString(photoUrlIndex);
double latitude = cursor.getDouble(latitudeIndex); double latitude = cursor.getDouble(latitudeIndex);
double longitude = cursor.getDouble(longitudeIndex); double longitude = cursor.getDouble(longitudeIndex);
@@ -46,7 +46,6 @@ import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery; import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
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.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
@@ -55,6 +54,7 @@ import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils; import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
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.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -170,7 +170,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(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);
@@ -393,11 +393,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
} }
} }
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
resultMap.put(IS_RECORDING_FIELD, false); resultMap.put(IS_RECORDING_FIELD, false);
resultMap.put(IS_PAUSED_FIELD, true); resultMap.put(IS_PAUSED_FIELD, true);
resultMap.put(ICON_ID_FIELD, statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin); resultMap.put(ICON_ID_FIELD, MarkerUtils.ICON_ID);
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker); resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker);
resultMap.put(NAME_FIELD, waypoint.getName()); resultMap.put(NAME_FIELD, waypoint.getName());
// Display the marker's track name in the total time field // Display the marker's track name in the total time field
@@ -405,8 +403,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
resultMap.put(TOTAL_DISTANCE_FIELD, null); resultMap.put(TOTAL_DISTANCE_FIELD, null);
resultMap.put(MARKER_COUNT_FIELD, 0); resultMap.put(MARKER_COUNT_FIELD, 0);
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime()); resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory()); resultMap.put(CATEGORY_FIELD, waypoint.getCategory());
resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription()); resultMap.put(DESCRIPTION_FIELD, waypoint.getDescription());
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl()); resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId()); resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
resultMap.put(MARKER_ID_FIELD, waypoint.getId()); resultMap.put(MARKER_ID_FIELD, waypoint.getId());
@@ -55,7 +55,6 @@ 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;
@@ -127,16 +126,24 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
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)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this); recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
setLockscreenPolicy();
setScreenOnPolicy();
} }
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) { if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
setLockscreenPolicy();
setScreenOnPolicy();
} }
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) { if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
setLockscreenPolicy(); setLockscreenPolicy();
} }
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
setScreenOnPolicy();
}
if (key == null) return; if (key == null) return;
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -189,7 +196,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
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);
@@ -227,11 +234,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
@Override @Override
public void onAttachedToWindow() { public void onAttachedToWindow() {
setLockscreenPolicy(); setLockscreenPolicy();
setScreenOnPolicy();
super.onAttachedToWindow(); super.onAttachedToWindow();
} }
private void setLockscreenPolicy() { private void setLockscreenPolicy() {
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this); boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
&& PreferencesUtils.isRecording(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen); setShowWhenLocked(showOnLockScreen);
@@ -242,6 +251,17 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
} }
} }
private void setScreenOnPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
&& PreferencesUtils.isRecording(this);
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
@@ -413,22 +433,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
return trackDataHub; return trackDataHub;
} }
/**
* Gets the track id.
*/
// TODO Seems to be no used.
@Deprecated
public long getTrackId() {
return trackId;
}
private void setBluetoothHeartRateSensorName(String sensorName) {
StatsUtils.setBluetoothHeartRateSensorName(this, sensorName, isRecording());
}
/**
* Handles the data in the intent.
*/
private void handleIntent(Intent intent) { private void handleIntent(Intent intent) {
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L); trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L); long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
@@ -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);
@@ -245,7 +245,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = ContentProviderUtils.Factory.get(this); contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
@@ -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;
@@ -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,23 +27,29 @@ 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;
import de.dennisguse.opentracks.util.MarkerUtils;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions; 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 +97,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;
@@ -112,11 +116,76 @@ public class ChartView extends View {
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()) {
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) { public ChartView(Context context, boolean chartByDistance) {
super(context); super(context);
this.chartByDistance = chartByDistance; this.chartByDistance = chartByDistance;
@@ -195,12 +264,9 @@ public class ChartView extends View {
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp); pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight()); pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin); Drawable waypointMarker = MarkerUtils.getDefaultPhoto(context);
markerWidth = statisticsMarker.getIntrinsicWidth(); markerWidth = waypointMarker.getIntrinsicWidth();
markerHeight = statisticsMarker.getIntrinsicHeight(); markerHeight = waypointMarker.getIntrinsicHeight();
statisticsMarker.setBounds(0, 0, markerWidth, markerHeight);
Drawable waypointMarker = getResources().getDrawable(R.drawable.ic_marker_blue_pushpin);
waypointMarker.setBounds(0, 0, markerWidth, markerHeight); waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
scroller = new Scroller(context); scroller = new Scroller(context);
@@ -223,16 +289,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.
* *
@@ -411,79 +467,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);
} }
@@ -633,8 +624,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);
} }
@@ -734,8 +724,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);
@@ -870,8 +859,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();
@@ -468,12 +466,8 @@ public class TrackDataHub implements DataSourceListener {
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints); samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
} }
if (!LocationUtils.isValidLocation(location)) { if (!LocationUtils.isValidLocation(location)) { //This can be split markers (not anymore supported feature)
// TODO: also include the last valid point before a split
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSegmentSplit(location);
includeNextPoint = true; includeNextPoint = true;
}
} else { } else {
// Also include the last point if the selected track is not recording. // Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) { if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
@@ -51,14 +51,9 @@ public interface TrackDataListener {
*/ */
void onSampledOutTrackPoint(Location location); void onSampledOutTrackPoint(Location location);
/**
* Called when an invalid track point representing a segment split is read.
*/
void onSegmentSplit(Location location);
/** /**
* Called when finish sending new track points. * Called when finish sending new track points.
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)}, {@link #onSampledOutTrackPoint(Location)} and {@link #onSegmentSplit(Location)}. * This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)} and {@link #onSampledOutTrackPoint(Location)}.
*/ */
void onNewTrackPointsDone(); void onNewTrackPointsDone();
@@ -22,7 +22,6 @@ 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.Nullable;
@@ -33,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;
@@ -77,10 +76,9 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
// 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
@@ -89,8 +87,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
return; return;
} }
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
chartView.setShowPointer(isSelectedTrackRecording()); chartView.setShowPointer(isSelectedTrackRecording());
chartView.invalidate(); chartView.invalidate();
} }
@@ -112,21 +108,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
@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
@@ -216,13 +198,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
} }
} }
@Override
public void onSegmentSplit(Location location) {
if (isResumed()) {
fillDataPoint(location, null);
}
}
@Override @Override
public void onNewTrackPointsDone() { public void onNewTrackPointsDone() {
if (isResumed()) { if (isResumed()) {
@@ -401,18 +376,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
} }
} }
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.
* *
@@ -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));
@@ -41,12 +41,10 @@ import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.MarkerEditActivity; import de.dennisguse.opentracks.MarkerEditActivity;
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.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils; import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.StatsUtils; import de.dennisguse.opentracks.util.MarkerUtils;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
@@ -62,7 +60,7 @@ public class MarkerDetailFragment extends Fragment {
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND; private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND;
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private Handler handler; private Handler handler;
private ImageView photo; private ImageView photoView;
private ImageView textGradient; private ImageView textGradient;
private LinearLayout waypointInfo; private LinearLayout waypointInfo;
private Waypoint waypoint; private Waypoint waypoint;
@@ -110,7 +108,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);
} }
@@ -119,11 +117,11 @@ public class MarkerDetailFragment extends Fragment {
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.marker_detail_fragment, container, false); View view = inflater.inflate(R.layout.marker_detail_fragment, container, false);
photo = view.findViewById(R.id.marker_detail_waypoint_photo); photoView = view.findViewById(R.id.marker_detail_waypoint_photo);
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient); textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info); waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
photo.setOnClickListener(new View.OnClickListener() { photoView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
handler.removeCallbacks(hideText); handler.removeCallbacks(hideText);
@@ -156,11 +154,8 @@ public class MarkerDetailFragment extends Fragment {
@Override @Override
public void setMenuVisibility(boolean menuVisible) { public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible); super.setMenuVisibility(menuVisible);
/* // View pager caches the neighboring fragments in the resumed state.
* View pager caches the neighboring fragments in the resumed state. If // If becoming visible from the resumed state, update the UI to display the text above the image.
* becoming visible from the resumed state, update the UI to display the
* text above the image.
*/
if (isResumed()) { if (isResumed()) {
if (menuVisible) { if (menuVisible) {
updateUi(); updateUi();
@@ -214,27 +209,14 @@ public class MarkerDetailFragment extends Fragment {
} }
} }
/**
* Updates the UI.
*/
private void updateUi() { private void updateUi() {
View waypointView = getView().findViewById(R.id.marker_detail_waypoint);
View statisticsView = getView().findViewById(R.id.marker_detail_statistics);
boolean isWaypoint = waypoint.getType() == WaypointType.WAYPOINT;
waypointView.setVisibility(isWaypoint ? View.VISIBLE : View.GONE);
statisticsView.setVisibility(isWaypoint ? View.GONE : View.VISIBLE);
if (isWaypoint) {
boolean hasPhoto = waypoint.hasPhoto(); boolean hasPhoto = waypoint.hasPhoto();
photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
waypointInfo.setVisibility(View.VISIBLE);
if (hasPhoto) { if (hasPhoto) {
handler.removeCallbacks(hideText); handler.removeCallbacks(hideText);
photo.setImageURI(waypoint.getPhotoURI()); photoView.setImageURI(waypoint.getPhotoURI());
handler.postDelayed(hideText, HIDE_TEXT_DELAY); handler.postDelayed(hideText, HIDE_TEXT_DELAY);
} else {
photoView.setImageResource(MarkerUtils.ICON_ID);
} }
setName(R.id.marker_detail_waypoint_name, hasPhoto); setName(R.id.marker_detail_waypoint_name, hasPhoto);
@@ -246,15 +228,6 @@ public class MarkerDetailFragment extends Fragment {
ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto); ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto);
setLocation(R.id.marker_detail_waypoint_location, hasPhoto); setLocation(R.id.marker_detail_waypoint_location, hasPhoto);
} else {
setName(R.id.marker_detail_statistics_name, false);
setLocation(R.id.marker_detail_statistics_location, false);
Track track = contentProviderUtils.getTrack(waypoint.getTrackId());
StatsUtils.setTripStatisticsValues(getActivity(), waypoint.getTripStatistics(), null);
StatsUtils.setLocationValues(getActivity(), waypoint.getLocation(), false);
}
} }
private void setName(int resId, boolean addShadow) { private void setName(int resId, boolean addShadow) {
@@ -20,16 +20,18 @@ import android.location.Location;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import java.util.EnumSet; import java.util.EnumSet;
@@ -43,9 +45,11 @@ import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.ITrackRecordingService; import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
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.StatsUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
@@ -59,6 +63,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName(); public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND;
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
private Handler handlerUpdateUI; private Handler handlerUpdateUI;
@@ -67,18 +73,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private Location lastLocation = null; private Location lastLocation = null;
private TripStatistics lastTripStatistics = null; private TripStatistics lastTripStatistics = null;
private final Runnable updateUIeachSecond = new Runnable() {
public void run() {
if (isResumed() && isSelectedTrackRecording()) {
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime());
updateSensorDataUI();
}
handlerUpdateUI.postDelayed(this, UnitConversions.ONE_SECOND);
}
}
};
private String category = ""; private String category = "";
private int recordingGpsAccuracy; private int recordingGpsAccuracy;
@@ -87,6 +81,111 @@ public class StatsFragment extends Fragment implements TrackDataListener {
return inflater.inflate(R.layout.stats, container, false); return inflater.inflate(R.layout.stats, container, false);
} }
/* Views */
private View sensorContainerView;
private TextView heartRateValueView;
private TextView heartRateSensorView;
private TextView totalTimeValueView;
private final Runnable updateUIeachSecond = new Runnable() {
public void run() {
if (isResumed() && isSelectedTrackRecording()) {
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
updateTotalTime();
updateSensorDataUI();
}
handlerUpdateUI.postDelayed(this, UI_UPDATE_INTERVAL);
}
}
};
private TextView distanceValue;
private TextView distanceUnit;
private View activityLabel;
private Spinner activitySpinner;
private TextView movingTimeValue;
private TextView speedAvgLabel;
private TextView speedAvgValue;
private TextView speedAvgUnit;
private TextView speedMaxLabel;
private TextView speedMaxValue;
private TextView speedMaxUnit;
private TextView speedMovingLabel;
private TextView speedMovingValue;
private TextView speedMovingUnit;
private View gradeElevationSeparator;
private View gradeElevationContainer;
private TextView elevationMinValue;
private TextView elevationMinUnit;
private TextView elevationMaxValue;
private TextView elevationMaxUnit;
private View speedContainer;
private TextView speedLabel;
private TextView speedValue;
private TextView speedUnit;
private View elevationContainer;
private TextView elevationValue;
private TextView elevationUnit;
private View coordinateSeparator;
private View coordinateContainer;
private TextView latitudeValue;
private TextView longitudeValue;
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
sensorContainerView = view.findViewById(R.id.stats_sensor_container);
heartRateValueView = view.findViewById(R.id.stats_sensor_heart_rate_value);
heartRateSensorView = view.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
totalTimeValueView = view.findViewById(R.id.stats_total_time_value);
distanceValue = view.findViewById(R.id.stats_distance_value);
distanceUnit = view.findViewById(R.id.stats_distance_unit);
activityLabel = view.findViewById(R.id.stats_activity_type_label);
activitySpinner = view.findViewById(R.id.stats_activity_type_icon);
movingTimeValue = view.findViewById(R.id.stats_moving_time_value);
speedAvgLabel = view.findViewById(R.id.stats_average_speed_label);
speedAvgValue = view.findViewById(R.id.stats_average_speed_value);
speedAvgUnit = view.findViewById(R.id.stats_average_speed_unit);
speedMaxLabel = view.findViewById(R.id.stats_max_speed_label);
speedMaxValue = view.findViewById(R.id.stats_max_speed_value);
speedMaxUnit = view.findViewById(R.id.stats_max_speed_unit);
speedMovingLabel = view.findViewById(R.id.stats_moving_speed_label);
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
gradeElevationSeparator = view.findViewById(R.id.stats_elevation_separator);
gradeElevationContainer = view.findViewById(R.id.stats_elevation_container);
elevationMinValue = view.findViewById(R.id.stats_elevation_min_value);
elevationMinUnit = view.findViewById(R.id.stats_elevation_min_unit);
elevationMaxValue = view.findViewById(R.id.stats_elevation_max_value);
elevationMaxUnit = view.findViewById(R.id.stats_elevation_max_unit);
speedContainer = view.findViewById(R.id.stats_speed);
speedLabel = view.findViewById(R.id.stats_speed_label);
speedValue = view.findViewById(R.id.stats_speed_value);
speedUnit = view.findViewById(R.id.stats_speed_unit);
elevationContainer = view.findViewById(R.id.stats_elevation);
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
coordinateSeparator = view.findViewById(R.id.stats_coordinate_separator);
coordinateContainer = view.findViewById(R.id.stats_coordinate_container);
latitudeValue = view.findViewById(R.id.stats_latitude_value);
longitudeValue = view.findViewById(R.id.stats_longitude_value);
}
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
@@ -125,11 +224,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null); trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
trackRecordingServiceConnection.startConnection(getContext()); trackRecordingServiceConnection.startConnection(getContext());
updateUi(getActivity());
if (isSelectedTrackRecording()) {
handlerUpdateUI.post(updateUIeachSecond); handlerUpdateUI.post(updateUIeachSecond);
} }
}
@Override @Override
public void onPause() { public void onPause() {
@@ -145,6 +241,60 @@ public class StatsFragment extends Fragment implements TrackDataListener {
trackRecordingServiceConnection = null; trackRecordingServiceConnection = null;
} }
@Override
public void onDestroyView() {
super.onDestroyView();
sensorContainerView = null;
heartRateValueView = null;
heartRateSensorView = null;
totalTimeValueView = null;
distanceValue = null;
distanceUnit = null;
activityLabel = null;
activitySpinner = null;
movingTimeValue = null;
speedAvgLabel = null;
speedAvgValue = null;
speedAvgUnit = null;
speedMaxLabel = null;
speedMaxValue = null;
speedMaxUnit = null;
speedMovingLabel = null;
speedMovingValue = null;
speedMovingUnit = null;
gradeElevationSeparator = null;
gradeElevationContainer = null;
elevationMinValue = null;
elevationMinUnit = null;
elevationMaxValue = null;
elevationMaxUnit = null;
speedContainer = null;
speedLabel = null;
speedValue = null;
speedUnit = null;
elevationContainer = null;
elevationValue = null;
elevationUnit = null;
coordinateSeparator = null;
coordinateContainer = null;
latitudeValue = null;
longitudeValue = null;
}
@Override @Override
public void onTrackUpdated(final Track track) { public void onTrackUpdated(final Track track) {
if (isResumed()) { if (isResumed()) {
@@ -154,7 +304,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
if (isResumed()) { if (isResumed()) {
lastTripStatistics = track != null ? track.getTripStatistics() : null; lastTripStatistics = track != null ? track.getTripStatistics() : null;
category = track != null ? track.getCategory() : ""; category = track != null ? track.getCategory() : "";
updateUi(getActivity()); updateUI();
} }
} }
}); });
@@ -176,11 +326,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
lastLocation = location; lastLocation = location;
} }
@Override
public void onSegmentSplit(Location location) {
// We don't care.
}
@Override @Override
public void onNewTrackPointsDone() { public void onNewTrackPointsDone() {
if (isResumed()) { if (isResumed()) {
@@ -200,7 +345,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
lastLocation = null; lastLocation = null;
} }
} }
StatsUtils.setLocationValues(getActivity(), lastLocation, isSelectedTrackRecording()); setLocationValues();
} }
} }
}); });
@@ -229,7 +374,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
@Override @Override
public void run() { public void run() {
if (isResumed()) { if (isResumed()) {
updateUi(getActivity()); updateUI();
} }
} }
}); });
@@ -244,7 +389,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
@Override @Override
public void run() { public void run() {
if (isResumed()) { if (isResumed()) {
updateUi(getActivity()); updateUI();
} }
} }
}); });
@@ -300,6 +445,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
return trackDataHub != null && trackDataHub.isSelectedTrackPaused(); return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
} }
private void updateUI() {
updateStats();
setLocationValues();
updateSensorDataUI();
}
/** /**
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}. * Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}.
*/ */
@@ -314,16 +465,178 @@ public class StatsFragment extends Fragment implements TrackDataListener {
sensorDataSet = trackRecordingService.getSensorData(); sensorDataSet = trackRecordingService.getSensorData();
} }
StatsUtils.setSensorData(getActivity(), sensorDataSet, isSelectedTrackRecording()); setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
} }
/** private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
* Updates the UI. // heart rate
*/ int isVisible = View.VISIBLE;
private void updateUi(FragmentActivity activity) { if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext())) {
String trackIconValue = TrackIconUtils.getIconValue(activity, category); isVisible = View.INVISIBLE;
StatsUtils.setTripStatisticsValues(activity, lastTripStatistics, trackIconValue); }
StatsUtils.setLocationValues(activity, lastLocation, isSelectedTrackRecording()); sensorContainerView.setVisibility(isVisible);
updateSensorDataUI();
if (isRecording) {
String heartRate = getContext().getString(R.string.value_unknown);
String sensorName = getContext().getString(R.string.value_unknown);
if (sensorDataSet != null && sensorDataSet.isRecent(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)) {
sensorName = sensorDataSet.getSensorName();
if (sensorDataSet.hasHeartRate()) {
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
}
}
heartRateSensorView.setText(sensorName);
heartRateValueView.setText(heartRate);
}
}
private void updateStats() {
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
boolean isRecording = isSelectedTrackRecording();
// Set total distance
{
double totalDistance = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getTotalDistance();
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, metricUnits);
distanceValue.setText(parts.first);
distanceUnit.setText(parts.second);
}
// Set activity type
{
activityLabel.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
activitySpinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
activitySpinner.setEnabled(isRecording);
if (trackIconValue != null) {
TrackIconUtils.setIconSpinner(activitySpinner, trackIconValue);
}
}
// Set time
if (lastTripStatistics != null) {
movingTimeValue.setText(StringUtils.formatElapsedTime(lastTripStatistics.getMovingTime()));
updateTotalTime();
}
// Set average speed/pace
{
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageSpeed() : Double.NaN;
speedAvgLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
speedAvgValue.setText(parts.first);
speedAvgUnit.setText(parts.second);
}
// Set max speed/pace
{
double speed = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxSpeed();
speedMaxLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
speedMaxValue.setText(parts.first);
speedMaxUnit.setText(parts.second);
}
// Set moving speed/pace
{
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageMovingSpeed() : Double.NaN;
speedMovingLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
speedMovingValue.setText(parts.first);
speedMovingUnit.setText(parts.second);
}
// Set elevation
{
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(getContext());
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
if (showElevation) {
{
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation();
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
elevationMinValue.setText(parts.first);
elevationMinUnit.setText(parts.second);
}
{
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation();
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
elevationMaxValue.setText(parts.first);
elevationMaxUnit.setText(parts.second);
}
}
}
}
private void updateTotalTime() {
long totalTime;
if (isSelectedTrackRecording()) {
totalTime = System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime();
} else {
totalTime = lastTripStatistics.getTotalTime();
}
totalTimeValueView.setText(StringUtils.formatElapsedTime(totalTime));
}
private void setLocationValues() {
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
boolean isRecording = isSelectedTrackRecording();
// Set speed/pace
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
if (isRecording) {
speedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
double speed = lastLocation != null && lastLocation.hasSpeed() ? lastLocation.getSpeed() : Double.NaN;
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
speedValue.setText(parts.first);
speedUnit.setText(parts.second);
}
// Set elevation
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(getContext());
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
elevationValue.setText(parts.first);
elevationUnit.setText(parts.second);
}
// Set coordinate
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(getContext());
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
if (showCoordinate) {
double latitude = lastLocation != null ? lastLocation.getLatitude() : Double.NaN;
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
latitudeValue.setText(latitudeText);
double longitude = lastLocation != null ? lastLocation.getLongitude() : Double.NaN;
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
longitudeValue.setText(longitudeText);
}
} }
} }
@@ -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);
} }
@@ -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;
try {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId); KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream));
try (ByteArrayInputStream 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);
@@ -43,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;
@@ -55,7 +56,6 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory; import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor; import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.stats.TripStatisticsUpdater; import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
@@ -94,7 +94,6 @@ public class TrackRecordingService extends Service {
private Handler handler; private Handler handler;
private LocationManagerConnector locationManagerConnector; private LocationManagerConnector locationManagerConnector;
private PeriodicTaskExecutor voiceExecutor; private PeriodicTaskExecutor voiceExecutor;
private PeriodicTaskExecutor splitExecutor;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private TrackRecordingServiceNotificationManager notificationManager; private TrackRecordingServiceNotificationManager notificationManager;
private long recordingTrackId; private long recordingTrackId;
@@ -125,14 +124,10 @@ public class TrackRecordingService extends Service {
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) { if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context); boolean metricUnits = PreferencesUtils.isMetricUnits(context);
voiceExecutor.setMetricUnits(metricUnits); voiceExecutor.setMetricUnits(metricUnits);
splitExecutor.setMetricUnits(metricUnits);
} }
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) { if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context)); voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
} }
if (PreferencesUtils.isKey(context, R.string.split_frequency_key, key)) {
splitExecutor.setTaskFrequency(PreferencesUtils.getSplitFrequency(context));
}
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);
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) { if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
@@ -214,11 +209,10 @@ public class TrackRecordingService extends Service {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
executorService = Executors.newSingleThreadExecutor(); executorService = Executors.newSingleThreadExecutor();
contentProviderUtils = ContentProviderUtils.Factory.get(this); contentProviderUtils = new ContentProviderUtils(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());
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
notificationManager = new TrackRecordingServiceNotificationManager(this); notificationManager = new TrackRecordingServiceNotificationManager(this);
@@ -271,12 +265,6 @@ public class TrackRecordingService extends Service {
// unregister sharedPreferences before shutting down splitExecutor and voiceExecutor // unregister sharedPreferences before shutting down splitExecutor and voiceExecutor
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
try {
splitExecutor.shutdown();
} finally {
splitExecutor = null;
}
try { try {
voiceExecutor.shutdown(); voiceExecutor.shutdown();
} finally { } finally {
@@ -506,7 +494,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();
@@ -565,7 +553,6 @@ public class TrackRecordingService extends Service {
// Restore periodic tasks // Restore periodic tasks
voiceExecutor.restore(); voiceExecutor.restore();
splitExecutor.restore();
} }
/** /**
@@ -652,7 +639,6 @@ public class TrackRecordingService extends Service {
private void endRecording(boolean trackStopped, long trackId) { private void endRecording(boolean trackStopped, long trackId) {
// Shutdown periodic tasks // Shutdown periodic tasks
voiceExecutor.shutdown(); voiceExecutor.shutdown();
splitExecutor.shutdown();
// Update instance variables // Update instance variables
if (remoteSensorManager != null) { if (remoteSensorManager != null) {
@@ -839,7 +825,6 @@ public class TrackRecordingService extends Service {
Log.w(TAG, "SQLiteException", e); Log.w(TAG, "SQLiteException", e);
} }
voiceExecutor.update(); voiceExecutor.update();
splitExecutor.update();
} }
/** /**
@@ -86,11 +86,11 @@ public class BluetoothRemoteSensorManager {
@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_heart_rate_key, key)) { if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context); if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
stop(); stop();
return; return;
} }
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
if (bluetoothConnectionManager.isSameBluetoothDevice(address)) { if (bluetoothConnectionManager.isSameBluetoothDevice(address)) {
return; return;
} }
@@ -145,12 +145,11 @@ public class BluetoothRemoteSensorManager {
return; return;
} }
// String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
Log.w(TAG, "No bluetooth address."); Log.w(TAG, "No bluetooth address.");
return; return;
} }
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
Log.i(TAG, "Connecting to bluetooth address: " + address); Log.i(TAG, "Connecting to bluetooth address: " + address);
BluetoothDevice device; BluetoothDevice device;
@@ -21,12 +21,8 @@ import android.media.AudioManager;
import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener; import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.UtteranceProgressListener; import android.speech.tts.UtteranceProgressListener;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log; import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
@@ -34,7 +30,6 @@ 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;
/** /**
@@ -47,17 +42,49 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
/** /**
* The rate at which announcements are spoken. * The rate at which announcements are spoken.
*/ */
@VisibleForTesting private static final float TTS_SPEECH_RATE = 0.9f;
static final float TTS_SPEECH_RATE = 0.9f;
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName(); private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
private final Context context; private final Context context;
private final AudioManager audioManager; private final AudioManager audioManager;
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "Audio focus changed to " + focusChange);
boolean stop = false;
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
stop = false;
break;
case AudioManager.AUDIOFOCUS_LOSS:
stop = true;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
stop = true;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
stop = true;
break;
}
if (stop && tts != null && tts.isSpeaking()) {
tts.stop();
Log.i(TAG, "Aborting current tts due to focus change " + focusChange);
}
}
};
private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() { private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() {
@Override @Override
public void onStart(String utteranceId) { public void onStart(String utteranceId) {
int result = audioManager.requestAudioFocus(audioFocusChangeListener, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "Failed to request audio focus.");
}
} }
@Override @Override
@@ -70,32 +97,16 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
@Override @Override
public void onError(String utteranceId) { public void onError(String utteranceId) {
Log.e(TAG, "An error occurred for utteranceId " + utteranceId);
} }
}; };
private TextToSpeech tts; private TextToSpeech tts;
// Response from TTS after its initialization // Response from TTS after its initialization
private int initStatus = TextToSpeech.ERROR; private int ttsInitStatus = TextToSpeech.ERROR;
private boolean ttsReady = false; private boolean ttsReady = false;
// True if speech is allowed
private boolean speechAllowed;
/**
* Listener which updates {@link #speechAllowed} when the phone state changes.
*/
private final PhoneStateListener phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
if (!speechAllowed && tts != null && tts.isSpeaking()) {
// If we're already speaking, stop it.
tts.stop();
}
}
};
AnnouncementPeriodicTask(Context context) { AnnouncementPeriodicTask(Context context) {
this.context = context; this.context = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -103,16 +114,17 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
@Override @Override
public void start() { public void start() {
Log.d(TAG, "Start");
if (tts == null) { if (tts == null) {
tts = new TextToSpeech(context, new OnInitListener() { tts = new TextToSpeech(context, new OnInitListener() {
@Override @Override
public void onInit(int status) { public void onInit(int status) {
initStatus = status; Log.i(TAG, "TextToSpeech initialized with status " + status);
ttsInitStatus = status;
} }
}); });
} }
speechAllowed = true;
listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
} }
@Override @Override
@@ -129,8 +141,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
* *
* @param tripStatistics the trip statistics * @param tripStatistics the trip statistics
*/ */
@VisibleForTesting private void announce(TripStatistics tripStatistics) {
void announce(TripStatistics tripStatistics) {
if (tripStatistics == null) { if (tripStatistics == null) {
Log.e(TAG, "TripStatistics is null."); Log.e(TAG, "TripStatistics is null.");
return; return;
@@ -138,7 +149,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
synchronized (this) { synchronized (this) {
if (!ttsReady) { if (!ttsReady) {
ttsReady = initStatus == TextToSpeech.SUCCESS; ttsReady = ttsInitStatus == TextToSpeech.SUCCESS;
if (ttsReady) { if (ttsReady) {
onTtsReady(); onTtsReady();
} }
@@ -149,7 +160,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
} }
} }
if (!speechAllowed) { if (audioManager.getMode() == AudioManager.MODE_IN_CALL || audioManager.getMode() == AudioManager.MODE_IN_COMMUNICATION) {
Log.i(TAG, "Speech is not allowed at this time."); Log.i(TAG, "Speech is not allowed at this time.");
return; return;
} }
@@ -158,21 +169,18 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
@Override @Override
public void shutdown() { public void shutdown() {
listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_NONE);
if (tts != null) { if (tts != null) {
tts.shutdown(); tts.shutdown();
tts = null; tts = null;
} }
} }
/**
* Called when TTS is ready.
*/
private void onTtsReady() { private void onTtsReady() {
Locale locale = Locale.getDefault(); Locale locale = Locale.getDefault();
int languageAvailability = tts.isLanguageAvailable(locale); int languageAvailability = tts.isLanguageAvailable(locale);
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA if (languageAvailability == TextToSpeech.LANG_MISSING_DATA || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|| languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.w(TAG, "Default locale not available, use English."); Log.w(TAG, "Default locale not available, use English.");
locale = Locale.ENGLISH; locale = Locale.ENGLISH;
/* /*
@@ -188,42 +196,12 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
tts.setOnUtteranceProgressListener(utteranceListener); tts.setOnUtteranceProgressListener(utteranceListener);
} }
/**
* Speaks the announcement.
*
* @param announcement the announcement
*/
private void speakAnnouncement(String announcement) { private void speakAnnouncement(String announcement) {
int result = audioManager.requestAudioFocus(null, TTSUtils.getTTSStream(), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); // We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "Failed to request audio focus.");
}
/*
* We don't care about the utterance id. It is supplied here to force
* onUtteranceCompleted to be called.
*/
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used"); tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
} }
/** private String getAnnouncement(TripStatistics tripStatistics) {
* Create a new {@link TextToSpeech}.
*
* @param aContext a context
* @param onInitListener an on init listener
*/
@VisibleForTesting
protected TextToSpeech newTextToSpeech(Context aContext, OnInitListener onInitListener) {
return new TextToSpeech(aContext, onInitListener);
}
/**
* Gets the announcement.
*
* @param tripStatistics the trip statistics
*/
@VisibleForTesting
protected String getAnnouncement(TripStatistics tripStatistics) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context); boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context); boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM; double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
@@ -256,27 +234,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
getAnnounceTime(tripStatistics.getMovingTime()), rate); getAnnounceTime(tripStatistics.getMovingTime()), rate);
} }
/** private String getAnnounceTime(long time) {
* Listens to phone state.
*
* @param listener the listener
* @param events the interested events
*/
@VisibleForTesting
protected void listenToPhoneState(PhoneStateListener listener, int events) {
TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (telephony != null) {
telephony.listen(listener, events);
}
}
/**
* Gets the announce time.
*
* @param time the time
*/
@VisibleForTesting
String getAnnounceTime(long time) {
int[] parts = StringUtils.getTimeParts(time); int[] parts = StringUtils.getTimeParts(time);
String seconds = context.getResources() String seconds = context.getResources()
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]); .getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
@@ -1,41 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services.tasks;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.services.TrackRecordingService;
/**
* A simple task to insert statistics markers periodically.
*
* @author Sandor Dornbush
*/
public class SplitPeriodicTask implements PeriodicTask {
@Override
public void start() {
}
@Override
public void run(TrackRecordingService trackRecordingService) {
trackRecordingService.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
}
@Override
public void shutdown() {
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
/**
* A {@link PeriodicTaskFactory} for {@link SplitPeriodicTask}.
*
* @author Jimmy Shih
*/
public class SplitPeriodicTaskFactory implements PeriodicTaskFactory {
@Override
public PeriodicTask create(Context context) {
return new SplitPeriodicTask();
}
}
@@ -24,6 +24,7 @@ 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.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/** /**
* Preference to select a discoverable Bluetooth LE device. * Preference to select a discoverable Bluetooth LE device.
@@ -33,6 +34,8 @@ public class BluetoothLePreference extends DialogPreference {
private final static String TAG = BluetoothLePreference.class.getSimpleName(); private final static String TAG = BluetoothLePreference.class.getSimpleName();
private final static int DEVICE_NONE_RESOURCEID = R.string.value_none;
public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
} }
@@ -75,6 +78,10 @@ public class BluetoothLePreference extends DialogPreference {
@Override @Override
public CharSequence getSummary() { public CharSequence getSummary() {
if (getValue() == null || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext(), getValue())) {
return getContext().getString(DEVICE_NONE_RESOURCEID);
}
return getValue(); return getValue();
} }
@@ -142,12 +149,12 @@ public class BluetoothLePreference extends DialogPreference {
} }
String deviceNone = getContext().getString(R.string.sensor_type_value_none); String deviceNone = getContext().getString(R.string.sensor_type_value_none);
listAdapter.add(getContext().getString(R.string.value_none), deviceNone); listAdapter.add(getContext().getString(DEVICE_NONE_RESOURCEID), deviceNone);
selectedEntryIndex = 0; selectedEntryIndex = 0;
BluetoothLePreference preference = (BluetoothLePreference) getPreference(); BluetoothLePreference preference = (BluetoothLePreference) getPreference();
String deviceSelected = preference.getValue(); String deviceSelected = preference.getValue();
if (!deviceNone.equals(deviceSelected)) { if (deviceSelected != null && !deviceNone.equals(deviceSelected)) {
listAdapter.add(preference.getValue(), preference.getValue()); listAdapter.add(preference.getValue(), preference.getValue());
selectedEntryIndex = 1; selectedEntryIndex = 1;
} }
@@ -167,9 +167,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key)); ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits)); voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
ListPreference splitFrequency = findPreference(getString(R.string.split_frequency_key));
splitFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key)); ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity())); minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
@@ -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) {
@@ -0,0 +1,20 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.R;
public class MarkerUtils {
public static final int ICON_ID = R.drawable.ic_marker_blue_pushpin;
private MarkerUtils() {
}
public static Drawable getDefaultPhoto(@NonNull Context context) {
return context.getResources().getDrawable(ICON_ID);
}
}
@@ -32,8 +32,6 @@ import de.dennisguse.opentracks.R;
*/ */
public class PreferencesUtils { public class PreferencesUtils {
public static final String BLUETOOTH_SENSOR_DEFAULT = "";
private PreferencesUtils() { private PreferencesUtils() {
} }
@@ -251,12 +249,20 @@ 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 String getBluetoothHeartRateSensorAddressNone(Context context) {
return context.getString(R.string.sensor_type_value_none);
}
public static boolean isBluetoothHeartRateSensorAddressDefault(Context context) { public static boolean isBluetoothHeartRateSensorAddressDefault(Context context) {
return PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(getBluetoothHeartRateSensorAddress(context)); return getBluetoothHeartRateSensorAddressNone(context).equals(getBluetoothHeartRateSensorAddress(context));
}
public static boolean isBluetoothHeartRateSensorAddressDefault(Context context, String currentValue) {
return getBluetoothHeartRateSensorAddressNone(context).equals(currentValue);
} }
public static String getBluetoothHeartRateSensorAddress(Context context) { public static String getBluetoothHeartRateSensorAddress(Context context) {
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothHeartRateSensorAddressNone(context));
} }
public static boolean shouldChartShowCadence(Context context) { public static boolean shouldChartShowCadence(Context context) {
@@ -289,6 +295,11 @@ public class PreferencesUtils {
return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT); return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
} }
public static boolean shouldKeepScreenOn(Context context) {
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_keep_screen_on_while_recording_default);
return getBoolean(context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
}
public static boolean isShowStatsGradeElevation(Context context) { public static boolean isShowStatsGradeElevation(Context context) {
final boolean STATS_SHOW_GRADE_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_grade_elevation_default); final boolean STATS_SHOW_GRADE_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_grade_elevation_default);
return PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_GRADE_ELEVATION); return PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_GRADE_ELEVATION);
@@ -304,11 +315,6 @@ public class PreferencesUtils {
return PreferencesUtils.getInt(context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT); return PreferencesUtils.getInt(context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
} }
public static int getSplitFrequency(Context context) {
final int SPLIT_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.split_frequency_default));
return PreferencesUtils.getInt(context, R.string.split_frequency_key, SPLIT_FREQUENCY_DEFAULT);
}
public static int getRecordingDistanceInterval(Context context) { 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, getRecordingDistanceIntervalDefault(context)); return PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
@@ -1,283 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.util;
import android.app.Activity;
import android.location.Location;
import android.util.Pair;
import android.view.View;
import android.widget.Spinner;
import android.widget.TextView;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* Utilities for updating the statistics UI labels and values.
*
* @author Jimmy Shih
*/
public class StatsUtils {
private static final String GRADE_PERCENTAGE = "%";
private static final String GRADE_FORMAT = "%1$d";
private StatsUtils() {
}
/**
* Sets the location values.
*
* @param activity the activity for finding views
* @param location the location
* @param isRecording true if recording
*/
public static void setLocationValues(Activity activity, Location location, boolean isRecording) {
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
// Set speed/pace
View speedContainer = activity.findViewById(R.id.stats_speed);
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
if (isRecording) {
TextView speedLabel = activity.findViewById(R.id.stats_speed_label);
speedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
double speed = location != null && location.hasSpeed() ? location.getSpeed() : Double.NaN;
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
TextView speedValue = activity.findViewById(R.id.stats_speed_value);
speedValue.setText(parts.first);
TextView speedUnit = activity.findViewById(R.id.stats_speed_unit);
speedUnit.setText(parts.second);
}
// Set elevation
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(activity);
View elevationContainer = activity.findViewById(R.id.stats_elevation);
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
double altitude = location != null && location.hasAltitude() ? location.getAltitude() : Double.NaN;
Pair<String, String> parts = StringUtils.formatElevation(activity, altitude, metricUnits);
TextView elevationValue = activity.findViewById(R.id.stats_elevation_current_value);
elevationValue.setText(parts.first);
TextView elevationUnit = activity.findViewById(R.id.stats_elevation_current_unit);
elevationUnit.setText(parts.second);
}
// Set coordinate
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(activity);
View coordinateSeparator = activity.findViewById(R.id.stats_coordinate_separator);
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
View coordinateContainer = activity.findViewById(R.id.stats_coordinate_container);
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
if (showCoordinate) {
double latitude = location != null ? location.getLatitude() : Double.NaN;
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? activity.getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
TextView latitudeValue = activity.findViewById(R.id.stats_latitude_value);
latitudeValue.setText(latitudeText);
double longitude = location != null ? location.getLongitude() : Double.NaN;
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? activity.getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
TextView longitudeValue = activity.findViewById(R.id.stats_longitude_value);
longitudeValue.setText(longitudeText);
}
}
public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
// heart rate
int isVisible = View.VISIBLE;
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(activity)) {
isVisible = View.INVISIBLE;
}
activity.findViewById(R.id.stats_sensor_container).setVisibility(isVisible);
if (isRecording) {
TextView heartRateValue = activity.findViewById(R.id.stats_sensor_heart_rate_value);
TextView heartRateSensor = activity.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
String heartRate = activity.getString(R.string.value_unknown);
String sensor = activity.getString(R.string.value_unknown);
if (sensorDataSet != null && sensorDataSet.isRecent(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)) {
sensor = sensorDataSet.getSensorName();
if (sensorDataSet.hasHeartRate()) {
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
}
}
setBluetoothHeartRateSensorName(activity, sensor, isRecording);
heartRateSensor.setText(sensor);
}
}
public static void setBluetoothHeartRateSensorName(Activity activity, String sensorName, boolean isRecording) {
if (isRecording) {
TextView heartRateSensor = activity.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
heartRateSensor.setText(sensorName);
}
}
/**
* Sets the total time value.
*
* @param activity the activity
* @param totalTime the total time
*/
public static void setTotalTimeValue(Activity activity, long totalTime) {
TextView totalTimeValue = activity.findViewById(R.id.stats_total_time_value);
totalTimeValue.setText(StringUtils.formatElapsedTime(totalTime));
}
/**
* Sets the trip statistics values.
*
* @param activity the activity for finding views
* @param tripStatistics the trip statistics
* @param trackIconValue the track icon value or null to hide the track icon spinner
*/
public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics, String trackIconValue) {
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
boolean isRecording = PreferencesUtils.isRecording(activity);
// Set total distance
{
double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance();
Pair<String, String> parts = StringUtils.getDistanceParts(activity, totalDistance, metricUnits);
TextView distanceValue = activity.findViewById(R.id.stats_distance_value);
distanceValue.setText(parts.first);
TextView distanceUnit = activity.findViewById(R.id.stats_distance_unit);
distanceUnit.setText(parts.second);
}
// Set activity type
{
activity.findViewById(R.id.stats_activity_type_label).setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
Spinner spinner = activity.findViewById(R.id.stats_activity_type_icon);
spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
if (trackIconValue != null) {
TrackIconUtils.setIconSpinner(spinner, trackIconValue);
}
spinner.setEnabled(isRecording);
}
// Set time
if (tripStatistics != null) {
setTotalTimeValue(activity, tripStatistics.getTotalTime());
TextView movingTimeValue = activity.findViewById(R.id.stats_moving_time_value);
movingTimeValue.setText(StringUtils.formatElapsedTime(tripStatistics.getMovingTime()));
}
// Set average speed/pace
{
double speed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
TextView speedLabel = activity.findViewById(R.id.stats_average_speed_label);
speedLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
TextView speedValue = activity.findViewById(R.id.stats_average_speed_value);
speedValue.setText(parts.first);
TextView speedUnit = activity.findViewById(R.id.stats_average_speed_unit);
speedUnit.setText(parts.second);
}
// Set max speed/pace
{
double speed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed();
TextView speedLabel = activity.findViewById(R.id.stats_max_speed_label);
speedLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
TextView speedValue = activity.findViewById(R.id.stats_max_speed_value);
speedValue.setText(parts.first);
TextView speedUnit = activity.findViewById(R.id.stats_max_speed_unit);
speedUnit.setText(parts.second);
}
// Set average speed/pace
{
double speed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
TextView speedLabel = activity.findViewById(R.id.stats_average_speed_label);
speedLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
TextView speedValue = activity.findViewById(R.id.stats_average_speed_value);
speedValue.setText(parts.first);
TextView speedUnit = activity.findViewById(R.id.stats_average_speed_unit);
speedUnit.setText(parts.second);
}
// Set moving speed/pace
{
double speed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed() : Double.NaN;
TextView speedLabel = activity.findViewById(R.id.stats_moving_speed_label);
speedLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
TextView speedValue = activity.findViewById(R.id.stats_moving_speed_value);
speedValue.setText(parts.first);
TextView speedUnit = activity.findViewById(R.id.stats_moving_speed_unit);
speedUnit.setText(parts.second);
}
// Set elevation
{
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(activity);
View gradeElevationSeparator = activity.findViewById(R.id.stats_elevation_separator);
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
View gradeElevationContainer = activity.findViewById(R.id.stats_elevation_container);
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
if (showElevation) {
{
double elevation = tripStatistics == null ? Double.NaN : tripStatistics.getMinElevation();
Pair<String, String> parts = StringUtils.formatElevation(activity, elevation, metricUnits);
TextView elevationValue = activity.findViewById(R.id.stats_elevation_min_value);
elevationValue.setText(parts.first);
TextView elevationUnit = activity.findViewById(R.id.stats_elevation_min_unit);
elevationUnit.setText(parts.second);
}
{
double elevation = tripStatistics == null ? Double.NaN : tripStatistics.getMaxElevation();
Pair<String, String> parts = StringUtils.formatElevation(activity, elevation, metricUnits);
TextView elevationValue = activity.findViewById(R.id.stats_elevation_max_value);
elevationValue.setText(parts.first);
TextView elevationUnit = activity.findViewById(R.id.stats_elevation_max_unit);
elevationUnit.setText(parts.second);
}
}
}
}
}
@@ -132,7 +132,7 @@ public class StringUtils {
/** /**
* Format a decimal number while removing trailing zeros of the decimal part (if present). * Format a decimal number while removing trailing zeros of the decimal part (if present).
*/ */
static String formatDecimal(double value, int decimalPlaces) { public static String formatDecimal(double value, int decimalPlaces) {
if (decimalPlaces < 1) { if (decimalPlaces < 1) {
return Long.toString(Math.round(value)); return Long.toString(Math.round(value));
} }
@@ -158,7 +158,7 @@ public class StringUtils {
* @param metricUnits true to use metric unit * @param metricUnits true to use metric unit
* @return the formatted distance (or null) and it's unit as {@link Pair} * @return the formatted distance (or null) and it's unit as {@link Pair}
*/ */
static Pair<String, String> getDistanceParts(Context context, double distance, boolean metricUnits) { public static Pair<String, String> getDistanceParts(Context context, double distance, boolean metricUnits) {
if (Double.isNaN(distance) || Double.isInfinite(distance)) { if (Double.isNaN(distance) || Double.isInfinite(distance)) {
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet)); return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

-14
View File
@@ -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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/chart_view_layout" android:id="@+id/chart_view_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" /> 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>
@@ -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" />
@@ -18,7 +18,6 @@ limitations under the License.
android:layout_height="match_parent"> android:layout_height="match_parent">
<FrameLayout <FrameLayout
android:id="@+id/marker_detail_waypoint"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@@ -69,33 +68,4 @@ limitations under the License.
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
<ScrollView
android:id="@+id/marker_detail_statistics"
style="@style/StatsScrollView">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/marker_detail_statistics_name"
style="@style/TextMedium"
android:paddingTop="8dp" />
<TextView
android:id="@+id/marker_detail_statistics_location"
style="@style/TextSmall" />
<TextView
style="@style/TextSmall"
android:paddingTop="4dp"
android:paddingBottom="6dp"
android:text="@string/marker_detail_statistics_title" />
<include layout="@layout/stats_content" />
</LinearLayout>
</ScrollView>
</FrameLayout> </FrameLayout>
+1 -1
View File
@@ -41,7 +41,7 @@ limitations under the License.
<EditText <EditText
android:id="@+id/marker_edit_statistics_name" android:id="@+id/marker_edit_statistics_name"
style="@style/EditNameMarker" style="@style/EditNameMarker"
android:drawableLeft="@drawable/ic_marker_yellow_pushpin" android:drawableLeft="@drawable/ic_marker_blue_pushpin"
android:hint="@string/generic_name" android:hint="@string/generic_name"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:importantForAutofill="no" /> android:importantForAutofill="no" />
-1
View File
@@ -363,7 +363,6 @@ limitations under the License.
<LinearLayout style="@style/StatsLargeItemContainer"> <LinearLayout style="@style/StatsLargeItemContainer">
<TextView <TextView
android:id="@+id/stats_sensor_heart_rate_label"
style="@style/StatsLargeLabel" style="@style/StatsLargeLabel"
android:text="@string/sensor_state_heart_rate" /> android:text="@string/sensor_state_heart_rate" />
+3 -22
View File
@@ -25,6 +25,9 @@
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string> <string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool> <bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
<string name="stats_keep_screen_on_while_recording_key" translatable="false">trackdetail_keep_screen_on_while_recording</string>
<bool name="stats_keep_screen_on_while_recording_default" translatable="false">false</bool>
<string name="settings_reset_key" translatable="false">settingsReset</string> <string name="settings_reset_key" translatable="false">settingsReset</string>
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string> <string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
@@ -114,28 +117,6 @@
<item>@string/auto_resume_track_timeout_always</item> <item>@string/auto_resume_track_timeout_always</item>
</string-array> </string-array>
<string name="split_frequency_key" translatable="false">splitFrequency</string>
<string name="split_frequency_default" translatable="false">@string/frequency_off</string>
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
<string name="frequency_off" translatable="false">0</string>
<string-array name="frequency_values">
<item>@string/frequency_off</item>
<item>1</item>
<item>2</item>
<item>5</item>
<item>10</item>
<item>15</item>
<item>30</item>
<item>60</item>
<item>-1</item>
<item>-5</item>
<item>-10</item>
<item>-25</item>
<item>-50</item>
<item>-100</item>
</string-array>
<string name="recording_distance_interval_key" translatable="false">recordingDistanceInterval</string> <string name="recording_distance_interval_key" translatable="false">recordingDistanceInterval</string>
<string name="recording_distance_interval_default" translatable="false">10</string> <string name="recording_distance_interval_default" translatable="false">10</string>
<string-array name="recording_distance_interval_values"> <string-array name="recording_distance_interval_values">
@@ -15,4 +15,26 @@
<item>@string/settings_chart_by_distance</item> <item>@string/settings_chart_by_distance</item>
<item>@string/settings_chart_by_time</item> <item>@string/settings_chart_by_time</item>
</string-array> </string-array>
<string name="split_frequency_key" translatable="false">splitFrequency</string>
<string name="split_frequency_default" translatable="false">@string/frequency_off</string>
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
<string name="frequency_off" translatable="false">0</string>
<string-array name="frequency_values">
<item>@string/frequency_off</item>
<item>1</item>
<item>2</item>
<item>5</item>
<item>10</item>
<item>15</item>
<item>30</item>
<item>60</item>
<item>-1</item>
<item>-5</item>
<item>-10</item>
<item>-25</item>
<item>-50</item>
<item>-100</item>
</string-array>
</resources> </resources>
+7
View File
@@ -1194,6 +1194,13 @@ limitations under the License.
While recording, show statistics without unlocking the device. While recording, show statistics without unlocking the device.
</string> </string>
<string name="settings_recording_keep_screen_on_while_recording_title" translation_description="In the 'Settings' page, the option to change the 'show on lockscreen' behavior while recording.">
Keep screen on
</string>
<string name="settings_recording_keep_screen_on_while_recording_summary" translation_description="In the 'Settings' page, the option to change the 'show on lockscreen' behavior while recording.">
While recording, keep the screen on.
</string>
<string name="settings_recording" translation_description="In the 'Settings' page, the option to change the recording settings. <string name="settings_recording" translation_description="In the 'Settings' page, the option to change the recording settings.
Character limit: ~26 (1 line)"> Character limit: ~26 (1 line)">
+5 -6
View File
@@ -74,18 +74,17 @@ limitations under the License.
android:key="@string/stats_show_on_lockscreen_while_recording_key" android:key="@string/stats_show_on_lockscreen_while_recording_key"
android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary" android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary"
android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" /> android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" />
<CheckBoxPreference
android:defaultValue="@bool/stats_keep_screen_on_while_recording_default"
android:key="@string/stats_keep_screen_on_while_recording_key"
android:summary="@string/settings_recording_keep_screen_on_while_recording_summary"
android:title="@string/settings_recording_keep_screen_on_while_recording_title" />
<ListPreference <ListPreference
android:defaultValue="@string/voice_frequency_default" android:defaultValue="@string/voice_frequency_default"
android:entryValues="@array/frequency_values" android:entryValues="@array/frequency_values"
android:key="@string/voice_frequency_key" android:key="@string/voice_frequency_key"
android:title="@string/menu_voice_frequency" android:title="@string/menu_voice_frequency"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="@string/split_frequency_default"
android:entryValues="@array/frequency_values"
android:key="@string/split_frequency_key"
android:title="@string/menu_split_frequency"
app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
android:defaultValue="@string/track_name_default" android:defaultValue="@string/track_name_default"
android:entries="@array/track_name_options" android:entries="@array/track_name_options"