mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-24 11:15:18 +02:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1feb5cbb65 | |||
| 1d2fde6e69 | |||
| 20423121ee | |||
| ebf7b0a0c3 | |||
| 1189a07794 | |||
| 63e9041922 | |||
| 699be450b0 | |||
| 66b175c56c | |||
| 81a7aca4a0 | |||
| 065f42fbef | |||
| c92371b36a | |||
| 97c46183c3 | |||
| c02b8fab53 | |||
| 6540913d59 | |||
| b70c3be5af | |||
| 461f1c246b | |||
| 6d091dfa81 | |||
| 97ad6bc2f8 | |||
| 93ee8a53ed | |||
| bf9b6d6627 | |||
| 5f637952aa | |||
| 17de8cea6f | |||
| afea79476f | |||
| 7215a2def0 | |||
| b8d2d14d68 |
+1
-1
@@ -11,7 +11,7 @@ For the time being, the OpenTracks Data API can be used to implement mainly cust
|
||||
|
||||
## Implementation
|
||||
OpenTracks by default does not expose any data to 3rd-party applications.
|
||||
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary) as well as the `TrackPoint`s of one or more selected tracks.
|
||||
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary), the `TrackPoint`s, and the `Waypoint`s of one or more selected tracks.
|
||||
For each, one URI is provided.
|
||||
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
|
||||
|
||||
|
||||
+4
-24
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,6 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
def getVersionCode = { ->
|
||||
try {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'rev-list', 'HEAD', '--count'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return Integer.parseInt(stdout.toString().trim())
|
||||
}
|
||||
catch (ignored) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
def getVersionName = { ->
|
||||
try {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
@@ -64,9 +50,8 @@ android {
|
||||
applicationName: "@string/app_name"
|
||||
]
|
||||
|
||||
versionCode 3449
|
||||
//versionCode getVersionCode() //Useful for debugging.
|
||||
versionName "v3.7.6"
|
||||
versionCode 3474
|
||||
versionName "v3.8.1"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -111,13 +96,8 @@ dependencies {
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.0.0'
|
||||
|
||||
testImplementation 'androidx.test:core:1.2.0'
|
||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.4.4'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="ic_marker_orange_pushpin_with_shadow.svg">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient871"
|
||||
|
||||
x1="91.712517"
|
||||
y1="266.48581"
|
||||
x2="95.902588"
|
||||
y2="266.72339"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="rotate(22.027833,208.46275,168.26892)"
|
||||
|
||||
|
||||
>
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop867" />
|
||||
<stop
|
||||
style="stop-color:#898d8d;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop869" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.49497475"
|
||||
inkscape:cx="298.98541"
|
||||
inkscape:cy="561.0427"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1012"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1080"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
d="m 108.52826,213.29755 a 56.035402,7.0061195 5.281088 0 0 -22.425508,2.5321 56.035402,7.0061195 5.281088 0 0 -0.233576,0.17697 l -0.206706,-0.0237 c -0.04471,0.11554 -0.07454,0.2314 -0.09612,0.34752 a 56.035402,7.0061195 5.281088 0 0 0.337963,1.58159 c 0.980726,1.70884 3.760629,3.46326 6.583579,5.2188 l -22.744347,15.50009 a 67.064653,11.556784 4.2275244 0 0 -39.967588,5.26867 67.064653,11.556784 4.2275244 0 0 -0.729154,0.57564 67.064653,11.556784 4.2275244 0 0 -2.754353,1.36358 67.064653,11.556784 4.2275244 0 0 38.908736,16.04324 l -32.176847,27.0646 52.228334,-24.74551 a 67.064653,11.556784 4.2275244 0 0 71.411147,-3.8186 67.064653,11.556784 4.2275244 0 0 0.72812,-0.57507 67.064653,11.556784 4.2275244 0 0 2.75539,-1.36415 67.064653,11.556784 4.2275244 0 0 -12.38374,-10.59818 40.033569,5.0733922 5.2502179 0 0 0.0563,-0.0369 40.033569,5.0733922 5.2502179 0 0 0.37672,-0.34325 l 22.45186,-15.30075 c 8.23597,-0.65161 17.65581,-1.47811 22.94433,-3.00957 a 56.035402,7.0061195 5.281088 0 0 2.65514,-1.03772 56.035402,7.0061195 5.281088 0 0 -46.08401,-12.26953 56.035402,7.0061195 5.281088 0 0 -41.6357,-2.54991 z"
|
||||
style="fill:#333333;fill-opacity:0.49421525;stroke:#000000;stroke-width:0.138931px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.265501"
|
||||
id="path839" />
|
||||
<path
|
||||
style="fill:url(#linearGradient871);fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.265501"
|
||||
d="M 70.603317,174.30832 33.024148,288.94657 94.566672,183.19473 Z"
|
||||
id="path865"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<ellipse
|
||||
cx="144.42586"
|
||||
cy="113.06232"
|
||||
rx="70.318817"
|
||||
ry="39.974442"
|
||||
id="ellipse26"
|
||||
style="fill:#d45500;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
transform="rotate(22.027833)" />
|
||||
<ellipse
|
||||
style="fill:#ff6600;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
id="ellipse30"
|
||||
ry="39.974442"
|
||||
rx="70.318817"
|
||||
cy="105.23625"
|
||||
cx="145.017"
|
||||
transform="rotate(22.027833)" />
|
||||
<path
|
||||
id="rect28"
|
||||
style="fill:#d45500;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
d="m 85.662408,24.322061 c -0.890996,8.350369 2.928035,17.133472 6.825491,25.923906 l -23.333902,57.672763 -0.730686,1.80598 0.248621,0.10059 a 42.43035,17.360788 22.027833 0 0 33.303948,30.10067 42.43035,17.360788 22.027833 0 0 44.86507,1.52585 l 0.13892,0.0562 0.24519,-0.60599 a 42.43035,17.360788 22.027833 0 0 0.59472,-1.15581 42.43035,17.360788 22.027833 0 0 0.37668,-1.24514 l 22.45185,-55.492655 c 9.28986,-2.66566 20.09069,-6.13552 24.76397,-13.17485 z" />
|
||||
<ellipse
|
||||
style="fill:#ff6600;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
id="path24"
|
||||
ry="23.966745"
|
||||
rx="59.409279"
|
||||
cy="-10.262156"
|
||||
cx="148.14029"
|
||||
transform="rotate(22.027833)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,16 @@
|
||||
v3.8.0: OpenTracks
|
||||
|
||||
Breaking change:
|
||||
- GPX import/export speed, cadence, and heartrate via TrackPointExtension/v2
|
||||
|
||||
Changes:
|
||||
- Dashboard API: provide access to markers (incl. callback to show in OpenTracks)
|
||||
- New marker icon (orange push pin, SVG)
|
||||
- Files to import can be selected via file manager
|
||||
|
||||
Bugfixes:
|
||||
- Aggregated stats UI respects preferred rate
|
||||
- Editing a marker failed on orientation change
|
||||
|
||||
Developer:
|
||||
- Refactored TrackRecordingService
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.8.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- While recording: add option for fullscreen
|
||||
- Dashboard is started on the same task
|
||||
|
||||
Bugfixes:
|
||||
- Intent filters for importing files were far too optimistic
|
||||
@@ -58,7 +58,6 @@ public class SearchEngineTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
providerUtils = new ContentProviderUtils(context);
|
||||
|
||||
+1
-1
@@ -206,7 +206,7 @@ public class CustomContentProviderUtilsTest {
|
||||
public void testDeleteAllTracks() {
|
||||
// Insert track, points and waypoint at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
+4
-7
@@ -103,8 +103,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -138,8 +137,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -201,8 +199,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -229,7 +226,7 @@ public class ExportImportTest {
|
||||
assertWaypoints();
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, false, false);
|
||||
assertTrackpoints(false, true, true);
|
||||
}
|
||||
|
||||
private void assertWaypoints() {
|
||||
|
||||
+2
@@ -21,6 +21,8 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
|
||||
|
||||
/**
|
||||
* Tests the {@link AdaptiveLocationListenerPolicy}.
|
||||
*
|
||||
|
||||
+13
-11
@@ -172,7 +172,7 @@ public class TrackRecordingServiceTest {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
|
||||
// when
|
||||
service.pauseCurrentTrack();
|
||||
@@ -182,7 +182,7 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
//when
|
||||
service.resumeTrack(trackId);
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(service.isRecording());
|
||||
@@ -201,14 +201,14 @@ public class TrackRecordingServiceTest {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
service.endCurrentTrack();
|
||||
|
||||
Assert.assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(service.isRecording());
|
||||
@@ -294,7 +294,7 @@ public class TrackRecordingServiceTest {
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
long trackId = service.getRecordingTrackId();
|
||||
|
||||
// when
|
||||
@@ -331,18 +331,18 @@ public class TrackRecordingServiceTest {
|
||||
addTrack(dummyTrack, isRecording);
|
||||
}
|
||||
|
||||
private static void insertLocation(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||
insertLocation(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||
newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
static void insertLocation(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||
insertLocation(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||
static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private static void insertLocation(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
|
||||
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
|
||||
Location location = new Location("gps");
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
@@ -350,7 +350,9 @@ public class TrackRecordingServiceTest {
|
||||
location.setSpeed(speed);
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
trackRecordingService.insertLocation(location);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
|
||||
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
//TODO Needed?
|
||||
Thread.sleep(200);
|
||||
|
||||
+35
-82
@@ -72,14 +72,11 @@ public class TrackRecordingServiceTestLocation {
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||
//Disable executorService to not insert locations from GPS via LocationManager
|
||||
service.enableLocationExecutor(false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Reset service (if some previous test failed)
|
||||
service.enableLocationExecutor(true);
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
@@ -95,12 +92,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0005, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 5, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -112,29 +109,6 @@ public class TrackRecordingServiceTestLocation {
|
||||
Assert.assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_movingInaccurate() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.3, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 99.0, 35.0, Long.MAX_VALUE, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(1, trackPoints.size());
|
||||
Assert.assertEquals(45.0, trackPoints.get(0).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
|
||||
@@ -142,12 +116,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000005, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 5, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -184,12 +158,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 6, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -210,12 +184,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 6, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -253,12 +227,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
});
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 0, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 0, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -282,11 +256,11 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.1, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.1, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.2, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.2, 35.0, 5, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -303,25 +277,4 @@ public class TrackRecordingServiceTestLocation {
|
||||
Assert.assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_firstTrackPointInvalid() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
service.insertLocation(TrackPoint.createPause().getLocation());
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 0, 0);
|
||||
service.insertLocation(TrackPoint.createPause().getLocation());
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(1, trackPoints.size());
|
||||
Assert.assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -215,7 +216,9 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setSpeed(10);
|
||||
location.setTime(startTime + i * 10000);
|
||||
location.setBearing(3.0f);
|
||||
service.insertLocation(location);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
service.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
if (i % 7 == 0) {
|
||||
service.insertWaypoint(null, null, null, null);
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HandlerServerTest {
|
||||
|
||||
@Mock
|
||||
private Context context;
|
||||
|
||||
@Mock
|
||||
private HandlerServer.HandlerServerInterface server;
|
||||
|
||||
@Mock
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
private HandlerServer subject;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject = new HandlerServer(locationHandler, server);
|
||||
subject.start(context);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
subject.stop(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSharedPreferenceChanged() {
|
||||
// when
|
||||
subject.onSharedPreferenceChanged(null, null, null);
|
||||
|
||||
// then
|
||||
verify(locationHandler).onSharedPreferenceChanged(null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendTrackPoint() throws InterruptedException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
int accuracy = 50;
|
||||
|
||||
// when
|
||||
subject.sendTrackPoint(trackPoint, accuracy);
|
||||
|
||||
// then
|
||||
Thread.sleep(10); // Wait for executor service
|
||||
verify(server).newTrackPoint(trackPoint, accuracy);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LocationHandlerTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private HandlerServer handlerServer;
|
||||
|
||||
@InjectMocks
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onStart(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a valid location changed in LocationHandler -> newTrackPoint service method is called.
|
||||
*/
|
||||
@Test
|
||||
public void testOnLocationChanged_okay() {
|
||||
// when
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(1)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* When location changed in LocationHandler with bad location -> newTrackPoint service method is not called.
|
||||
*/
|
||||
@Test
|
||||
public void testOnLocationChanged_badLocation() {
|
||||
// given
|
||||
float latitude = 91f;
|
||||
|
||||
// when
|
||||
// bad latitude
|
||||
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(0)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* When location changed in LocationHandler with poor accuracy -> newTrackPoint service method is not called.
|
||||
*/
|
||||
@Test
|
||||
public void testOnLocationChanged_poorAccuracy() {
|
||||
// given
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
|
||||
// when
|
||||
// poor latitude
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
// no newTrackPoint called
|
||||
verify(handlerServer, times(0)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLocationChanged_movingInaccurate() {
|
||||
// when
|
||||
locationHandler.onLocationChanged(createLocation(45.0, 35.0, 5, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(45.1, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(45.2, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(45.3, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(1)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location with parameters and returns the Location object.
|
||||
*/
|
||||
private static Location createLocation(double latitude, double longitude, float accuracy, long speed, long time) {
|
||||
Location location = new Location("gps");
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
location.setAccuracy(accuracy);
|
||||
location.setSpeed(speed);
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
return location;
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -60,7 +60,7 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
// then
|
||||
Assert.assertEquals(1, aggregatedStatistics.getCount());
|
||||
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
@@ -82,7 +82,7 @@ public class AggregatedStatisticsTest {
|
||||
aggregatedStatistics.aggregate(track);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(aggregatedStatistics.get(mountainBiking) != null);
|
||||
Assert.assertNotNull(aggregatedStatistics.get(mountainBiking));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,7 +99,7 @@ public class AggregatedStatisticsTest {
|
||||
aggregatedStatistics.aggregate(track);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(aggregatedStatistics.get(trailRunning) != null);
|
||||
Assert.assertNotNull(aggregatedStatistics.get(trailRunning));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,7 +121,7 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
// then
|
||||
Assert.assertEquals(1, aggregatedStatistics.getCount());
|
||||
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
@@ -153,9 +153,9 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
// then
|
||||
Assert.assertEquals(3, aggregatedStatistics.getCount());
|
||||
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||
Assert.assertTrue(aggregatedStatistics.get(running) != null);
|
||||
Assert.assertTrue(aggregatedStatistics.get(walking) != null);
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(running));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(walking));
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
|
||||
@@ -215,10 +215,10 @@ public class AggregatedStatisticsTest {
|
||||
Assert.assertEquals(4, aggregatedStatistics.getCount());
|
||||
|
||||
// There is a map for every sport.
|
||||
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||
Assert.assertTrue(aggregatedStatistics.get(running) != null);
|
||||
Assert.assertTrue(aggregatedStatistics.get(walking) != null);
|
||||
Assert.assertTrue(aggregatedStatistics.get(driving) != null);
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(running));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(walking));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(driving));
|
||||
|
||||
// Number of tracks by sport.
|
||||
Assert.assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
|
||||
|
||||
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto">
|
||||
|
||||
@@ -24,6 +25,28 @@ limitations under the License.
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.compass"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
|
||||
<application
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
@@ -33,7 +56,6 @@ limitations under the License.
|
||||
android:label="${applicationName}"
|
||||
android:theme="@style/ThemeCustom">
|
||||
|
||||
<!-- Activities -->
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
<activity
|
||||
@@ -49,22 +71,45 @@ limitations under the License.
|
||||
<activity
|
||||
android:name=".io.file.importer.ImportActivity"
|
||||
android:theme="@style/ThemeCustomTransparentActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ATTACH_DATA" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/gpx" />
|
||||
<data android:mimeType="application/gpx+xml" />
|
||||
<data android:mimeType="application/vnd.google-earth.gpx" />
|
||||
<data android:mimeType="application/vnd.google-earth.gpx+xml" />
|
||||
<data android:scheme="file" />
|
||||
<data android:mimeType="application/vnd.google-earth.kmz" />
|
||||
<data
|
||||
android:scheme="content"
|
||||
android:pathPattern="*kmz" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/vnd.google-earth.kml+xml" />
|
||||
<data
|
||||
android:scheme="content"
|
||||
android:pathPattern="*kml" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/gpx+xml" />
|
||||
<data
|
||||
android:scheme="content"
|
||||
android:pathPattern="*gpx" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MarkerDetailActivity" />
|
||||
<activity android:name=".MarkerDetailActivity">
|
||||
<intent-filter>
|
||||
<action android:name="de.dennisguse.opentracks.MarkerDetails"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MarkerEditActivity" />
|
||||
|
||||
@@ -150,27 +195,4 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
</application>
|
||||
|
||||
<!-- Permissions to record locations -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.compass"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
</manifest>
|
||||
|
||||
@@ -3,11 +3,9 @@ package de.dennisguse.opentracks;
|
||||
import android.os.Bundle;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
|
||||
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
|
||||
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
|
||||
|
||||
public class AggregatedStatisticsActivity extends AbstractActivity {
|
||||
@@ -23,15 +21,12 @@ public class AggregatedStatisticsActivity extends AbstractActivity {
|
||||
listView.setEmptyView(findViewById(R.id.aggregated_stats_empty_view));
|
||||
|
||||
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
||||
viewModel.getAggregatedStats().observe(this, new Observer<AggregatedStatistics>() {
|
||||
@Override
|
||||
public void onChanged(AggregatedStatistics aggregatedStatistics) {
|
||||
if (aggregatedStatistics != null) {
|
||||
adapter = new AggregatedStatisticsAdapter(getApplicationContext(), aggregatedStatistics);
|
||||
listView.setAdapter(adapter);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
viewModel.getAggregatedStats().observe(this, aggregatedStatistics -> {
|
||||
if (aggregatedStatistics != null) {
|
||||
adapter = new AggregatedStatisticsAdapter(getApplicationContext(), aggregatedStatistics);
|
||||
listView.setAdapter(adapter);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,7 +29,6 @@ import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
@@ -59,13 +58,14 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String BUNDLE_PHOTO_URI = "photo_uri";
|
||||
|
||||
private static final int CAMERA_REQUEST_CODE = 5;
|
||||
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
||||
|
||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||
private long trackId;
|
||||
private long markerId;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
private Waypoint waypoint;
|
||||
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
@@ -80,15 +80,13 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
private EditText waypointDescription;
|
||||
private ImageView waypointPhoto;
|
||||
private ImageView waypointDeletePhotoBtn;
|
||||
private Button done;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
||||
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
|
||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||
|
||||
@@ -101,26 +99,65 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
|
||||
|
||||
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
|
||||
waypointDeletePhotoBtn.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (waypoint != null && waypoint.hasPhoto())
|
||||
waypoint.setPhotoUrl(null);
|
||||
waypointPhoto.setImageBitmap(null);
|
||||
photoUri = null;
|
||||
hideAndShowOptions();
|
||||
waypointDeletePhotoBtn.setOnClickListener(v -> {
|
||||
if (waypoint != null && waypoint.hasPhoto()) {
|
||||
waypoint.setPhotoUrl(null);
|
||||
}
|
||||
waypointPhoto.setImageBitmap(null);
|
||||
photoUri = null;
|
||||
hideAndShowOptions();
|
||||
});
|
||||
|
||||
Button cancel = findViewById(R.id.marker_edit_cancel);
|
||||
cancel.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
cancel.setOnClickListener(v -> finish());
|
||||
|
||||
final boolean isNewMarker = markerId == -1L;
|
||||
|
||||
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
|
||||
Button done = findViewById(R.id.marker_edit_done);
|
||||
done.setText(isNewMarker ? R.string.generic_add : R.string.generic_save);
|
||||
done.setOnClickListener(v -> {
|
||||
if (isNewMarker) {
|
||||
addMarker();
|
||||
} else {
|
||||
saveMarker();
|
||||
}
|
||||
finish();
|
||||
});
|
||||
done = findViewById(R.id.marker_edit_done);
|
||||
updateUiByMarkerId();
|
||||
|
||||
if (isNewMarker) {
|
||||
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
|
||||
if (nextWaypointNumber == -1) {
|
||||
nextWaypointNumber = 0;
|
||||
}
|
||||
waypointName.setText(getString(R.string.marker_name_format, nextWaypointNumber));
|
||||
waypointName.selectAll();
|
||||
waypointMarkerType.setText("");
|
||||
waypointDescription.setText("");
|
||||
} else {
|
||||
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
|
||||
if (waypoint == null) {
|
||||
Log.d(TAG, "waypoint is null");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
waypointName.setText(waypoint.getName());
|
||||
waypointMarkerType.setText(waypoint.getCategory());
|
||||
waypointDescription.setText(waypoint.getDescription());
|
||||
if (waypoint.hasPhoto()) {
|
||||
photoUri = waypoint.getPhotoURI();
|
||||
}
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
photoUri = savedInstanceState.getParcelable(BUNDLE_PHOTO_URI);
|
||||
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||
}
|
||||
if (photoUri != null) {
|
||||
setWaypointImageView(photoUri);
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,6 +166,12 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(BUNDLE_PHOTO_URI, photoUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
@@ -198,54 +241,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the marker id.
|
||||
*/
|
||||
private void updateUiByMarkerId() {
|
||||
final boolean newMarker = markerId == -1L;
|
||||
|
||||
setTitle(newMarker ? R.string.menu_insert_marker : R.string.menu_edit);
|
||||
done.setText(newMarker ? R.string.generic_add : R.string.generic_save);
|
||||
done.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (newMarker) {
|
||||
addMarker();
|
||||
} else {
|
||||
saveMarker();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
if (newMarker) {
|
||||
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
|
||||
if (nextWaypointNumber == -1) {
|
||||
nextWaypointNumber = 0;
|
||||
}
|
||||
waypointName.setText(getString(R.string.marker_name_format, nextWaypointNumber));
|
||||
waypointName.selectAll();
|
||||
waypointMarkerType.setText("");
|
||||
waypointDescription.setText("");
|
||||
} else {
|
||||
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
|
||||
if (waypoint == null) {
|
||||
Log.d(TAG, "waypoint is null");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
waypointName.setText(waypoint.getName());
|
||||
waypointMarkerType.setText(waypoint.getCategory());
|
||||
waypointDescription.setText(waypoint.getDescription());
|
||||
if (waypoint.hasPhoto()) {
|
||||
photoUri = waypoint.getPhotoURI();
|
||||
setWaypointImageView(photoUri);
|
||||
}
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and hide/shows all buttons/options about marker photo options.
|
||||
*
|
||||
@@ -272,13 +267,13 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
*
|
||||
* @param uri the uri photo.
|
||||
*/
|
||||
private void setWaypointImageView(Uri uri) {
|
||||
private void setWaypointImageView(@NonNull Uri uri) {
|
||||
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
|
||||
FileDescriptor fd = pfd.getFileDescriptor();
|
||||
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
|
||||
waypointPhoto.setImageBitmap(bitmap);
|
||||
hideAndShowOptions();
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
@@ -295,9 +290,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker.
|
||||
*/
|
||||
private void addMarker() {
|
||||
trackRecordingServiceConnection.addMarker(this,
|
||||
waypointName.getText().toString(),
|
||||
@@ -306,9 +298,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
photoUri != null ? photoUri.toString() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a marker.
|
||||
*/
|
||||
private void saveMarker() {
|
||||
waypoint.setName(waypointName.getText().toString());
|
||||
waypoint.setCategory(waypointMarkerType.getText().toString());
|
||||
|
||||
@@ -26,8 +26,6 @@ import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -82,7 +80,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(() -> MarkerListActivity.this.invalidateOptionsMenu());
|
||||
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -129,13 +127,10 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
listView = findViewById(R.id.marker_list);
|
||||
listView.setEmptyView(findViewById(R.id.marker_list_empty));
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
|
||||
startActivity(intent);
|
||||
}
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
|
||||
startActivity(intent);
|
||||
});
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@Override
|
||||
@@ -272,8 +267,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_select_all:
|
||||
int size = listView.getCount();
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int i = 0; i < listView.getCount(); i++) {
|
||||
listView.setItemChecked(i, true);
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -26,8 +26,6 @@ import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
@@ -203,20 +201,17 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
ListView listView = findViewById(R.id.search_list);
|
||||
listView.setAdapter(arrayAdapter);
|
||||
listView.setEmptyView(findViewById(R.id.search_list_empty));
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
|
||||
if (markerId != null) {
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_MARKER_ID, markerId);
|
||||
} else {
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(intent);
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
|
||||
if (markerId != null) {
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_MARKER_ID, markerId);
|
||||
} else {
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(intent);
|
||||
});
|
||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
handleIntent(getIntent());
|
||||
|
||||
@@ -18,7 +18,7 @@ import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
*/
|
||||
public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
|
||||
|
||||
private TrackFileFormat trackFileFormat;
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
|
||||
protected ShowOnMapProxyActivity(TrackFileFormat trackFileFormat) {
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.app.Application;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -25,6 +27,10 @@ public class Startup extends Application {
|
||||
|
||||
//Set default values of preferences on first start.
|
||||
PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
|
||||
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
|
||||
}
|
||||
|
||||
|
||||
//In debug builds: show thread and VM warnings.
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
@@ -89,20 +88,12 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
activityType.setAdapter(adapter);
|
||||
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
activityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position))));
|
||||
activityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position)));
|
||||
}
|
||||
});
|
||||
activityType.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackEditActivity.this, activityType.getText().toString()));
|
||||
}
|
||||
TrackEditActivity.this, activityType.getText().toString()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -116,37 +107,28 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
activityTypeIcon = findViewById(R.id.track_edit_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue));
|
||||
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
description = findViewById(R.id.track_edit_description);
|
||||
description.setText(track.getDescription());
|
||||
|
||||
Button saveButton = findViewById(R.id.track_edit_save);
|
||||
saveButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
|
||||
activityType.getText().toString(), description.getText().toString(),
|
||||
contentProviderUtils);
|
||||
finish();
|
||||
}
|
||||
saveButton.setOnClickListener(v -> {
|
||||
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
|
||||
activityType.getText().toString(), description.getText().toString(),
|
||||
contentProviderUtils);
|
||||
finish();
|
||||
});
|
||||
|
||||
Button cancel = findViewById(R.id.track_edit_cancel);
|
||||
@@ -155,12 +137,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
cancel.setVisibility(View.GONE);
|
||||
} else {
|
||||
setTitle(R.string.menu_edit);
|
||||
cancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
cancel.setOnClickListener(v -> finish());
|
||||
cancel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,6 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -47,8 +45,6 @@ import androidx.loader.app.LoaderManager.LoaderCallbacks;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
@@ -229,31 +225,23 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Show trackController when search dialog is dismissed
|
||||
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
||||
if (searchManager != null) {
|
||||
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
trackController.show();
|
||||
}
|
||||
});
|
||||
searchManager.setOnDismissListener(() -> trackController.show());
|
||||
}
|
||||
|
||||
listView = findViewById(R.id.track_list);
|
||||
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long trackId) {
|
||||
Intent newIntent;
|
||||
if (trackId == recordingTrackId) {
|
||||
// Is recording -> open record activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(newIntent);
|
||||
listView.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||
Intent newIntent;
|
||||
if (trackId == recordingTrackId) {
|
||||
// Is recording -> open record activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(newIntent);
|
||||
});
|
||||
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@@ -290,7 +278,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
|
||||
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
|
||||
showStartupDialogs();
|
||||
|
||||
requestGPSPermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -436,19 +425,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows start up dialogs.
|
||||
*/
|
||||
private void showStartupDialogs() {
|
||||
// If stats_units_key is undefined, set it
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
|
||||
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
|
||||
}
|
||||
|
||||
requestGPSPermissions();
|
||||
}
|
||||
|
||||
private void requestGPSPermissions() {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_REQUEST_CODE);
|
||||
@@ -482,7 +458,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentDashboardUtils.startDashboard(this, trackIds);
|
||||
IntentDashboardUtils.startDashboard(this, trackIds, false);
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||
|
||||
@@ -187,7 +187,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentDashboardUtils.startDashboard(this, trackId);
|
||||
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, false);
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
|
||||
@@ -110,6 +110,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
|
||||
setFullscreenPolicy();
|
||||
}
|
||||
|
||||
if (key == null) return;
|
||||
|
||||
@@ -190,12 +193,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
public void onAttachedToWindow() {
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
setFullscreenPolicy();
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
@@ -207,8 +210,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
@@ -217,6 +219,16 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
}
|
||||
|
||||
private void setFullscreenPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -302,7 +314,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentDashboardUtils.startDashboard(this, trackId);
|
||||
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, true);
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
|
||||
@@ -17,8 +17,8 @@ import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
|
||||
|
||||
public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
|
||||
private AggregatedStatistics aggregatedStatistics;
|
||||
private Context context;
|
||||
private final AggregatedStatistics aggregatedStatistics;
|
||||
private final Context context;
|
||||
|
||||
public AggregatedStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
|
||||
this.context = context;
|
||||
@@ -83,12 +83,12 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
protected TextView distanceUnit;
|
||||
protected TextView time;
|
||||
protected boolean metricsUnits;
|
||||
|
||||
public ViewHolder() {
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||
}
|
||||
protected boolean reportSpeed;
|
||||
|
||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context, name);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||
|
||||
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
|
||||
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
|
||||
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
|
||||
@@ -128,14 +128,14 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, true);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
avgSpeed.setText(parts.first);
|
||||
avgSpeedUnit.setText(parts.second);
|
||||
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, true);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
maxSpeed.setText(parts.first);
|
||||
maxSpeedUnit.setText(parts.second);
|
||||
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
|
||||
@@ -164,14 +164,14 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
maxPaceLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, false);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
avgPace.setText(parts.first);
|
||||
avgPaceUnit.setText(parts.second);
|
||||
avgPaceLabel.setText(R.string.stats_average_moving_pace);
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, false);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
maxPace.setText(parts.first);
|
||||
maxPaceUnit.setText(parts.second);
|
||||
maxPaceLabel.setText(R.string.stats_fastest_pace);
|
||||
|
||||
@@ -108,28 +108,25 @@ public class SearchEngine {
|
||||
/**
|
||||
* Comparator for scored results.
|
||||
*/
|
||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = new Comparator<ScoredResult>() {
|
||||
@Override
|
||||
public int compare(ScoredResult r1, ScoredResult r2) {
|
||||
// Score ordering.
|
||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||
if (scoreDiff != 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
// Make tracks come before waypoints.
|
||||
if (r1.waypoint != null && r2.track != null) {
|
||||
return 1;
|
||||
} else if (r1.track != null && r2.waypoint != null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Finally, use arbitrary ordering, by ID.
|
||||
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
|
||||
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
|
||||
long idDiff = id2 - id1;
|
||||
return Long.signum(idDiff);
|
||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = (r1, r2) -> {
|
||||
// Score ordering.
|
||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||
if (scoreDiff != 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
// Make tracks come before waypoints.
|
||||
if (r1.waypoint != null && r2.track != null) {
|
||||
return 1;
|
||||
} else if (r1.track != null && r2.waypoint != null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Finally, use arbitrary ordering, by ID.
|
||||
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
|
||||
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
|
||||
long idDiff = id2 - id1;
|
||||
return Long.signum(idDiff);
|
||||
};
|
||||
|
||||
private final ContentProviderUtils providerUtils;
|
||||
|
||||
@@ -39,6 +39,7 @@ public final class Waypoint {
|
||||
private double length = 0.0;
|
||||
private long duration = 0;
|
||||
private Location location;
|
||||
@Deprecated //TODO Make an URI instead of String
|
||||
private String photoUrl = "";
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -30,6 +30,7 @@ public interface WaypointsColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "waypoints";
|
||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
|
||||
String DEFAULT_SORT_ORDER = "_id";
|
||||
|
||||
@@ -63,6 +63,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath(), UrlType.WAYPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath() + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.WAYPOINTS_BY_TRACKID.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,6 +141,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
case WAYPOINTS:
|
||||
return WaypointsColumns.CONTENT_TYPE;
|
||||
case WAYPOINTS_BY_ID:
|
||||
case WAYPOINTS_BY_TRACKID:
|
||||
return WaypointsColumns.CONTENT_ITEMTYPE;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URL " + url);
|
||||
@@ -219,6 +221,10 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||
break;
|
||||
case WAYPOINTS_BY_TRACKID:
|
||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(WaypointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
@@ -382,6 +388,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
WAYPOINTS,
|
||||
WAYPOINTS_BY_ID
|
||||
WAYPOINTS_BY_ID,
|
||||
WAYPOINTS_BY_TRACKID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
@@ -211,14 +210,11 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
|
||||
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks);
|
||||
|
||||
PipeDataWriter pipeDataWriter = new PipeDataWriter<String>() {
|
||||
@Override
|
||||
public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable String args) {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||
trackExporter.writeTrack(getContext(), fileOutputStream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||
}
|
||||
PipeDataWriter<String> pipeDataWriter = (PipeDataWriter<String>) (output, uri1, mimeType, opts, args) -> {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||
trackExporter.writeTrack(getContext(), fileOutputStream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -100,13 +100,4 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
if (cursor == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cursor.getCount();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
|
||||
public class SensorData {
|
||||
|
||||
private String sensorAddress;
|
||||
private String sensorName;
|
||||
private final String sensorAddress;
|
||||
private final String sensorName;
|
||||
|
||||
private long timestamp_ms;
|
||||
private final long timestamp_ms;
|
||||
|
||||
SensorData(String sensorAddress, String sensorName) {
|
||||
this(sensorAddress, sensorName, System.currentTimeMillis());
|
||||
|
||||
@@ -25,8 +25,8 @@ public final class SensorDataCycling {
|
||||
|
||||
public static class Cadence extends SensorData {
|
||||
|
||||
private long crankRevolutionsCount; // UINT32
|
||||
private int crankRevolutionsTime; // UINT16; 1/1024s
|
||||
private final long crankRevolutionsCount; // UINT32
|
||||
private final int crankRevolutionsTime; // UINT16; 1/1024s
|
||||
private float cadence_rpm = INVALID_VALUE_FLOAT;
|
||||
|
||||
public Cadence(String sensorAddress, String sensorName, long crankRevolutionsCount, int crankRevolutionsTime) {
|
||||
@@ -93,8 +93,8 @@ public final class SensorDataCycling {
|
||||
|
||||
public static class Speed extends SensorData {
|
||||
|
||||
private int wheelRevolutionsCount; // UINT16
|
||||
private int wheelRevolutionsTime; // UINT16; 1/1024s
|
||||
private final int wheelRevolutionsCount; // UINT16
|
||||
private final int wheelRevolutionsTime; // UINT16; 1/1024s
|
||||
private float speed_mps = INVALID_VALUE_FLOAT;
|
||||
|
||||
public Speed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
|
||||
@@ -154,8 +154,8 @@ public final class SensorDataCycling {
|
||||
|
||||
public static class CadenceAndSpeed extends SensorData {
|
||||
|
||||
private Cadence cadence;
|
||||
private Speed speed;
|
||||
private final Cadence cadence;
|
||||
private final Speed speed;
|
||||
|
||||
public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) {
|
||||
super(sensorAddress, sensorName);
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
public class SensorDataHeartRate extends SensorData {
|
||||
|
||||
private float heartRate_bpm;
|
||||
private final float heartRate_bpm;
|
||||
|
||||
public SensorDataHeartRate(String name, String address, float heartRate_bpm) {
|
||||
super(name, address);
|
||||
|
||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -77,18 +76,13 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
||||
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
|
||||
}
|
||||
return DialogUtils.createConfirmationDialog(
|
||||
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||
for (long markerId : markerIds) {
|
||||
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||
}
|
||||
caller.onDeleteMarkerDone();
|
||||
}).start();
|
||||
fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||
for (long markerId : markerIds) {
|
||||
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||
}
|
||||
});
|
||||
caller.onDeleteMarkerDone();
|
||||
}).start());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -166,9 +166,9 @@ public class ExportProgressDialogFragment extends DialogFragment {
|
||||
|
||||
private class ExportThread extends Thread {
|
||||
|
||||
private TrackFileFormat trackFileFormat;
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
|
||||
private DocumentFile directory;
|
||||
private final DocumentFile directory;
|
||||
|
||||
ExportThread(TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
this.directory = directory;
|
||||
|
||||
+14
-10
@@ -44,7 +44,9 @@ import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class ImportProgressDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
|
||||
private static final String EXTRA_IS_TREE_KEY = "tree_uri";
|
||||
|
||||
private static final String EXTRA_DATA_URI_KEY = "data_uri";
|
||||
|
||||
private static final String TAG = ImportProgressDialogFragment.class.getSimpleName();
|
||||
|
||||
@@ -59,12 +61,10 @@ public class ImportProgressDialogFragment extends DialogFragment {
|
||||
private int trackImportSuccessCount;
|
||||
private int fileCount;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*/
|
||||
public static void showDialog(FragmentManager fragmentManager, Uri documentFileUri) {
|
||||
public static void showDialog(FragmentManager fragmentManager, Uri documentFileUri, boolean isDirectory) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(EXTRA_DIRECTORY_URI_KEY, documentFileUri);
|
||||
bundle.putParcelable(EXTRA_DATA_URI_KEY, documentFileUri);
|
||||
bundle.putBoolean(EXTRA_IS_TREE_KEY, isDirectory);
|
||||
|
||||
ImportProgressDialogFragment dialogFragment = new ImportProgressDialogFragment();
|
||||
dialogFragment.setArguments(bundle);
|
||||
@@ -79,9 +79,13 @@ public class ImportProgressDialogFragment extends DialogFragment {
|
||||
// This fragment uses a background thread, so it should not be recreated during activity re-creation.
|
||||
setRetainInstance(true);
|
||||
|
||||
Uri directoryUri = getArguments().getParcelable(EXTRA_DIRECTORY_URI_KEY);
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(getContext(), directoryUri);
|
||||
|
||||
Uri directoryUri = getArguments().getParcelable(EXTRA_DATA_URI_KEY);
|
||||
DocumentFile documentFile;
|
||||
if (getArguments().getBoolean(EXTRA_IS_TREE_KEY)) {
|
||||
documentFile = DocumentFile.fromTreeUri(getContext(), directoryUri);
|
||||
} else {
|
||||
documentFile = DocumentFile.fromSingleUri(getContext(), directoryUri);
|
||||
}
|
||||
directoryDisplayName = FileUtils.getPath(documentFile);
|
||||
|
||||
importThread = new ImportThread(documentFile);
|
||||
@@ -162,7 +166,7 @@ public class ImportProgressDialogFragment extends DialogFragment {
|
||||
|
||||
private class ImportThread extends Thread {
|
||||
|
||||
private DocumentFile file;
|
||||
private final DocumentFile file;
|
||||
|
||||
ImportThread(DocumentFile file) {
|
||||
this.file = file;
|
||||
|
||||
@@ -109,7 +109,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
long markerId = getArguments().getLong(KEY_MARKER_ID);
|
||||
if (markerId == -1L) {
|
||||
Log.d(TAG, "invalid marker id");
|
||||
getFragmentManager().popBackStack();
|
||||
getParentFragmentManager().popBackStack();
|
||||
return;
|
||||
}
|
||||
contentProviderUtils = new ContentProviderUtils(getActivity());
|
||||
@@ -125,16 +125,13 @@ public class MarkerDetailFragment extends Fragment {
|
||||
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
||||
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
||||
|
||||
photoView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handler.removeCallbacks(hideText);
|
||||
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
|
||||
textGradient.setVisibility(visibility);
|
||||
waypointInfo.setVisibility(visibility);
|
||||
if (visibility == View.VISIBLE) {
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
}
|
||||
photoView.setOnClickListener(v -> {
|
||||
handler.removeCallbacks(hideText);
|
||||
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
|
||||
textGradient.setVisibility(visibility);
|
||||
waypointInfo.setVisibility(visibility);
|
||||
if (visibility == View.VISIBLE) {
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
}
|
||||
});
|
||||
return view;
|
||||
@@ -222,7 +219,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
|
||||
if (waypoint == null) {
|
||||
Log.d(TAG, "waypoint is null");
|
||||
getFragmentManager().popBackStack();
|
||||
getParentFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,17 +54,14 @@ public class StatisticsRecordedFragment extends Fragment implements TrackDataLis
|
||||
private TrackStatistics trackStatistics = null;
|
||||
private String category = "";
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -124,23 +121,17 @@ public class StatisticsRecordedFragment extends Fragment implements TrackDataLis
|
||||
|
||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
|
||||
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -203,23 +203,17 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
|
||||
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,7 @@ public class ExportActivity extends FragmentActivity implements ExportProgressDi
|
||||
|
||||
@Override
|
||||
public void onExportFinished(final String directoryDisplayName, final int successCount, final int fileCount) {
|
||||
runOnUiThread(() -> {
|
||||
ExportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount);
|
||||
});
|
||||
runOnUiThread(() -> ExportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,11 +33,13 @@ import de.dennisguse.opentracks.util.StringUtils;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
//TODO Can we export SensorData in GPX?
|
||||
public class GpxTrackWriter implements TrackWriter {
|
||||
|
||||
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat SPEED_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat CADENCE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
|
||||
static {
|
||||
/*
|
||||
@@ -50,6 +52,15 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
COORDINATE_FORMAT.setMaximumFractionDigits(6);
|
||||
COORDINATE_FORMAT.setMaximumIntegerDigits(3);
|
||||
COORDINATE_FORMAT.setGroupingUsed(false);
|
||||
|
||||
SPEED_FORMAT.setMaximumFractionDigits(2);
|
||||
SPEED_FORMAT.setGroupingUsed(false);
|
||||
|
||||
HEARTRATE_FORMAT.setMaximumFractionDigits(0);
|
||||
HEARTRATE_FORMAT.setGroupingUsed(false);
|
||||
|
||||
CADENCE_FORMAT.setMaximumFractionDigits(0);
|
||||
CADENCE_FORMAT.setGroupingUsed(false);
|
||||
}
|
||||
|
||||
private final String creator;
|
||||
@@ -82,11 +93,12 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\"");
|
||||
printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
|
||||
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1"
|
||||
+ " http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd\">");
|
||||
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
|
||||
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
|
||||
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd\">");
|
||||
printWriter.println("<metadata>");
|
||||
|
||||
Track track = tracks[0];
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
@@ -174,9 +186,28 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
if (trackPoint.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
|
||||
}
|
||||
printWriter.println(
|
||||
"<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
printWriter.println("<speed>" + trackPoint.getSpeed() + "</speed>");
|
||||
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
|
||||
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence()) {
|
||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed()) + "</gpxtpx:speed>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
printWriter.println("<gpxtpx:cad>" + HEARTRATE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
|
||||
}
|
||||
|
||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
||||
}
|
||||
|
||||
printWriter.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
|
||||
+35
-23
@@ -83,6 +83,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
protected String altitude;
|
||||
protected String time;
|
||||
protected String speed;
|
||||
protected String heartrate;
|
||||
protected String cadence;
|
||||
protected String power;
|
||||
protected String waypointType;
|
||||
protected String photoUrl;
|
||||
protected String uuid;
|
||||
@@ -248,14 +251,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
trackData.track.setName(name);
|
||||
}
|
||||
|
||||
UUID uuidParsed;
|
||||
try {
|
||||
uuidParsed = UUID.fromString(uuid);
|
||||
trackData.track.setUuid(UUID.fromString(uuid));
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
Log.w(TAG, "could not parse Track UUID, generating a new one.");
|
||||
uuidParsed = UUID.randomUUID();
|
||||
trackData.track.setUuid(UUID.randomUUID());
|
||||
}
|
||||
trackData.track.setUuid(uuidParsed);
|
||||
|
||||
if (description != null) {
|
||||
trackData.track.setDescription(description);
|
||||
@@ -416,45 +417,56 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (latitude == null || longitude == null) {
|
||||
return null;
|
||||
}
|
||||
double latitudeValue;
|
||||
double longitudeValue;
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
|
||||
try {
|
||||
latitudeValue = Double.parseDouble(latitude);
|
||||
longitudeValue = Double.parseDouble(longitude);
|
||||
trackPoint.setLatitude(Double.parseDouble(latitude));
|
||||
trackPoint.setLongitude(Double.parseDouble(longitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||
}
|
||||
Double altitudeValue = null;
|
||||
if (altitude != null) {
|
||||
try {
|
||||
altitudeValue = Double.parseDouble(altitude);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||
}
|
||||
}
|
||||
|
||||
long timeValue;
|
||||
if (time == null) {
|
||||
timeValue = trackData.importTime;
|
||||
trackPoint.setTime(trackData.importTime);
|
||||
} else {
|
||||
try {
|
||||
timeValue = StringUtils.parseTime(time);
|
||||
trackPoint.setTime(StringUtils.parseTime(time));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||
if (altitude != null) {
|
||||
try {
|
||||
trackPoint.setAltitude(Double.parseDouble(altitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||
}
|
||||
}
|
||||
|
||||
float speedValue;
|
||||
if (speed != null) {
|
||||
try {
|
||||
speedValue = Float.valueOf(speed);
|
||||
trackPoint.setSpeed(speedValue);
|
||||
trackPoint.setSpeed(Float.parseFloat(speed));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||
}
|
||||
}
|
||||
if (heartrate != null) {
|
||||
try {
|
||||
trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (cadence != null) {
|
||||
try {
|
||||
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||
}
|
||||
}
|
||||
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri directoryUri = resultData.getData();
|
||||
|
||||
|
||||
startActivity(createIntent(directoryUri));
|
||||
}
|
||||
finish();
|
||||
|
||||
@@ -28,6 +28,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
/**
|
||||
* Imports a GPX file.
|
||||
* Uses https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@@ -39,7 +40,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String TAG_GPX = "gpx";
|
||||
private static final String TAG_NAME = "name";
|
||||
private static final String TAG_TIME = "time";
|
||||
private static final String TAG_SPEED = "speed";
|
||||
private static final String TAG_TRACK = "trk";
|
||||
private static final String TAG_TRACK_POINT = "trkpt";
|
||||
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
||||
@@ -49,6 +49,10 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String ATTRIBUTE_LAT = "lat";
|
||||
private static final String ATTRIBUTE_LON = "lon";
|
||||
|
||||
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -116,11 +120,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
time = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_SPEED:
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ELEVATION:
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
@@ -131,6 +130,20 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
waypointType = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_SPEED:
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
case TAG_EXTENSION_HEARTRATE:
|
||||
if (content != null) {
|
||||
heartrate = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_CADENCE:
|
||||
if (content != null) {
|
||||
cadence = content.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -43,8 +42,12 @@ public class ImportActivity extends FragmentActivity implements ImportProgressDi
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null || !savedInstanceState.getBoolean(BUNDLE_ACTIVITY_RECREATED, false)) {
|
||||
Uri directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||
ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), directoryUri);
|
||||
if (getIntent().getData() != null) {
|
||||
ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), getIntent().getData(), false);
|
||||
} else {
|
||||
// Started from DirectoryChooserActivity
|
||||
ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +64,7 @@ public class ImportActivity extends FragmentActivity implements ImportProgressDi
|
||||
|
||||
@Override
|
||||
public void onImportFinished(final String directoryDisplayName, final int successCount, final int fileCount) {
|
||||
runOnUiThread(() -> {
|
||||
ImportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount);
|
||||
});
|
||||
runOnUiThread(() -> ImportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -53,7 +53,7 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
|
||||
private final Context context;
|
||||
private final long importTrackId;
|
||||
private Uri uriKmzFile;
|
||||
private final Uri uriKmzFile;
|
||||
|
||||
/**
|
||||
* @param context the context
|
||||
|
||||
@@ -24,11 +24,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
@@ -37,9 +33,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
@@ -50,6 +43,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||
@@ -62,7 +56,6 @@ import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
import de.dennisguse.opentracks.util.TrackPointUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* A background service that registers a location listener and records track points.
|
||||
@@ -70,25 +63,19 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class TrackRecordingService extends Service {
|
||||
public class TrackRecordingService extends Service implements HandlerServer.HandlerServerInterface {
|
||||
|
||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||
|
||||
// The following variables are set in onCreate:
|
||||
@Deprecated //TODO Should not be necessary
|
||||
private ExecutorService locationExecutorService; // Enforces order of location changes.
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private LocationManager locationManager;
|
||||
private PeriodicTaskExecutor voiceExecutor;
|
||||
private TrackRecordingServiceNotificationManager notificationManager;
|
||||
private LocationListenerPolicy locationListenerPolicy;
|
||||
|
||||
private long recordingTrackId;
|
||||
private boolean recordingTrackPaused;
|
||||
private int recordingDistanceInterval;
|
||||
private int maxRecordingDistance;
|
||||
private int recordingGpsAccuracy;
|
||||
private long currentRecordingInterval;
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
@@ -110,27 +97,14 @@ public class TrackRecordingService extends Service {
|
||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||
// Choose battery life over moving time accuracy.
|
||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND_MS, 5 * UnitConversions.ONE_MINUTE_MS, 5);
|
||||
} else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
|
||||
// Get all the updates.
|
||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND_MS, 30 * UnitConversions.ONE_SECOND_MS, 0);
|
||||
} else {
|
||||
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
}
|
||||
|
||||
handlerServer.onSharedPreferenceChanged(context, preferences, key);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,38 +117,16 @@ public class TrackRecordingService extends Service {
|
||||
private boolean isIdle;
|
||||
|
||||
private TrackRecordingServiceBinder binder = new TrackRecordingServiceBinder(this);
|
||||
private final LocationListener locationListener = new LocationListener() {
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(final Location location) {
|
||||
if (locationExecutorService == null || locationExecutorService.isShutdown() || locationExecutorService.isTerminated()) {
|
||||
return;
|
||||
}
|
||||
locationExecutorService.submit(() -> onLocationChangedAsync(location));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
Log.w(TAG, "LocationListener.onStatusChanged(): is not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
Log.w(TAG, "LocationListener.onProviderEnabled(): is not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
Log.w(TAG, "LocationListener.onProviderDisabled(): is not implemented.");
|
||||
}
|
||||
};
|
||||
private HandlerServer handlerServer;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
locationExecutorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
handlerServer = new HandlerServer(this);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
||||
|
||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||
@@ -200,6 +152,8 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
handlerServer.stop(this);
|
||||
|
||||
if (remoteSensorManager != null) {
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
@@ -208,9 +162,6 @@ public class TrackRecordingService extends Service {
|
||||
// Reverse order from onCreate
|
||||
showNotification(false); //TODO Why?
|
||||
|
||||
unregisterLocationListener();
|
||||
locationManager = null;
|
||||
|
||||
PreferencesUtils.unregister(this, sharedPreferenceChangeListener);
|
||||
|
||||
try {
|
||||
@@ -227,8 +178,6 @@ public class TrackRecordingService extends Service {
|
||||
// This should be the next to last operation
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
|
||||
// Shutdown the executorService last to avoid sending events to a dead executor.
|
||||
locationExecutorService.shutdown();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -428,7 +377,7 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
private void startGps() {
|
||||
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
||||
registerLocationListener();
|
||||
handlerServer.start(this);
|
||||
showNotification(true);
|
||||
}
|
||||
|
||||
@@ -498,6 +447,8 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
lastTrackPoint = null;
|
||||
|
||||
handlerServer.stop(this);
|
||||
|
||||
stopGps(trackStopped);
|
||||
}
|
||||
|
||||
@@ -509,7 +460,7 @@ public class TrackRecordingService extends Service {
|
||||
void stopGps(boolean shutdown) {
|
||||
if (!isRecording()) return;
|
||||
|
||||
unregisterLocationListener();
|
||||
handlerServer.stop(this);
|
||||
showNotification(false);
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
if (shutdown) {
|
||||
@@ -548,46 +499,27 @@ public class TrackRecordingService extends Service {
|
||||
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
|
||||
}
|
||||
|
||||
void onLocationChangedAsync(Location location) {
|
||||
@Override
|
||||
public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
if (!isRecording() || isPaused()) {
|
||||
Log.w(TAG, "Ignore onLocationChangedAsync. Not recording or paused.");
|
||||
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
|
||||
return;
|
||||
}
|
||||
|
||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Ignore onLocationChangedAsync. No track.");
|
||||
Log.w(TAG, "Ignore newTrackPoint. No track.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
Log.w(TAG, "Ignore onLocationChangedAsync. location is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
fillWithSensorDataSet(trackPoint);
|
||||
|
||||
notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy);
|
||||
|
||||
if (!TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy)) {
|
||||
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
|
||||
return;
|
||||
}
|
||||
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
long idleTime = 0L;
|
||||
if (TrackPointUtils.after(trackPoint, lastValidTrackPoint)) {
|
||||
idleTime = trackPoint.getTime() - lastValidTrackPoint.getTime();
|
||||
}
|
||||
|
||||
locationListenerPolicy.updateIdleTime(idleTime);
|
||||
if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) {
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
//Storing trackPoint
|
||||
|
||||
@@ -598,7 +530,7 @@ public class TrackRecordingService extends Service {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(lastValidTrackPoint.getLocation())) {
|
||||
if (lastValidTrackPoint == null || !LocationUtils.isValidLocation(lastValidTrackPoint.getLocation())) {
|
||||
// For some reason the previous first trackPoint was not stored, but currentSegmentHasLocation set true.
|
||||
// Should not happen. The current segment should have a location. Just insert the current location.
|
||||
insertTrackPoint(track, trackPoint);
|
||||
@@ -714,28 +646,6 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "locationManager is null.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
long interval = locationListenerPolicy.getDesiredPollingInterval();
|
||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, locationListenerPolicy.getMinDistance_m(), locationListener);
|
||||
currentRecordingInterval = interval;
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "locationManager is null.");
|
||||
return;
|
||||
}
|
||||
locationManager.removeUpdates(locationListener);
|
||||
}
|
||||
|
||||
private void showNotification(boolean isGpsStarted) {
|
||||
if (isRecording()) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||
@@ -765,19 +675,6 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables processing of location updates from {@link android.location.LocationManager}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void enableLocationExecutor(boolean enable) {
|
||||
if (enable) {
|
||||
locationExecutorService = Executors.newSingleThreadExecutor();
|
||||
} else {
|
||||
locationExecutorService.shutdownNow();
|
||||
locationExecutorService = null;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
this.remoteSensorManager = remoteSensorManager;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
|
||||
@@ -82,23 +81,11 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
|
||||
return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void insertLocation(Location location) {
|
||||
trackRecordingService.onLocationChangedAsync(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SensorDataSet getSensorData() {
|
||||
return trackRecordingService.getSensorDataSet();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void enableLocationExecutor(boolean enable) {
|
||||
trackRecordingService.enableLocationExecutor(enable);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
@@ -112,4 +99,10 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
|
||||
void detachFromService() {
|
||||
trackRecordingService = null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
trackRecordingService.newTrackPoint(trackPoint, recordingGpsAccuracy);
|
||||
}
|
||||
}
|
||||
|
||||
+34
-67
@@ -33,7 +33,6 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackEditActivity;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ServiceUtils;
|
||||
|
||||
/**
|
||||
* Wrapper for the track recording service.
|
||||
@@ -66,14 +65,43 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
* Starts and binds the service.
|
||||
*/
|
||||
public void startAndBind(Context context) {
|
||||
bindService(context, true);
|
||||
if (trackRecordingService != null) {
|
||||
// Service is already started and bound.
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Starting the service.");
|
||||
context.startService(new Intent(context, TrackRecordingService.class));
|
||||
|
||||
startConnection(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the service if it is started.
|
||||
* Resumes the track recording service connection.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
private void bindIfStarted(Context context) {
|
||||
bindService(context, false);
|
||||
public void startConnection(@NonNull Context context) {
|
||||
if (trackRecordingService != null) {
|
||||
// Service is already started and bound.
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Binding the service.");
|
||||
int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0;
|
||||
context.bindService(new Intent(context, TrackRecordingService.class), this, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds the service (but leave it running).
|
||||
*/
|
||||
public void unbind(Context context) {
|
||||
try {
|
||||
context.unbindService(this);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Means not bound to the service. OK to ignore.
|
||||
}
|
||||
setTrackRecordingService(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +119,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
return trackRecordingService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the trackRecordingService.
|
||||
*
|
||||
@@ -104,18 +131,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds the service (but leave it running).
|
||||
*/
|
||||
public void unbind(Context context) {
|
||||
try {
|
||||
context.unbindService(this);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Means not bound to the service. OK to ignore.
|
||||
}
|
||||
setTrackRecordingService(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.i(TAG, "Connected to the service.");
|
||||
@@ -139,44 +154,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
setTrackRecordingService(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the service if it is started.
|
||||
*
|
||||
* @param startIfNeeded start the service if needed
|
||||
*/
|
||||
private void bindService(Context context, boolean startIfNeeded) {
|
||||
if (trackRecordingService != null) {
|
||||
// Service is already started and bound.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!startIfNeeded && !ServiceUtils.isTrackRecordingServiceRunning(context)) {
|
||||
Log.d(TAG, "Service is not started. Not binding it.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startIfNeeded) {
|
||||
Log.i(TAG, "Starting the service.");
|
||||
context.startService(new Intent(context, TrackRecordingService.class));
|
||||
}
|
||||
|
||||
Log.i(TAG, "Binding the service.");
|
||||
int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0;
|
||||
context.bindService(new Intent(context, TrackRecordingService.class), this, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the track recording service connection.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public void startConnection(@NonNull Context context) {
|
||||
bindIfStarted(context);
|
||||
if (!ServiceUtils.isTrackRecordingServiceRunning(context)) {
|
||||
resetRecordingState(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the recording track.
|
||||
*/
|
||||
@@ -197,16 +174,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetRecordingState(Context context) {
|
||||
if (PreferencesUtils.isRecording(context)) {
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
boolean recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
if (!recordingTrackPaused) {
|
||||
PreferencesUtils.defaultRecordingTrackPaused(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker.
|
||||
*
|
||||
@@ -241,7 +208,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
public void stopRecording(@NonNull Context context, boolean showEditor) {
|
||||
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
|
||||
if (trackRecordingService == null) {
|
||||
resetRecordingState(context);
|
||||
Log.e(TAG, "TrackRecordingService not connected.");
|
||||
} else {
|
||||
try {
|
||||
if (showEditor) {
|
||||
|
||||
+11
-19
@@ -15,10 +15,9 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
|
||||
@@ -99,23 +98,16 @@ public interface TrackRecordingServiceInterface {
|
||||
*/
|
||||
SensorDataSet getSensorData();
|
||||
|
||||
/**
|
||||
* Inserts a location in the current recording track.
|
||||
* <p>
|
||||
* When recording a track, GPS locations are automatically inserted.
|
||||
* This is used for inserting special track points or for testing.
|
||||
*
|
||||
* @param location the location to be inserted
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void insertLocation(Location location);
|
||||
|
||||
/**
|
||||
* Disables processing of location updates from {@link android.location.LocationManager}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void enableLocationExecutor(boolean enable);
|
||||
|
||||
@VisibleForTesting
|
||||
void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager);
|
||||
|
||||
/**
|
||||
* Inserts a track point in the current recording track.
|
||||
* This is used for inserting special track points or for testing.
|
||||
*
|
||||
* @param trackPoint the track point object to be inserted.
|
||||
* @param recordingGpsAccuracy recording GPS accuracy.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy);
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.services;
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
/**
|
||||
* This is a simple location listener policy that will always dictate the same polling interval.
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.services;
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
/**
|
||||
* A {@link LocationListenerPolicy} that will change based on how long the user has been stationary.
|
||||
@@ -0,0 +1,70 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
public class HandlerServer {
|
||||
|
||||
private static String TAG = HandlerServer.class.getSimpleName();
|
||||
|
||||
private final LocationHandler locationHandler;
|
||||
private final HandlerServerInterface service;
|
||||
private ExecutorService serviceExecutor;
|
||||
|
||||
public HandlerServer(HandlerServerInterface service) {
|
||||
this.locationHandler = new LocationHandler(this);
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
HandlerServer(LocationHandler locationHandler, HandlerServerInterface service) {
|
||||
this.locationHandler = locationHandler;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public void start(Context context) {
|
||||
serviceExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
locationHandler.onStart(context);
|
||||
locationHandler.onSharedPreferenceChanged(context, null, null);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
locationHandler.onStop(context);
|
||||
|
||||
if (serviceExecutor != null) {
|
||||
serviceExecutor.shutdownNow();
|
||||
}
|
||||
serviceExecutor = null;
|
||||
}
|
||||
|
||||
public void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key) {
|
||||
locationHandler.onSharedPreferenceChanged(context, preferences, key);
|
||||
}
|
||||
|
||||
public void sendTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
if (serviceExecutor == null || serviceExecutor.isTerminated() || serviceExecutor.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
serviceExecutor.execute(() -> service.newTrackPoint(trackPoint, recordingGpsAccuracy));
|
||||
}
|
||||
|
||||
public interface HandlerServerInterface {
|
||||
void newTrackPoint(TrackPoint trackPoint, int gpsAccuracy);
|
||||
}
|
||||
|
||||
public interface Handler {
|
||||
void onStart(Context context);
|
||||
|
||||
void onStop(Context context);
|
||||
|
||||
void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackPointUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
class LocationHandler implements HandlerServer.Handler, LocationListener {
|
||||
|
||||
private final String TAG = LocationHandler.class.getSimpleName();
|
||||
|
||||
private LocationManager locationManager;
|
||||
private final HandlerServer handlerServer;
|
||||
private LocationListenerPolicy locationListenerPolicy;
|
||||
private long currentRecordingInterval;
|
||||
private int recordingGpsAccuracy;
|
||||
private TrackPoint lastValidTrackPoint;
|
||||
|
||||
public LocationHandler(HandlerServer handlerServer) {
|
||||
this.handlerServer = handlerServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(Context context) {
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(Context context) {
|
||||
locationManager = null;
|
||||
unregisterLocationListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(Context context, SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||
// Choose battery life over moving time accuracy.
|
||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND_MS, 5 * UnitConversions.ONE_MINUTE_MS, 5);
|
||||
} else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
|
||||
// Get all the updates.
|
||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND_MS, 30 * UnitConversions.ONE_SECOND_MS, 0);
|
||||
} else {
|
||||
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
|
||||
if (locationManager != null) {
|
||||
registerLocationListener();
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if location is valid and builds a track point that will be send through HandlerServer.
|
||||
*
|
||||
* @param location {@link Location} object.
|
||||
*/
|
||||
@Override
|
||||
public void onLocationChanged(@NonNull Location location) {
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
Log.w(TAG, "Ignore newTrackPoint. location is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
|
||||
if (!TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy)) {
|
||||
Log.d(TAG, "Ignore newTrackPoint. Poor accuracy.");
|
||||
return;
|
||||
}
|
||||
|
||||
long idleTime = 0L;
|
||||
if (TrackPointUtils.after(trackPoint, lastValidTrackPoint)) {
|
||||
idleTime = trackPoint.getTime() - lastValidTrackPoint.getTime();
|
||||
}
|
||||
|
||||
locationListenerPolicy.updateIdleTime(idleTime);
|
||||
if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) {
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
lastValidTrackPoint = trackPoint;
|
||||
handlerServer.sendTrackPoint(trackPoint, recordingGpsAccuracy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(@NonNull String provider) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(@NonNull String provider) {
|
||||
}
|
||||
|
||||
private void registerLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "locationManager is null.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
long interval = locationListenerPolicy.getDesiredPollingInterval();
|
||||
currentRecordingInterval = interval;
|
||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, locationListenerPolicy.getMinDistance_m(), this);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "locationManager is null.");
|
||||
return;
|
||||
}
|
||||
locationManager.removeUpdates(this);
|
||||
locationManager = null;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.services;
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
/**
|
||||
* This is an interface for classes that will manage the location listener policy.
|
||||
@@ -19,7 +19,6 @@ package de.dennisguse.opentracks.services.tasks;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -50,7 +49,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
|
||||
private final AudioManager audioManager;
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
|
||||
@Override
|
||||
@@ -116,12 +115,9 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
Log.d(TAG, "Start");
|
||||
|
||||
if (tts == null) {
|
||||
tts = new TextToSpeech(context, new OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
Log.i(TAG, "TextToSpeech initialized with status " + status);
|
||||
ttsInitStatus = status;
|
||||
}
|
||||
tts = new TextToSpeech(context, status -> {
|
||||
Log.i(TAG, "TextToSpeech initialized with status " + status);
|
||||
ttsInitStatus = status;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Spinner;
|
||||
@@ -51,12 +50,7 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
setDialogIcon(null);
|
||||
setPersistent(true);
|
||||
|
||||
SummaryProvider<DialogPreference> summaryProvider = new SummaryProvider<DialogPreference>() {
|
||||
@Override
|
||||
public CharSequence provideSummary(DialogPreference preference) {
|
||||
return PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
|
||||
}
|
||||
};
|
||||
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity(ActivityTypePreference.this.getContext());
|
||||
setSummaryProvider(summaryProvider);
|
||||
}
|
||||
|
||||
@@ -90,43 +84,31 @@ public class ActivityTypePreference extends DialogPreference {
|
||||
textView.setText(category);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
textView.setAdapter(adapter);
|
||||
textView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, (String) textView.getAdapter().getItem(position));
|
||||
TrackIconUtils.setIconSpinner(spinner, iconValue);
|
||||
}
|
||||
textView.setOnItemClickListener((parent, v, position, id) -> {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, (String) textView.getAdapter().getItem(position));
|
||||
TrackIconUtils.setIconSpinner(spinner, iconValue);
|
||||
});
|
||||
textView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, textView.getText().toString());
|
||||
TrackIconUtils.setIconSpinner(spinner, iconValue);
|
||||
}
|
||||
textView.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, textView.getText().toString());
|
||||
TrackIconUtils.setIconSpinner(spinner, iconValue);
|
||||
}
|
||||
});
|
||||
|
||||
String iconValue = TrackIconUtils.getIconValue(context, category);
|
||||
spinner = view.findViewById(R.id.activity_type_preference_spinner);
|
||||
spinner.setAdapter(TrackIconUtils.getIconSpinnerAdapter(context, iconValue));
|
||||
spinner.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
showIconSelectDialog();
|
||||
}
|
||||
return true;
|
||||
spinner.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
showIconSelectDialog();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
spinner.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
showIconSelectDialog();
|
||||
}
|
||||
return true;
|
||||
spinner.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
showIconSelectDialog();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -56,15 +56,12 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
|
||||
public static class PrefsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
|
||||
getActivity().runOnUiThread(() -> updateReset());
|
||||
}
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
|
||||
getActivity().runOnUiThread(() -> updateUnits());
|
||||
}
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
|
||||
getActivity().runOnUiThread(this::updateReset);
|
||||
}
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
|
||||
getActivity().runOnUiThread(this::updateUnits);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -117,7 +114,7 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
|
||||
if (dialogFragment != null) {
|
||||
dialogFragment.setTargetFragment(this, 0);
|
||||
dialogFragment.show(getFragmentManager(), getClass().getSimpleName());
|
||||
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+4
-7
@@ -183,14 +183,11 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
|
||||
builder.setSingleChoiceItems(listAdapter, selectedEntryIndex,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
selectedEntryIndex = which;
|
||||
(dialog, which) -> {
|
||||
selectedEntryIndex = which;
|
||||
|
||||
BluetoothLeSensorPreferenceDialog.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
dialog.dismiss();
|
||||
}
|
||||
BluetoothLeSensorPreferenceDialog.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
dialog.dismiss();
|
||||
});
|
||||
builder.setIcon(android.R.drawable.stat_sys_data_bluetooth);
|
||||
builder.setPositiveButton(null, null);
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ListView;
|
||||
|
||||
@@ -94,17 +93,13 @@ public class ActivityUtils {
|
||||
}
|
||||
searchView.setQueryRefinementEnabled(true);
|
||||
searchView.setSubmitButtonEnabled(true);
|
||||
searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
// Hide and show trackController when searchable widget has focus/no focus
|
||||
if (trackController != null) {
|
||||
if (hasFocus) {
|
||||
trackController.hide();
|
||||
} else {
|
||||
trackController.show();
|
||||
}
|
||||
searchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> {
|
||||
// Hide and show trackController when searchable widget has focus/no focus
|
||||
if (trackController != null) {
|
||||
if (hasFocus) {
|
||||
trackController.hide();
|
||||
} else {
|
||||
trackController.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -48,8 +48,6 @@ public class FileUtils {
|
||||
*/
|
||||
static final String FILEPROVIDER = BuildConfig.APPLICATION_ID + ".fileprovider";
|
||||
|
||||
public static final String EXPORT_DIR = "OpenTracks";
|
||||
|
||||
private static final String JPEG_EXTENSION = "jpeg";
|
||||
|
||||
/**
|
||||
@@ -309,7 +307,10 @@ public class FileUtils {
|
||||
public static ArrayList<DocumentFile> getFiles(DocumentFile file) {
|
||||
ArrayList<DocumentFile> files = new ArrayList<>();
|
||||
|
||||
if (!file.isDirectory()) return files;
|
||||
if (!file.isDirectory()) {
|
||||
files.add(file);
|
||||
return files;
|
||||
}
|
||||
|
||||
for (DocumentFile candidate : file.listFiles()) {
|
||||
if (!candidate.isDirectory()) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
/**
|
||||
@@ -23,11 +24,14 @@ public class IntentDashboardUtils {
|
||||
|
||||
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
||||
|
||||
private static final String EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK = "EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK";
|
||||
private static final String EXTRAS_SHOULD_KEEP_SCREEN_ON = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||
private static final String EXTRAS_SHOW_WHEN_LOCKED = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||
private static final String EXTRAS_SHOW_FULLSCREEN = "EXTRAS_SHOULD_FULLSCREEN";
|
||||
|
||||
private static int TRACK_URI_INDEX = 0;
|
||||
private static int TRACKPOINTS_URI_INDEX = 1;
|
||||
private static final int TRACK_URI_INDEX = 0;
|
||||
private static final int TRACKPOINTS_URI_INDEX = 1;
|
||||
private static final int WAYPOINTS_URI_INDEX = 2;
|
||||
|
||||
private IntentDashboardUtils() {
|
||||
}
|
||||
@@ -38,7 +42,7 @@ public class IntentDashboardUtils {
|
||||
* @param context the context
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static void startDashboard(Context context, long[] trackIds) {
|
||||
public static void startDashboard(Context context, long[] trackIds, boolean isRecording) {
|
||||
if (trackIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -48,26 +52,27 @@ public class IntentDashboardUtils {
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
||||
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
uris.add(WAYPOINTS_URI_INDEX, Uri.withAppendedPath(WaypointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
|
||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(context));
|
||||
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(context));
|
||||
intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
|
||||
if (isRecording) {
|
||||
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen(context));
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
ClipData clipData = ClipData.newRawUri(null, uris.get(TRACK_URI_INDEX));
|
||||
clipData.addItem(new ClipData.Item(uris.get(TRACKPOINTS_URI_INDEX)));
|
||||
clipData.addItem(new ClipData.Item(uris.get(WAYPOINTS_URI_INDEX)));
|
||||
intent.setClipData(clipData);
|
||||
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void startDashboard(Context context, long trackId) {
|
||||
startDashboard(context, new long[]{trackId});
|
||||
}
|
||||
|
||||
public static long[] extractTrackIdsFromIntent(@NonNull Intent intent) {
|
||||
final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD);
|
||||
final Uri tracksUri = uris.get(TRACK_URI_INDEX);
|
||||
|
||||
@@ -16,13 +16,6 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
* Utility class for decimating tracks at a given level of precision.
|
||||
@@ -39,112 +32,6 @@ public class LocationUtils {
|
||||
private LocationUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the distance on the two sphere between the point c0 and the line segment c1 to c2.
|
||||
*
|
||||
* @param c0 the first coordinate
|
||||
* @param c1 the beginning of the line segment
|
||||
* @param c2 the end of the lone segment
|
||||
* @return the distance in m (assuming spherical earth)
|
||||
*/
|
||||
private static double distance(final Location c0, final Location c1, final Location c2) {
|
||||
if (c1.equals(c2)) {
|
||||
return c2.distanceTo(c0);
|
||||
}
|
||||
|
||||
final double s0lat = c0.getLatitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s0lng = c0.getLongitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s1lat = c1.getLatitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s1lng = c1.getLongitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s2lat = c2.getLatitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s2lng = c2.getLongitude() * UnitConversions.DEG_TO_RAD;
|
||||
|
||||
double s2s1lat = s2lat - s1lat;
|
||||
double s2s1lng = s2lng - s1lng;
|
||||
final double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng)
|
||||
/ (s2s1lat * s2s1lat + s2s1lng * s2s1lng);
|
||||
|
||||
if (u <= 0) {
|
||||
return c0.distanceTo(c1);
|
||||
}
|
||||
|
||||
if (u >= 1) {
|
||||
return c0.distanceTo(c2);
|
||||
}
|
||||
|
||||
Location sa = new Location("");
|
||||
sa.setLatitude(c0.getLatitude() - c1.getLatitude());
|
||||
sa.setLongitude(c0.getLongitude() - c1.getLongitude());
|
||||
|
||||
Location sb = new Location("");
|
||||
sb.setLatitude(u * (c2.getLatitude() - c1.getLatitude()));
|
||||
sb.setLongitude(u * (c2.getLongitude() - c1.getLongitude()));
|
||||
|
||||
return sa.distanceTo(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates the given trackPoints for a given zoom level.
|
||||
* This uses a Douglas-Peucker decimation algorithm.
|
||||
*
|
||||
* @param tolerance in meters
|
||||
* @param trackPoints input
|
||||
*/
|
||||
//TODO What was it used for? Sharing data with other apps?
|
||||
private static List<TrackPoint> decimate(double tolerance, List<TrackPoint> trackPoints) {
|
||||
List<TrackPoint> decimated = new ArrayList<>();
|
||||
final int n = trackPoints.size();
|
||||
if (n < 1) {
|
||||
return null;
|
||||
}
|
||||
int idx;
|
||||
int maxIdx = 0;
|
||||
Stack<int[]> stack = new Stack<>();
|
||||
double[] dists = new double[n];
|
||||
dists[0] = 1;
|
||||
dists[n - 1] = 1;
|
||||
double maxDist;
|
||||
double dist;
|
||||
int[] current;
|
||||
|
||||
if (n > 2) {
|
||||
int[] stackVal = new int[]{0, (n - 1)};
|
||||
stack.push(stackVal);
|
||||
while (stack.size() > 0) {
|
||||
current = stack.pop();
|
||||
maxDist = 0;
|
||||
for (idx = current[0] + 1; idx < current[1]; ++idx) {
|
||||
dist = LocationUtils.distance(trackPoints.get(idx).getLocation(), trackPoints.get(current[0]).getLocation(), trackPoints.get(current[1]).getLocation());
|
||||
if (dist > maxDist) {
|
||||
maxDist = dist;
|
||||
maxIdx = idx;
|
||||
}
|
||||
}
|
||||
if (maxDist > tolerance) {
|
||||
dists[maxIdx] = maxDist;
|
||||
int[] stackValCurMax = {current[0], maxIdx};
|
||||
stack.push(stackValCurMax);
|
||||
int[] stackValMaxCur = {maxIdx, current[1]};
|
||||
stack.push(stackValMaxCur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
idx = 0;
|
||||
decimated.clear();
|
||||
for (TrackPoint l : trackPoints) {
|
||||
if (dists[idx] != 0) {
|
||||
decimated.add(l);
|
||||
i++;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
Log.d(TAG, "Decimating " + n + " points to " + i + " w/ tolerance = " + tolerance);
|
||||
|
||||
return decimated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given location is a valid (i.e. physically possible) location on Earth.
|
||||
* Note: The special separator locations (which have latitude = 100) will not qualify as valid.
|
||||
|
||||
@@ -9,7 +9,7 @@ import de.dennisguse.opentracks.R;
|
||||
|
||||
public class MarkerUtils {
|
||||
|
||||
public static final int ICON_ID = R.drawable.ic_marker_blue_pushpin;
|
||||
public static final int ICON_ID = R.drawable.ic_marker_orange_pushpin_with_shadow;
|
||||
|
||||
private MarkerUtils() {
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ public class PreferencesUtils {
|
||||
//NOTE: This was also used to recover from a service restart, but this data should not be exposed to the whole application.
|
||||
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
|
||||
|
||||
@Deprecated //Use the TrackRecordingService
|
||||
public static long getRecordingTrackId(Context context) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
return sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT);
|
||||
@@ -208,14 +209,17 @@ public class PreferencesUtils {
|
||||
return currentStatsRate.equals(context.getString(R.string.stats_rate_speed));
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
public static boolean isRecordingTrackPaused(Context context) {
|
||||
return getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
public static boolean isRecordingTrackPausedDefault(Context context) {
|
||||
return context.getResources().getBoolean(R.bool.recording_track_paused_default);
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
public static void defaultRecordingTrackPaused(Context context) {
|
||||
final boolean RECORDING_TRACK_PAUSED = context.getResources().getBoolean(R.bool.recording_track_paused_default);
|
||||
setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
||||
@@ -271,6 +275,11 @@ public class PreferencesUtils {
|
||||
return getBoolean(context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean shouldUseFullscreen(Context context) {
|
||||
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_fullscreen_while_recording_default);
|
||||
return getBoolean(context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean isShowStatsElevation(Context context) {
|
||||
final boolean STATS_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_elevation_default);
|
||||
return getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_ELEVATION);
|
||||
@@ -331,6 +340,7 @@ public class PreferencesUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated //Use TrackRecordingService
|
||||
public static boolean isRecording(Context context) {
|
||||
long recordingTrackId = getRecordingTrackId(context);
|
||||
return isRecording(recordingTrackId);
|
||||
|
||||
@@ -18,7 +18,7 @@ public class ServiceUtils {
|
||||
*
|
||||
* @param context the current context
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated //TODO This method must be removed as the service will nowadays always be started
|
||||
public static boolean isTrackRecordingServiceRunning(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (activityManager == null) {
|
||||
|
||||
@@ -2,8 +2,8 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
public class UintUtils {
|
||||
|
||||
public static int UINT16_MAX = 0xFFFF;
|
||||
public static long UINT32_MAX = 0xFFFFFFFFL;
|
||||
public static final int UINT16_MAX = 0xFFFF;
|
||||
public static final long UINT32_MAX = 0xFFFFFFFFL;
|
||||
|
||||
private UintUtils() {
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
public class AggregatedStatistics {
|
||||
|
||||
private HashMap<String, AggregatedStatistic> aggregatedStatistics = new HashMap<>();
|
||||
private final Map<String, AggregatedStatistic> aggregatedStatistics = new HashMap<>();
|
||||
|
||||
public void aggregate(List<Track> tracks) {
|
||||
for (Track track : tracks) {
|
||||
@@ -50,7 +48,7 @@ public class AggregatedStatistics {
|
||||
}
|
||||
|
||||
public static class AggregatedStatistic {
|
||||
private TrackStatistics trackStatistics;
|
||||
private final TrackStatistics trackStatistics;
|
||||
private int countTracks;
|
||||
|
||||
public AggregatedStatistic(TrackStatistics trackStatistics) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,59 @@
|
||||
<vector android:height="34dp"
|
||||
android:viewportHeight="297"
|
||||
android:viewportWidth="210"
|
||||
android:width="24.040403dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillAlpha="0.49421525"
|
||||
android:fillColor="#333333"
|
||||
android:pathData="m108.528,213.298a56.035,7.006 5.281,0 0,-22.426 2.532,56.035 7.006,5.281 0,0 -0.234,0.177l-0.207,-0.024c-0.045,0.116 -0.075,0.231 -0.096,0.348a56.035,7.006 5.281,0 0,0.338 1.582c0.981,1.709 3.761,3.463 6.584,5.219l-22.744,15.5a67.065,11.557 4.228,0 0,-39.968 5.269,67.065 11.557,4.228 0,0 -0.729,0.576 67.065,11.557 4.228,0 0,-2.754 1.364,67.065 11.557,4.228 0,0 38.909,16.043l-32.177,27.065 52.228,-24.746a67.065,11.557 4.228,0 0,71.411 -3.819,67.065 11.557,4.228 0,0 0.728,-0.575 67.065,11.557 4.228,0 0,2.755 -1.364,67.065 11.557,4.228 0,0 -12.384,-10.598 40.034,5.073 5.25,0 0,0.056 -0.037,40.034 5.073,5.25 0,0 0.377,-0.343l22.452,-15.301c8.236,-0.652 17.656,-1.478 22.944,-3.01a56.035,7.006 5.281,0 0,2.655 -1.038,56.035 7.006,5.281 0,0 -46.084,-12.27 56.035,7.006 5.281,0 0,-41.636 -2.55z"
|
||||
android:strokeAlpha="0.265501"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.138931" />
|
||||
<path
|
||||
android:pathData="M70.603,174.308 L33.024,288.947 94.567,183.195Z"
|
||||
android:strokeAlpha="0.265501"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.264583">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="67.193245"
|
||||
android:endY="217.31992"
|
||||
android:startX="63.39815"
|
||||
android:startY="215.52817"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFFFFFFF"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF898D8D"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#d45500"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M26.293,132.603a39.974,70.319 112.028,1 0,130.371 52.747a39.974,70.319 112.028,1 0,-130.371 -52.747z"
|
||||
android:strokeWidth="0.264583" />
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M29.776,125.57a39.974,70.319 112.028,1 0,130.371 52.747a39.974,70.319 112.028,1 0,-130.371 -52.747z"
|
||||
android:strokeWidth="0.264583" />
|
||||
<path
|
||||
android:fillColor="#d45500"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m85.662,24.322c-0.891,8.35 2.928,17.133 6.825,25.924l-23.334,57.673 -0.731,1.806 0.249,0.101a42.43,17.361 22.028,0 0,33.304 30.101,42.43 17.361,22.028 0,0 44.865,1.526l0.139,0.056 0.245,-0.606a42.43,17.361 22.028,0 0,0.595 -1.156,42.43 17.361,22.028 0,0 0.377,-1.245l22.452,-55.493c9.29,-2.666 20.091,-6.136 24.764,-13.175z"
|
||||
android:strokeWidth="0.264583" />
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M86.103,23.766a23.967,59.409 112.028,1 0,110.145 44.564a23.967,59.409 112.028,1 0,-110.145 -44.564z"
|
||||
android:strokeWidth="0.264583" />
|
||||
</vector>
|
||||
@@ -24,11 +24,6 @@ limitations under the License.
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/track_controller_record"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -69,9 +64,5 @@ limitations under the License.
|
||||
android:contentDescription="@string/image_stop"
|
||||
android:src="@drawable/ic_button_stop_disabled" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@@ -40,7 +40,7 @@ limitations under the License.
|
||||
<EditText
|
||||
android:id="@+id/marker_edit_waypoint_name"
|
||||
style="@style/EditNameMarker"
|
||||
android:drawableLeft="@drawable/ic_marker_blue_pushpin"
|
||||
android:drawableStart="@drawable/ic_marker_orange_pushpin_with_shadow"
|
||||
android:hint="@string/generic_name"
|
||||
android:imeOptions="actionNext"
|
||||
android:importantForAutofill="no" />
|
||||
|
||||
@@ -23,11 +23,6 @@ limitations under the License.
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/track_controller_record"
|
||||
android:layout_width="0dp"
|
||||
@@ -39,14 +34,16 @@ limitations under the License.
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="6">
|
||||
android:layout_weight="6"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/track_controller_total_time"
|
||||
style="@style/TextSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
@@ -56,7 +53,7 @@ limitations under the License.
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@id/track_controller_total_time"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:singleLine="true" />
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -69,9 +66,5 @@ limitations under the License.
|
||||
android:contentDescription="@string/image_stop"
|
||||
android:src="@drawable/ic_button_stop_disabled" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@@ -69,7 +69,7 @@ Si está parado o en el interior, no se registrarán los datos del sensor (pero
|
||||
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks no está grabando las localizaciones?</string>
|
||||
<string name="help_gps_troubleshooting_content">
|
||||
Lo que puedes hacer...
|
||||
Lo que puedes hacer…
|
||||
a) Comprobar si el GPS está activado.
|
||||
b) mejorar la recepción del GPS (por ejemplo, ir al exterior),\n
|
||||
c) comprueba si con <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, y\n
|
||||
@@ -79,7 +79,7 @@ Si todo no ayuda, por favor informe de un error.</string>
|
||||
|
||||
<string name="help_support_title">¿Cómo puedo apoyar OpenTracks?</string>
|
||||
<string name="help_support_content">
|
||||
Puedes...
|
||||
Puedes…
|
||||
a) difundir la palabra sobre OpenTracks,...
|
||||
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">proponer características</a>,\n
|
||||
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">report bugs</a>,\n
|
||||
|
||||
@@ -25,7 +25,7 @@ limitations under the License.
|
||||
<string name="help_feature_title">OpenTracks heeft de volgende kenmerken:</string>
|
||||
<string name="help_feature_content">
|
||||
* creëer GPS tracks
|
||||
* gebruik een hartslagsensor (alleen Bluetooth LE)...
|
||||
* gebruik een hartslagsensor (alleen Bluetooth LE)…
|
||||
* spraakaankondigingen (op tijd of afstand)...
|
||||
* creëer markers (incl. fotos)
|
||||
* werkt volledig offline
|
||||
@@ -79,7 +79,7 @@ Als alles niet helpt, meldt u dan alstublieft een bug.</string>
|
||||
|
||||
<string name="help_support_title">Hoe kan ik OpenTracks ondersteunen?</string>
|
||||
<string name="help_support_content">
|
||||
Je kunt...
|
||||
Je kunt…
|
||||
a) het woord verspreiden over OpenTracks, en...
|
||||
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">voorstellen van functies</a>,\n
|
||||
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">melding van bugs</a>,\n
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<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="stats_fullscreen_while_recording_key" translatable="false">trackdetail_fullscreen_while_recording</string>
|
||||
<bool name="stats_fullscreen_while_recording_default" translatable="false">false</bool>
|
||||
|
||||
<string name="settings_reset_key" translatable="false">settingsReset</string>
|
||||
|
||||
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
|
||||
|
||||
@@ -349,6 +349,8 @@ limitations under the License.
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary">While recording, show statistics without unlocking the device.</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title">Keep screen on</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary">While recording, keep the screen on.</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title">Fullscreen</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">While recording, present in fullscreen.</string>
|
||||
<string name="settings_recording">Recording</string>
|
||||
<string name="settings_recording_auto_resume_track_timeout_always_summary">Always resume a recording after reboot</string>
|
||||
<string name="settings_recording_auto_resume_track_timeout_never_summary">Never resume a recording after reboot</string>
|
||||
|
||||
@@ -67,6 +67,11 @@ limitations under the License.
|
||||
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" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/stats_fullscreen_while_recording_default"
|
||||
android:key="@string/stats_fullscreen_while_recording_key"
|
||||
android:summary="@string/settings_recording_fullscreen_on_while_recording_summary"
|
||||
android:title="@string/settings_recording_fullscreen_on_while_recording_title" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/voice_frequency_default"
|
||||
android:entryValues="@array/frequency_values"
|
||||
|
||||
Reference in New Issue
Block a user