mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-24 19:24:48 +02:00
Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cdf122e8c | |||
| 495ef2fc6f | |||
| ff561c1b25 | |||
| 7106f64a3c | |||
| 8420f9febb | |||
| 0744ba790f | |||
| 9a7f24bddc | |||
| ef47c86e7c | |||
| 93f203bcf1 | |||
| 6b0ec72b9b | |||
| d46cea6150 | |||
| 0e3ffa58f0 | |||
| c00d769188 | |||
| 00489a121a | |||
| d7f339c6f8 | |||
| da28ea3e95 | |||
| 9872658f81 | |||
| b6b155f19c | |||
| 067f31ac21 | |||
| 3f0ce27870 | |||
| 438f8fe273 | |||
| 392c02a56f | |||
| 6dca3bea58 | |||
| 13034c8bb3 | |||
| 1175ce270e | |||
| 02cad98a4f | |||
| d6b38a390e | |||
| 98deed8ef7 | |||
| 9983168cb9 | |||
| 7b5f29db09 | |||
| 49efd04326 | |||
| 3159c62eb2 | |||
| 0e8811d8c9 | |||
| 4126e36f1c | |||
| b7e2df9f2f | |||
| 055093772d | |||
| 7f8b49814b | |||
| 5cc0e06fbb | |||
| 311a2550e5 | |||
| 4311b29833 | |||
| 86121ac27a | |||
| ed6daeb48b | |||
| af72960cf6 | |||
| 99767d0c0b | |||
| 1fc0a552c7 | |||
| 18dd1de1f9 | |||
| ae3078304d | |||
| 48b6438c06 | |||
| e822172f58 | |||
| a3aee403a1 | |||
| aac2ad4165 | |||
| 76d5e5780c | |||
| 931fd7514a | |||
| d1570ad700 | |||
| 04fff89f68 | |||
| b57fd368ae | |||
| 58d6a3b5a9 | |||
| 289a5273a9 | |||
| 120e20a0ea | |||
| 2843d19450 | |||
| 50127e311a | |||
| f83a1b3a4c | |||
| 65ecbc4521 | |||
| fa50db08c3 | |||
| b395c19832 | |||
| 7e6ffbabd2 | |||
| 6b43d341d0 | |||
| 8af0fb3250 | |||
| 49219de46a | |||
| 1baf652b81 | |||
| 6723bfb979 | |||
| 8357b234d2 | |||
| 9de9fbd058 | |||
| 6da6fc6004 | |||
| 114fc31be0 | |||
| ae795a76fa | |||
| 92004b0ec7 | |||
| f277347529 | |||
| ad1aa06407 | |||
| 3b92303511 | |||
| e62faa1253 | |||
| d3900f4dad | |||
| 00b8834795 | |||
| 1672a5c722 | |||
| bf552a9219 | |||
| 348f697b56 | |||
| 887d4700d4 | |||
| 391dfd30e2 | |||
| b416857001 | |||
| df3720ef0d | |||
| b784a47af6 | |||
| 0f3156e71b | |||
| 0035c85922 | |||
| 976a50ca15 | |||
| c0ad53eef1 | |||
| 1192b02c0a | |||
| 7a3abe9962 | |||
| 60b989c13a | |||
| 340f16dc99 | |||
| 7cf29f7cea | |||
| e39352cc9e | |||
| e2964e805e | |||
| 8cab43d558 | |||
| 13f2a09652 | |||
| 7d4072c0d8 | |||
| 310b35713d | |||
| 7e6237de3a | |||
| 772dad4837 | |||
| 99126fa357 | |||
| b2c91d7592 | |||
| 3c5a1be16b | |||
| 01d83deefd | |||
| bc2a3ddee3 | |||
| da70fca389 | |||
| 67bbd9ca24 | |||
| 5e1293fc82 | |||
| 610da5a106 | |||
| 2b5744ccc1 | |||
| f0341e831f | |||
| 99e5dfbe72 | |||
| 0fb43f6530 | |||
| 6f9c788965 | |||
| f2b1d2a386 | |||
| 18789a0729 | |||
| d0f99be9d3 | |||
| a899be4598 | |||
| 1e518a4c35 | |||
| 1c6012596e | |||
| 978206dee4 | |||
| 8713eb3d0c | |||
| d450e045b1 | |||
| 9f87f67153 | |||
| 3a073a3b0a | |||
| bc1b4e3cd2 | |||
| e09b04308c | |||
| f3cf1a2571 | |||
| 7be4ac436c | |||
| 405120a78b | |||
| e86aeace79 | |||
| afe5c6ee92 | |||
| 055764b462 | |||
| 360738f4c8 | |||
| 3bd3e8fd9b | |||
| 5cf421cf42 | |||
| 455522a03d | |||
| b4c258c686 | |||
| 05ee54d58c | |||
| 28fbcdd2ca | |||
| 0231343aa8 | |||
| 852885cca1 | |||
| d3c1247617 |
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [29]
|
||||
api-level: [ 23, 26, 29 ]
|
||||
target: [default]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
uses: reactivecircus/android-emulator-runner@v2.16.0
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
Generated
+1
@@ -3,6 +3,7 @@
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
|
||||
@@ -49,39 +49,6 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Features:
|
||||
* __Tracking:__ track your sport and outdoor activities
|
||||
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
||||
* __Bluetooth LE sensors:__
|
||||
* heart rate
|
||||
* cycling: speed and cadence
|
||||
* cycling: power meter
|
||||
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
||||
* __No Internet access:__ Internet is not used
|
||||
* __Voice announcements__
|
||||
* __No advertising__
|
||||
* __Privacy:__
|
||||
* _does not contain any in-app analytics_
|
||||
* _does not use Google Play Services_
|
||||
* _has no cloud integration_
|
||||
|
||||
__Only required permissions:__
|
||||
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
||||
|
||||
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
||||
|
||||
_Please note:_
|
||||
_OpenTracks_ does not contain a _map_ (as this would require internet access).
|
||||
|
||||
## Custom Dashboards (incl. Map)
|
||||
As of v3.3.1 OpenTracks enables to use custom dashboards.
|
||||
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
|
||||
The developer documentation is in [README_API.md](README_API.md).
|
||||
|
||||
Alternatively, recorded tracks can be shared as KMZ/GPX to installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)).
|
||||
However, this is rather slow and does not provide updates while recording.
|
||||
|
||||
|
||||
## Screenshots
|
||||
<div>
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot1.png">
|
||||
@@ -96,12 +63,54 @@ However, this is rather slow and does not provide updates while recording.
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot8.png">
|
||||
</div>
|
||||
|
||||
## Features
|
||||
* __Tracking:__ track your sport and outdoor activities
|
||||
* __Voice announcements__
|
||||
* __Photos and Markers:__ mark interesting locations while tracking
|
||||
* __Export:__
|
||||
* export tracks either as KMZ (incl. photos), KML, or GPX
|
||||
* export automatically after each recording (e.g., to sync)
|
||||
* avoid duplication: each exported file contain a random unique identifier (i.e., `opentracks:trackid`)
|
||||
* __Altitude:__
|
||||
* gain/loss via barometric sensor (if present)
|
||||
* shown in EGM2008 (above mean sea level); exported as WGS84
|
||||
* __Bluetooth LE sensors:__
|
||||
* heart rate
|
||||
* cycling: speed and distance
|
||||
* cycling: cadence
|
||||
* cycling: power meter
|
||||
|
||||
### Gadgetbridge integration
|
||||
|
||||
OpenTracks can be used with [Gadgetbridge](https://www.gadgetbridge.org/):
|
||||
* shows statistics via notification on smart watches (requires Gadgetbridge 0.56.1 or later), and
|
||||
* Gadgetbridge's GPX exporter generates `opentracks:trackid` to avoid duplication (Gadgetbridge 0.53.0 or later).
|
||||
|
||||
### Privacy
|
||||
* __No Internet access:__ Internet is not used
|
||||
* __No advertising__
|
||||
* __No in-app analytics__
|
||||
* __No use of Google Play Services__
|
||||
|
||||
__Only required permission:__
|
||||
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
||||
|
||||
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
||||
|
||||
## Custom Dashboards (incl. map)
|
||||
As of v3.3.1, OpenTracks supports custom dashboards.
|
||||
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
|
||||
The developer documentation is in [README_API.md](README_API.md).
|
||||
|
||||
Alternatively, recorded tracks can be shared as KMZ/GPX with installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)).
|
||||
However, this is rather slow and does not provide updates while recording.
|
||||
|
||||
## Project history
|
||||
|
||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
|
||||
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
|
||||
The [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
|
||||
Then [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
|
||||
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
|
||||
In 2019, _OpenTracks_ was forked from Plonk42's _My Tracks_ and major rework was conducted.
|
||||
|
||||
@@ -114,11 +123,11 @@ Rework of _OpenTracks_ included:
|
||||
* removing calorie estimation and activity estimation,
|
||||
* removing support for ANT+ and Classic Bluetooth,
|
||||
* adding support for Bluetooth LE heart rate sensors,
|
||||
* removing Protobuf (store sensosensorr data in SQLite columns directly), and
|
||||
* removing Protobuf (store sensor data in SQLite columns directly), and
|
||||
* removing Android Service API for other apps.
|
||||
|
||||
Artwork, logos and user interface remained more or less unchanged.
|
||||
|
||||
More information about _Google My Tracks_:
|
||||
* [Wikipedia page](https://en.wikipedia.org/wiki/MyTracks)
|
||||
* [Google code archive](https://code.google.com/archive/p/mytracks/)
|
||||
* [Google code archive](https://code.google.com/archive/p/mytracks/)
|
||||
@@ -10,6 +10,7 @@
|
||||
Has updatable firmware that requires a user account at polar.com
|
||||
* Wahoo Tickr (Model: WFBTHR02)
|
||||
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
|
||||
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
|
||||
|
||||
## CSCP: Cycling Cadence and Speed Service
|
||||
|
||||
@@ -27,6 +28,8 @@ However, often only one value is provided.
|
||||
|
||||
* Wahoo Speed (Model: WFRPMSPD)
|
||||
|
||||
* CooSpo cadence and speed sensor (model: BK467)
|
||||
|
||||
### Cadence
|
||||
|
||||
* Garmin Cadence Sensor 2
|
||||
|
||||
+12
-8
@@ -3,17 +3,17 @@ apply plugin: 'com.android.application'
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath 'com.android.tools.build:gradle:4.2.1'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +49,14 @@ android {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
cruncherEnabled = false
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 3941
|
||||
versionName "v3.16.0"
|
||||
versionCode 4092
|
||||
versionName "v3.18.1"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -86,10 +90,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha05'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha06'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
@@ -99,5 +103,5 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.8.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.10.0'
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
@startuml
|
||||
SensorManager --> ElevationSumManager
|
||||
SensorManager --> AltitudeSumManager
|
||||
LocationManager --> TrackRecordingService
|
||||
ElevationSumManager --> TrackRecordingService
|
||||
|
||||
@@ -14,6 +14,6 @@ TrackDataHub --> StatisticsRecordingFragment : TrackPoints, TrackStatistics, Way
|
||||
TrackDataHub --> StatisticsRecordedFragment : TrackPoints, TrackStatistics, Waypoints (push)
|
||||
TrackDataHub --> ChartFragment --> ChartView
|
||||
|
||||
TrackRecordingServiceConnection --> StatisticsRecordingFragment : Sensor data (heartrate, cadence, elevation gain; pull 1s)
|
||||
TrackRecordingServiceConnection --> StatisticsRecordingFragment : Sensor data (heartrate, cadence, altitude gain; pull 1s)
|
||||
TrackRecordingServiceConnection --> TrackController : Start time (pull, onStart)
|
||||
@enduml
|
||||
@@ -18,7 +18,7 @@ class TrackStatistics {
|
||||
- totalTime
|
||||
- movingTime
|
||||
- maxSpeed
|
||||
- totalElevationGain
|
||||
- totalGain
|
||||
}
|
||||
|
||||
class TrackStatisticsUpdater {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="22"
|
||||
width="22"
|
||||
fill="#000000"
|
||||
version="1.0"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 21.84952 21.99934"
|
||||
enable-background="new 0 0 99.316 99.997"
|
||||
xml:space="preserve"
|
||||
id="svg10"
|
||||
sodipodi:docname="ic_activity_kayaking.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
|
||||
id="metadata16"><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 /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs14" /><sodipodi:namedview
|
||||
pagecolor="#000000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1024"
|
||||
id="namedview12"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:zoom="22.472123"
|
||||
inkscape:cx="11.932326"
|
||||
inkscape:cy="8.4821218"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg10"
|
||||
inkscape:document-rotation="0" /><path
|
||||
id="path2"
|
||||
style="fill:#ffffff;stroke-width:0.20758"
|
||||
d="m 18.296044,13.516712 c -1.017125,-0.01824 -1.903328,0.770666 -2.945647,0.500454 -0.952298,-0.271819 -1.950516,-0.765534 -2.949277,-0.338881 -0.938402,0.492101 -2.078868,0.493759 -3.017025,0 C 8.3845167,13.251481 7.3852162,13.744815 6.4321693,14.017166 5.229901,14.31201 4.2005498,13.256398 2.9984219,13.558671 2.2076549,13.806805 1.0222411,14.11299 1.0222411,14.11299 v 1.764949 c 1.0226762,0.01947 1.9128198,-0.776355 2.9601398,-0.498166 0.4416703,0.216761 1.6682446,0.3639 1.6529583,0.601941 l -1.3879215,1.383539 c -0.7385797,-0.502111 -1.5118619,0.0527 -1.9901253,0.625328 -0.5087615,0.568011 -1.1635382,1.025896 -1.5505475,1.687065 -0.44607206,0.952772 0.6576964,2.056499 1.6104671,1.610525 0.7016688,-0.428027 1.2006595,-1.122907 1.8093513,-1.672458 0.6218606,-0.429609 0.7776032,-1.247655 0.6297837,-1.861615 l 2.0038313,-1.999046 c 0.9553792,-0.44042 2.0942104,-0.606317 3.0559286,-0.07975 0.9566571,0.429062 1.9815791,0.09313 2.8934291,-0.263824 1.038124,-0.339466 1.95525,0.382078 2.955963,0.461915 1.087184,0.105691 2.019821,-0.784126 3.121615,-0.493622 0.55673,-0.01509 1.943197,1.034046 1.977683,0.148315 v -1.471252 c -0.878739,0.05089 -1.592095,-0.588897 -2.468753,-0.540124 z"
|
||||
sodipodi:nodetypes="sccccccccccccccccccccccss" /><path
|
||||
id="path4"
|
||||
style="fill:#ffffff;stroke-width:0.20758"
|
||||
d="M 19.135498,4.6094374 C 19.777729,3.9239548 20.518106,3.3189058 21.090567,2.5760423 21.630775,1.6723294 20.631775,0.46345938 19.642408,0.81572972 18.952145,1.1177762 18.525406,1.7968856 17.966518,2.2833866 17.44088,2.7777653 16.748383,3.4079168 17.123672,4.2079927 17.316456,4.5791871 16.670933,4.8230987 16.479267,5.1359209 L 15.065885,6.5509368 C 13.459538,5.9569743 11.849312,5.3583867 10.246781,4.7652258 9.2045735,4.5310231 8.2546176,5.3827898 8.1060604,6.3824119 7.7002321,7.7689937 7.2645314,9.1479847 6.8776693,10.539386 h -5.851069 v 2.366209 c 1.0341353,-0.06583 1.9719234,-0.790016 3.0482038,-0.491433 0.8514673,0.182595 1.7032854,0.742921 2.5975093,0.377297 1.0893234,-0.553582 2.4097276,-0.562618 3.4983496,-0.0022 0.895986,0.368665 1.751474,-0.190407 2.60469,-0.37517 1.078334,-0.302717 2.015803,0.427862 3.051521,0.490909 0.985146,-0.07753 1.867505,-0.470806 2.884112,-0.579104 0.847652,-0.0903 2.163908,-1.785603 2.163908,-1.785603 0,0 -1.819193,-0.01538 -2.935123,0.0029 -1.990467,-2.45e-4 -3.980934,-0.0021 -5.9714,-0.0044 l 2.483907,-2.4814163 c 0.762424,0.5726687 1.957001,-0.3877847 1.20272,-1.1977389 l 2.032212,-2.039685 c 0.462782,0.2519942 1.079225,0.1706574 1.448288,-0.2104864 z m -8.060346,5.9299486 c -0.486522,0.101516 -0.612149,-0.06543 -0.374498,-0.502616 l 0.909432,-3.029364 2.189559,0.8099787 z"
|
||||
sodipodi:nodetypes="cccccccccccccccccsccccccccccccc" /><path
|
||||
id="path851"
|
||||
style="fill:#ffffff;stroke-width:1.16861"
|
||||
d="m 12.520186,2.6733336 c 0.05505,1.1807821 -1.406378,2.0132887 -2.394197,1.3656163 -1.0445314,-0.553724 -1.0179219,-2.235304 0.04332,-2.7559413 1.018447,-0.59197226 2.378758,0.20782 2.350878,1.390325 z" /></svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,18 @@
|
||||
v3.17.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Add icon for kayaking
|
||||
- Charts draw missing sensor data properly
|
||||
- Default: show coordinates while recording
|
||||
|
||||
Bugfixes:
|
||||
- GPX schema corrected
|
||||
- GPX exported altitude loss as sensor distance
|
||||
- Statistics not computed correctly if using a speed/distance sensor
|
||||
|
||||
Developer:
|
||||
- Distance/speed value objects
|
||||
- Replaced communication via SharedPreferences
|
||||
- Use non-static service binder
|
||||
- Use altitude in the code base (instead of elevation)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.17.1: OpenTracks
|
||||
|
||||
|
||||
Bugfix:
|
||||
- Interval length in meters instead of kilometers (introduced in v3.17.0)
|
||||
- Elevation gain/loss not recorded (introduced in v3.17.0)
|
||||
- Announcements not working on Android 11
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.17.2: OpenTracks
|
||||
|
||||
WARNING: The v3.17.0+ releases contain a memory leak.
|
||||
This issues is NOT FIXED by this release.
|
||||
It is recommended to not upgrade if you still use a release before v3.17.0
|
||||
|
||||
Bugfix:
|
||||
- Crash while using voice announcements
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.17.3: OpenTracks
|
||||
|
||||
WARNING: The v3.17.0+ releases contain a memory leak.
|
||||
This issues is NOT FIXED by this release.
|
||||
It is recommended to not upgrade if you still use a release before v3.17.0
|
||||
|
||||
Bugfix:
|
||||
- Crash if using pace for voice announcements (introduced in v3.17.0)
|
||||
- Statistics showed pace initially (introduced in v3.17.0)
|
||||
- Rare crash when deleting tracks
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
v3.18.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Altitude is shown in EGM2008 (stored/exported as WGS84)
|
||||
|
||||
Bugfix:
|
||||
- Fixed memory leak (introduced in v3.17.0)
|
||||
- Charts: altitude should be in M/FT (introduced in v3.17.1)
|
||||
- Charts: while recording was not correctly updated (introduced in v3.17.3)
|
||||
- TrackDataHub did not account for recordingDistanceInterval (introduced in v3.17.2)
|
||||
|
||||
Developer:
|
||||
- Disable PNG crunching (reproducible build)
|
||||
- Refactored TrackImporter and optimized KMZ import
|
||||
- Use LiveData for TrackRecordingService to UI communication (removing memory leak)
|
||||
@@ -0,0 +1,13 @@
|
||||
v3.18.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Deleting tracks is handled by a service
|
||||
- Uninstall deletes all data (i.e., hasFragileData=false)
|
||||
|
||||
Bugfixes:
|
||||
- Race condition: crash while recording and switching OpenTracks into background
|
||||
- Search in track list was not working
|
||||
- Cycling speed sensor: distance computation was wrong (only affected statistics)
|
||||
|
||||
Developer:
|
||||
- ControllerFragment replaces TrackController
|
||||
@@ -3,9 +3,9 @@ OpenTracks is a sport tracking application that completely respects your privacy
|
||||
Features:
|
||||
* Sport tracking: track your sport and outdoor activities
|
||||
* Photos and markers: mark interesting locations while tracking
|
||||
* Supports Bluetooth LE sensors:
|
||||
* heart rate
|
||||
* cycling: speed and cadence
|
||||
* cycling: power meter
|
||||
* Export data: export tracks either as KML or GPX
|
||||
* No Internet access: Internet is not used
|
||||
* Altitude gain and loss: via barometric sensor
|
||||
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84
|
||||
* Voice announcements
|
||||
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling)
|
||||
* Export data: export tracks either as KMZ (incl. photos), KML, or GPX
|
||||
* No Internet access: Internet is not used
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -82,6 +82,7 @@ esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@@ -129,6 +130,7 @@ fi
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
|
||||
Vendored
+104
-103
@@ -1,103 +1,104 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.test.espresso.Espresso;
|
||||
import androidx.test.espresso.IdlingResource;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.action.MotionEvents;
|
||||
import androidx.test.espresso.assertion.ViewAssertions;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.services.TrackDeleteService;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onData;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.Espresso.pressBack;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoDeleteTrackTest {
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant("android.permission.ACCESS_FINE_LOCATION");
|
||||
|
||||
private MyIdlingResource idlingResource;
|
||||
|
||||
@Before
|
||||
public void registerIntentServiceIdlingResource() {
|
||||
Instrumentation instrumentation
|
||||
= InstrumentationRegistry.getInstrumentation();
|
||||
idlingResource = new MyIdlingResource(instrumentation.getTargetContext());
|
||||
Espresso.registerIdlingResources(idlingResource);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unregisterIntentServiceIdlingResource() {
|
||||
Espresso.unregisterIdlingResources(idlingResource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void espressoDeleteTrackTest() {
|
||||
int countBefore;
|
||||
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(5000));
|
||||
|
||||
// stop;
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
}
|
||||
|
||||
// back
|
||||
pressBack();
|
||||
|
||||
// get number of items before deleting a track
|
||||
countBefore = numberOfItemsListView();
|
||||
|
||||
// select track
|
||||
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(5000));
|
||||
|
||||
// open menu and delete selected track
|
||||
ViewInteraction overflowMenuButton = onView(
|
||||
allOf(withContentDescription("More options"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.toolbar),
|
||||
2),
|
||||
2),
|
||||
isDisplayed()));
|
||||
overflowMenuButton.perform(click());
|
||||
|
||||
ViewInteraction appCompatTextView = onView(
|
||||
allOf(withId(R.id.title), withText("Delete"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.content),
|
||||
0),
|
||||
0),
|
||||
isDisplayed()));
|
||||
appCompatTextView.perform(click());
|
||||
|
||||
ViewInteraction appCompatButton = onView(
|
||||
allOf(withId(android.R.id.button1), withText("Yes"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.buttonPanel),
|
||||
0),
|
||||
3)));
|
||||
appCompatButton.perform(scrollTo(), click());
|
||||
|
||||
// check number of items after delete
|
||||
onView(withId(R.id.track_list)).check(ViewAssertions.matches(withListSize(countBefore - 1)));
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(
|
||||
final Matcher<View> parentMatcher, final int position) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Child at position " + position + " in parent ");
|
||||
parentMatcher.describeTo(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
return parent instanceof ViewGroup && parentMatcher.matches(parent)
|
||||
&& view.equals(((ViewGroup) parent).getChildAt(position));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<View> withListSize (final int size) {
|
||||
return new TypeSafeMatcher<View> () {
|
||||
@Override public boolean matchesSafely (final View view) {
|
||||
return ((ListView) view).getCount () == size;
|
||||
}
|
||||
|
||||
@Override public void describeTo (final Description description) {
|
||||
description.appendText ("ListView should have " + size + " items");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction waitFor(final long duration_ms) {
|
||||
return new ViewAction() {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Wait for milliseconds.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction veryLongTouch(final int duration_ms) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Perform long touch.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
// Get view absolute position
|
||||
int[] location = new int[2];
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
// Offset coordinates by view position
|
||||
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
|
||||
|
||||
// Send down event, pause, and send up
|
||||
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
MotionEvents.sendUp(uiController, down, coordinates);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private int numberOfItemsListView() {
|
||||
final int[] counts = new int[1];
|
||||
onView(withId(R.id.track_list)).check(matches(new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
ListView listView = (ListView) view;
|
||||
|
||||
counts[0] = listView.getCount();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
return counts[0];
|
||||
}
|
||||
|
||||
|
||||
private class MyIdlingResource implements IdlingResource {
|
||||
|
||||
private Context context;
|
||||
private ResourceCallback resourceCallback;
|
||||
|
||||
public MyIdlingResource(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return MyIdlingResource.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIdleTransitionCallback(IdlingResource.ResourceCallback resourceCallback) {
|
||||
this.resourceCallback = resourceCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdleNow() {
|
||||
boolean idle = !isDeleteServiceRunning();
|
||||
if (idle && resourceCallback != null) {
|
||||
resourceCallback.onTransitionToIdle();
|
||||
}
|
||||
return idle;
|
||||
}
|
||||
|
||||
private boolean isDeleteServiceRunning() {
|
||||
ActivityManager manager =
|
||||
(ActivityManager) context.getSystemService(
|
||||
Context.ACTIVITY_SERVICE);
|
||||
for (ActivityManager.RunningServiceInfo info :
|
||||
manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (TrackDeleteService.class.getName().equals(
|
||||
info.service.getClassName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,13 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onData;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@@ -39,13 +42,13 @@ public class EspressoUITest {
|
||||
public void record_pause_resume_stop() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(5000));
|
||||
@@ -72,13 +75,13 @@ public class EspressoUITest {
|
||||
public void record_move_through_tabs() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(1));
|
||||
tabLayout.perform(waitFor(1000));
|
||||
@@ -97,6 +100,13 @@ public class EspressoUITest {
|
||||
}
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void selectAndDeleteTrack() {
|
||||
onView(withId(R.id.track_list)).check(matches(isDisplayed()));
|
||||
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(2000));
|
||||
}
|
||||
|
||||
private static ViewAction veryLongTouch(final int duration_ms) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.fragments.TrackStubUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ChartPointTest {
|
||||
|
||||
@Test
|
||||
public void create_by_time() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setTotalTime(Duration.ofSeconds(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, false, false);
|
||||
|
||||
// then
|
||||
assertEquals(1000000, (long) point.getTimeOrDistance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_by_distance() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setTotalDistance(Distance.of(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, true, true);
|
||||
|
||||
// then
|
||||
assertEquals(1, (long) point.getTimeOrDistance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_get_altitude_speed_and_pace() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
||||
|
||||
// then
|
||||
assertEquals(50, point.getAltitude(), 0.01);
|
||||
assertEquals(36, point.getSpeed(), 0.01);
|
||||
assertEquals(1.66, point.getPace(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_sensorNotAvailable() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
||||
|
||||
// then
|
||||
assertNull(point.getHeartRate());
|
||||
assertNull(point.getCadence());
|
||||
assertNull(point.getPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_sensorAvailable() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setHeartRate_bpm(100f);
|
||||
trackPoint.setCyclingCadence_rpm(101f);
|
||||
trackPoint.setPower(102f);
|
||||
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), 50, false, true);
|
||||
|
||||
// then
|
||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||
assertEquals(101.0, point.getCadence(), 0.01);
|
||||
assertEquals(102.0, point.getPower(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -44,16 +44,16 @@ public class ChartValueSeriesTest {
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
Integer.MIN_VALUE,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{100, 1000},
|
||||
R.string.description_elevation_metric,
|
||||
R.string.description_elevation_imperial,
|
||||
R.color.chart_elevation_fill,
|
||||
R.color.chart_elevation_border,
|
||||
new int[]{1, 100, 1000},
|
||||
R.string.description_altitude_metric,
|
||||
R.string.description_altitude_imperial,
|
||||
R.color.chart_altitude_fill,
|
||||
R.color.chart_altitude_border,
|
||||
15,
|
||||
18) {
|
||||
@Override
|
||||
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getElevation();
|
||||
Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getAltitude();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,6 +77,17 @@ public class ChartValueSeriesTest {
|
||||
assertFalse(series.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerySmallUpdates() {
|
||||
series.update(new ChartPoint(1f));
|
||||
series.update(new ChartPoint(2f));
|
||||
series.update(new ChartPoint(3f));
|
||||
series.updateDimension();
|
||||
assertEquals(1, series.getInterval());
|
||||
assertEquals(1, series.getMinMarkerValue());
|
||||
assertEquals(6, series.getMaxMarkerValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallUpdates() {
|
||||
series.update(new ChartPoint(0));
|
||||
|
||||
+29
-27
@@ -29,6 +29,8 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
@@ -60,24 +62,24 @@ public class DescriptionGeneratorTest {
|
||||
public void testGenerateTrackDescription() {
|
||||
Track track = new Track();
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setMaxElevation(550);
|
||||
stats.setMinElevation(-500);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
stats.setTotalElevationLoss(6000f);
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setMaxAltitude(550);
|
||||
stats.setMinAltitude(-500);
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
stats.setTotalAltitudeLoss(6000f);
|
||||
stats.setStartTime(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
String expected = //"Created by"
|
||||
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
|
||||
+ "Name: -<br>"
|
||||
+ "Activity type: hiking<br>"
|
||||
+ "Description: -<br>"
|
||||
+ "Total distance: 20.00 km (12.4 mi)<br>"
|
||||
+ "Total time: 10:00<br>"
|
||||
+ "Name: -<br>"
|
||||
+ "Activity type: hiking<br>"
|
||||
+ "Description: -<br>"
|
||||
+ "Total distance: 20.00 km (12.4 mi)<br>"
|
||||
+ "Total time: 10:00<br>"
|
||||
+ "Moving time: 05:00<br>"
|
||||
+ "Average speed: 120.00 km/h (74.6 mi/h)<br>"
|
||||
+ "Average moving speed: 240.00 km/h (149.1 mi/h)<br>"
|
||||
@@ -95,17 +97,17 @@ public class DescriptionGeneratorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateTrackDescriptionWithoutMaxMinElevation() {
|
||||
public void testGenerateTrackDescriptionWithoutMaxMinAltitude() {
|
||||
Track track = new Track();
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setMaxElevation(Double.POSITIVE_INFINITY);
|
||||
stats.setMinElevation(Double.NEGATIVE_INFINITY);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
stats.setTotalElevationLoss(6000f);
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setMaxAltitude(Double.POSITIVE_INFINITY);
|
||||
stats.setMinAltitude(Double.NEGATIVE_INFINITY);
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
stats.setTotalAltitudeLoss(6000f);
|
||||
stats.setStartTime(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
@@ -132,12 +134,12 @@ public class DescriptionGeneratorTest {
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeDistance(Distance, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteDistance() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writeDistance(1100, builder, R.string.description_total_distance, "<br>");
|
||||
descriptionGenerator.writeDistance(Distance.of(1100), builder, R.string.description_total_distance, "<br>");
|
||||
assertEquals("Total distance: 1.10 km (0.7 mi)<br>", builder.toString());
|
||||
}
|
||||
|
||||
@@ -152,32 +154,32 @@ public class DescriptionGeneratorTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeSpeed(Speed, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteSpeed() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writeSpeed(1.1, builder, R.string.description_average_speed, "\n");
|
||||
descriptionGenerator.writeSpeed(Speed.of(1.1), builder, R.string.description_average_speed, "\n");
|
||||
assertEquals("Average speed: 3.96 km/h (2.5 mi/h)\n", builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writeElevation(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeAltitude(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteElevation() {
|
||||
public void testWriteAltitude() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writeElevation(4.2, builder, R.string.description_min_elevation, "<br>");
|
||||
descriptionGenerator.writeAltitude(4.2, builder, R.string.description_min_altitude, "<br>");
|
||||
assertEquals("Min elevation: 4 m (14 ft)<br>", builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writePace(Speed, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWritePace() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n");
|
||||
descriptionGenerator.writePace(Speed.of(1.1), builder, R.string.description_average_pace_in_minute, "\n");
|
||||
assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import android.util.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class TestDataUtil {
|
||||
@@ -19,8 +21,8 @@ public class TestDataUtil {
|
||||
public static final double INITIAL_LATITUDE = 37.0;
|
||||
public static final double INITIAL_LONGITUDE = -57.0;
|
||||
public static final double ALTITUDE_INTERVAL = 2.5;
|
||||
public static final float ELEVATION_GAIN = 3;
|
||||
public static final float ELEVATION_LOSS = 3;
|
||||
public static final float ALTITUDE_GAIN = 3;
|
||||
public static final float ALTITUDE_LOSS = 3;
|
||||
|
||||
/**
|
||||
* Create a track without any trackPoints.
|
||||
@@ -57,7 +59,7 @@ public class TestDataUtil {
|
||||
|
||||
int i = 0;
|
||||
List<TrackPoint> trackPoints = List.of(
|
||||
TrackPoint.createSegmentStartManualWithTime(Instant.ofEpochMilli(i++ + 1)),
|
||||
TrackPoint.createSegmentStartManualWithTime(Instant.ofEpochSecond(i++)),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
@@ -65,19 +67,23 @@ public class TestDataUtil {
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
TrackPoint.createSegmentEndWithTime(Instant.ofEpochSecond(i++ + 1)),
|
||||
createTrackPoint(i++, TrackPoint.Type.SEGMENT_END_MANUAL),
|
||||
|
||||
TrackPoint.createSegmentStartManualWithTime(Instant.ofEpochSecond(i++)),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
TrackPoint.createSegmentEndWithTime(Instant.ofEpochSecond(i++ + 1))
|
||||
createTrackPoint(i, TrackPoint.Type.SEGMENT_END_MANUAL)
|
||||
);
|
||||
|
||||
//TODO Use TrackStatisticsUpdater
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(0));
|
||||
stats.setTotalTime(Duration.ofMillis(0));
|
||||
List<Marker> markers = List.of(
|
||||
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(1), null),
|
||||
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(4), null),
|
||||
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(5), null)
|
||||
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, stats, trackPoints.get(1), null),
|
||||
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, stats, trackPoints.get(4), null),
|
||||
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, stats, trackPoints.get(5), null)
|
||||
);
|
||||
|
||||
return new TrackData(track, trackPoints, markers);
|
||||
@@ -104,11 +110,6 @@ public class TestDataUtil {
|
||||
return pair.first;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*
|
||||
* @param i the index for the TrackPoint.
|
||||
*/
|
||||
public static TrackPoint createTrackPoint(int i) {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
trackPoint.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
@@ -116,13 +117,13 @@ public class TestDataUtil {
|
||||
trackPoint.setAccuracy((float) i / 100.0f);
|
||||
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
trackPoint.setTime(Instant.ofEpochSecond(i + 1));
|
||||
trackPoint.setSpeed(5f + (i / 10f));
|
||||
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
|
||||
|
||||
trackPoint.setHeartRate_bpm(100f + i);
|
||||
trackPoint.setCyclingCadence_rpm(300f + i);
|
||||
trackPoint.setPower(400f + i);
|
||||
trackPoint.setElevationGain(ELEVATION_GAIN);
|
||||
trackPoint.setElevationLoss(ELEVATION_LOSS);
|
||||
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
|
||||
trackPoint.setAltitudeLoss(ALTITUDE_LOSS);
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
@@ -149,7 +150,12 @@ public class TestDataUtil {
|
||||
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
|
||||
String photoUrl = photoUri.toString();
|
||||
|
||||
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, 0.0, 0, trackPoint, photoUrl);
|
||||
//TODO Use TrackStatisticsUpdater
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(0));
|
||||
stats.setTotalTime(Duration.ofMillis(0));
|
||||
|
||||
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, stats, trackPoint, photoUrl);
|
||||
}
|
||||
|
||||
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||
|
||||
@@ -22,9 +22,9 @@ public class TestSensorDataUtil {
|
||||
tp.setAccuracy(1f);
|
||||
tp.setAltitude(1f);
|
||||
tp.setTime(time);
|
||||
tp.setSpeed(5f + (i / 10f));
|
||||
tp.setElevationGain(3f);
|
||||
tp.setElevationLoss(3f);
|
||||
tp.setSpeed(Speed.of(5f + (i / 10f)));
|
||||
tp.setAltitudeGain(3f);
|
||||
tp.setAltitudeLoss(3f);
|
||||
trackPointList.add(tp);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -25,4 +28,35 @@ public class TrackPointTest {
|
||||
|
||||
assertFalse(tp.isRecent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void distanceToPrevious() {
|
||||
Location l1 = new Location("test");
|
||||
l1.setLatitude(0);
|
||||
l1.setLongitude(0.0001);
|
||||
TrackPoint tp1 = new TrackPoint(l1);
|
||||
|
||||
Location l2 = new Location("test");
|
||||
l2.setLatitude(0);
|
||||
l2.setLongitude(0.0002);
|
||||
TrackPoint tp2 = new TrackPoint(l2);
|
||||
|
||||
// without sensor distance
|
||||
assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||
|
||||
// tp1 has sensor distance
|
||||
tp1.setSensorDistance(Distance.of(5));
|
||||
tp1.setSensorDistance(null);
|
||||
assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||
|
||||
// tp2 has sensor distance
|
||||
tp1.setSensorDistance(null);
|
||||
tp2.setSensorDistance(Distance.of(5));
|
||||
assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||
|
||||
// tp1 and tp2 have sensor distance
|
||||
tp1.setSensorDistance(Distance.of(10));
|
||||
tp2.setSensorDistance(Distance.of(5));
|
||||
assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||
}
|
||||
}
|
||||
+14
-12
@@ -42,8 +42,10 @@ import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.TestSensorDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -105,7 +107,7 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
testIterator(new Track.Id(1), 20000);
|
||||
testIterator(new Track.Id(1), 20000 / 2);
|
||||
}
|
||||
|
||||
private void testIterator(Track.Id trackId, int numPoints) {
|
||||
@@ -428,11 +430,11 @@ public class CustomContentProviderUtilsTest {
|
||||
statistics.setStopTime(Instant.ofEpochMilli(2500));
|
||||
statistics.setTotalTime(Duration.ofMillis(1500));
|
||||
statistics.setMovingTime(Duration.ofMillis(700));
|
||||
statistics.setTotalDistance(750.0);
|
||||
statistics.setTotalElevationGain(50.0f);
|
||||
statistics.setMaxSpeed(60.0);
|
||||
statistics.setMaxElevation(1250.0);
|
||||
statistics.setMinElevation(1200.0);
|
||||
statistics.setTotalDistance(Distance.of(750.0));
|
||||
statistics.setTotalAltitudeGain(50.0f);
|
||||
statistics.setMaxSpeed(Speed.of(60.0));
|
||||
statistics.setMaxAltitude(1250.0);
|
||||
statistics.setMinAltitude(1200.0);
|
||||
|
||||
track.first.setTrackStatistics(statistics);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
@@ -564,10 +566,10 @@ public class CustomContentProviderUtilsTest {
|
||||
// statistics.setTotalTime(1500L);
|
||||
// statistics.setMovingTime(700L);
|
||||
// statistics.setTotalDistance(750.0);
|
||||
// statistics.setTotalElevationGain(50.0);
|
||||
// statistics.setTotalAltitudeGain(50.0);
|
||||
// statistics.setMaxSpeed(60.0);
|
||||
// statistics.setMaxElevation(1250.0);
|
||||
// statistics.setMinElevation(1200.0);
|
||||
// statistics.setMaxAltitude(1250.0);
|
||||
// statistics.setMinAltitude(1200.0);
|
||||
//
|
||||
// track.setTrackStatistics(statistics);
|
||||
// TestDataUtil.insertTrackWithLocations(contentProviderUtils, track);
|
||||
@@ -606,7 +608,7 @@ public class CustomContentProviderUtilsTest {
|
||||
contentProviderUtils.insertMarker(marker3);
|
||||
contentProviderUtils.insertMarker(marker4);
|
||||
|
||||
assertEquals(4, contentProviderUtils.getNextMarkerNumber(trackId));
|
||||
assertEquals(Integer.valueOf(4), contentProviderUtils.getNextMarkerNumber(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -807,7 +809,7 @@ public class CustomContentProviderUtilsTest {
|
||||
assertEquals(longitude, trackPoint.getLongitude(), 0.01);
|
||||
assertEquals(latitude, trackPoint.getLatitude(), 0.01);
|
||||
assertEquals(time, trackPoint.getTime().toEpochMilli());
|
||||
assertEquals(speed, trackPoint.getSpeed(), 0.01);
|
||||
assertEquals(speed, trackPoint.getSpeed().toMPS(), 0.01);
|
||||
assertFalse(trackPoint.hasHeartRate());
|
||||
}
|
||||
|
||||
@@ -1250,7 +1252,7 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_veryLongActivity12h() {
|
||||
testGetSensorStats_randomData(43200, false);
|
||||
testGetSensorStats_randomData(43200 / 6, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+7
-6
@@ -5,6 +5,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.util.UintUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -100,11 +101,11 @@ public class SensorDataCyclingTest {
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2150);
|
||||
current.compute(previous, Distance.ofMM(2150));
|
||||
|
||||
// then
|
||||
assertEquals(1.20, current.getValue().distance_m, 2150);
|
||||
assertEquals(1.20, current.getValue().speed_mps, 0.01);
|
||||
assertEquals(2.15, current.getValue().distance.toM(), 0.01);
|
||||
assertEquals(1.20, current.getValue().speed.toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,11 +115,11 @@ public class SensorDataCyclingTest {
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2000);
|
||||
current.compute(previous, Distance.ofMM(2000));
|
||||
|
||||
// then
|
||||
assertEquals(1.20, current.getValue().distance_m, 2000);
|
||||
assertEquals(2, current.getValue().speed_mps, 0.01);
|
||||
assertEquals(2, current.getValue().distance.toM(), 0.01);
|
||||
assertEquals(2, current.getValue().speed.toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -28,21 +27,14 @@ import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests {@link ChartFragment}.
|
||||
*
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
//TODO Add tests that check ChartFragment
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ChartFragmentTest {
|
||||
|
||||
@@ -68,261 +60,8 @@ public class ChartFragmentTest {
|
||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_sensorIncorrect() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
|
||||
// then
|
||||
assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
||||
assertEquals(Float.NaN, point.getCadence(), 0.01);
|
||||
assertEquals(Float.NaN, point.getPower(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the correct values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_sensorCorrect() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setHeartRate_bpm(100f);
|
||||
trackPoint.setCyclingCadence_rpm(101f);
|
||||
trackPoint.setPower(102f);
|
||||
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
|
||||
// then
|
||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||
assertEquals(101.0, point.getCadence(), 0.01);
|
||||
assertEquals(102.0, point.getPower(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of metric Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_distanceMetric() {
|
||||
chartFragment.setChartByDistance(true);
|
||||
// Resets last location and writes first location.
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The second is a same location, just different time.
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The third location is a new location, and use metric.
|
||||
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint3.setLatitude(23);
|
||||
point = chartFragment.createPendingPoint(trackPoint3);
|
||||
|
||||
// Computes the distance between Latitude 22 and 23.
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(),
|
||||
trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM;
|
||||
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The fourth location is a new location, and use metric.
|
||||
TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint4.setLatitude(24);
|
||||
point = chartFragment.createPendingPoint(trackPoint4);
|
||||
|
||||
// Computes the distance between Latitude 23 and 24.
|
||||
Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(),
|
||||
trackPoint4.getLatitude(), trackPoint4.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM;
|
||||
assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_distanceImperial() {
|
||||
// By distance.
|
||||
chartFragment.setChartByDistance(true);
|
||||
// Setups to use imperial.
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// The first is a same location, just different time.
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The second location is a new location, and use imperial.
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setLatitude(23);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 22 and 23.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The third location is a new location, and use imperial.
|
||||
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint3.setLatitude(24);
|
||||
point = chartFragment.createPendingPoint(trackPoint3);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 23 and 24.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the values of time in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_time() {
|
||||
// given
|
||||
chartFragment.setChartByDistance(false);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setTime(Instant.ofEpochMilli(TrackStubUtils.INITIAL_TIME)); //Keep old TrackPoint behavior of having time=0 for this test
|
||||
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
|
||||
// then
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
Duration timeSpan = Duration.ofMillis(222);
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setTime(Instant.ofEpochMilli(TrackStubUtils.INITIAL_TIME).plus(timeSpan));
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(timeSpan, Duration.ofMillis((long) point.getTimeOrDistance()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of elevation in {@link ChartFragment#createPendingPoint(TrackPoint)} by one and two points.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_elevation() {
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* At first, clear old points of elevation, so give true to the second parameter.
|
||||
* Then only one value INITIAL_ALTITUDE in buffer.
|
||||
*/
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getElevation(), 0.01);
|
||||
|
||||
/*
|
||||
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
|
||||
*/
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getElevation(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of speed in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
* In this test, firstly remove all points in memory, and then fill in two points one by one.
|
||||
* The speed values of these points are 129, 130.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_speed() {
|
||||
/*
|
||||
* At first, clear old points of speed, so give true to the second parameter.
|
||||
* It will not be filled in to the speed buffer.
|
||||
*/
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(128.5f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
/*
|
||||
* Tests the logic when both metricUnits and reportSpeed are true.
|
||||
* This location will be filled into speed buffer.
|
||||
*/
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint1.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(130f);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to compute speed when use Imperial.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_speedImperial() {
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(100.0f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint2.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(102f);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get pace value when reportSpeed is false.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_pace_nonZeroSpeed() {
|
||||
chartFragment.setReportSpeed(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(100.0f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint2.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(102f);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_pace_zeroSpeed() {
|
||||
chartFragment.setReportSpeed(false);
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setSpeed(0f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
assertEquals(0.0, point.getPace(), 0.01);
|
||||
public void nothing() {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
+143
-77
@@ -3,10 +3,13 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
@@ -14,14 +17,12 @@ import androidx.test.rule.ServiceTestRule;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
@@ -31,7 +32,10 @@ import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -39,9 +43,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -53,7 +55,7 @@ import static org.junit.Assert.assertTrue;
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
* <p>
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExportImportTest {
|
||||
|
||||
private static final String TAG = ExportImportTest.class.getSimpleName();
|
||||
@@ -71,7 +73,6 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
@@ -79,6 +80,9 @@ public class ExportImportTest {
|
||||
private static final String TRACK_CATEGORY = "the category";
|
||||
private static final String TRACK_DESCRIPTION = "the description";
|
||||
|
||||
private File tmpFile;
|
||||
private Uri tmpFileUri;
|
||||
|
||||
private Track track;
|
||||
private List<Marker> markers = new ArrayList<>();
|
||||
private List<TrackPoint> trackPoints = new ArrayList<>();
|
||||
@@ -87,22 +91,44 @@ public class ExportImportTest {
|
||||
private Track.Id importTrackId;
|
||||
|
||||
@Before
|
||||
public void setUp() throws TimeoutException {
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)));
|
||||
public void fileSetup() throws IOException {
|
||||
tmpFile = File.createTempFile("test", "test", context.getFilesDir());
|
||||
tmpFileUri = Uri.fromFile(tmpFile);
|
||||
}
|
||||
|
||||
@After
|
||||
public void FileTearDown() {
|
||||
tmpFile.deleteOnExit();
|
||||
tmpFileUri = null;
|
||||
}
|
||||
|
||||
public void setUp(boolean hasSensorDistance) throws TimeoutException {
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
|
||||
|
||||
trackId = service.startNewTrack();
|
||||
//TODO Workaround as those managers overwrite input data; We need to refactor TrackRecordingService to make it actually testable
|
||||
service.setAltitudeSumManager(null);
|
||||
service.setRemoteSensorManager(null);
|
||||
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
|
||||
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance), 0);
|
||||
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
service.resumeCurrentTrack();
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
//TODO Workaround as those managers overwrite input data; We need to refactor TrackRecordingService to make it actually testable
|
||||
service.setAltitudeSumManager(null);
|
||||
service.setRemoteSensorManager(null);
|
||||
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
service.endCurrentTrack();
|
||||
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -126,17 +152,11 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Not implemented")
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_only_track() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
public void kml_with_trackdetail() throws TimeoutException, IOException {
|
||||
setUp(false);
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_trackdetail() {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
@@ -144,13 +164,12 @@ public class ExportImportTest {
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(track, outputStream);
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
@@ -167,7 +186,7 @@ public class ExportImportTest {
|
||||
assertTrackpoints(trackPoints, false, false, false, false, false, false);
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(false, false, false);
|
||||
assertTrackStatistics(false, false, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -175,7 +194,9 @@ public class ExportImportTest {
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_trackdetail_and_sensordata() {
|
||||
public void kml_with_trackdetail_and_sensordata() throws TimeoutException, IOException {
|
||||
setUp(true);
|
||||
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
@@ -183,13 +204,12 @@ public class ExportImportTest {
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(track, outputStream);
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
@@ -211,23 +231,64 @@ public class ExportImportTest {
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
//TODO Does not test images
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException {
|
||||
setUp(true);
|
||||
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// 2. import
|
||||
KmzTrackImporter trackImporter = new KmzTrackImporter();
|
||||
importTrackId = trackImporter.importFile(context, tmpFileUri).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. trackpoints
|
||||
assertTrackpoints(trackPoints, true, true, true, true, true, true);
|
||||
|
||||
// 2. trackstatistics
|
||||
assertTrackStatistics(false, true, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test(expected = ImportAlreadyExistsException.class)
|
||||
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
|
||||
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException, IOException {
|
||||
setUp(false);
|
||||
|
||||
// given
|
||||
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true);
|
||||
editor.commit();
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(track, outputStream);
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
@@ -237,7 +298,9 @@ public class ExportImportTest {
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx() {
|
||||
public void gpx() throws TimeoutException, IOException {
|
||||
setUp(true);
|
||||
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
@@ -245,13 +308,12 @@ public class ExportImportTest {
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(track, outputStream);
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context, contentProviderUtils));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
@@ -283,21 +345,24 @@ public class ExportImportTest {
|
||||
|
||||
@LargeTest
|
||||
@Test(expected = ImportAlreadyExistsException.class)
|
||||
public void gpx_duplicate_trackUUID() {
|
||||
public void gpx_duplicate_trackUUID() throws TimeoutException, IOException {
|
||||
setUp(false);
|
||||
|
||||
// given
|
||||
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true);
|
||||
editor.commit();
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(track, outputStream);
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context, contentProviderUtils));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
@@ -325,7 +390,7 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss, boolean verifyDistance) {
|
||||
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyAltitudeGain, boolean verifyAltitudeLoss, boolean verifyDistance) {
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
||||
|
||||
@@ -348,11 +413,11 @@ public class ExportImportTest {
|
||||
}
|
||||
assertEquals(trackPoint.hasSpeed(), importedTrackPoint.hasSpeed());
|
||||
if (trackPoint.hasSpeed()) {
|
||||
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
|
||||
assertEquals(trackPoint.getSpeed().toMPS(), importedTrackPoint.getSpeed().toMPS(), 0.001);
|
||||
}
|
||||
assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
|
||||
if (trackPoint.hasAltitude()) {
|
||||
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
|
||||
assertEquals(trackPoint.getAltitude().toM(), importedTrackPoint.getAltitude().toM(), 0.001);
|
||||
}
|
||||
|
||||
if (type.equals(TrackPoint.Type.SEGMENT_START_MANUAL) || type.equals(TrackPoint.Type.SEGMENT_END_MANUAL)) {
|
||||
@@ -369,19 +434,19 @@ public class ExportImportTest {
|
||||
if (verifyPower) {
|
||||
assertEquals("" + trackPoint, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
}
|
||||
if (verifyElevationGain) {
|
||||
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
|
||||
if (verifyAltitudeGain) {
|
||||
assertEquals(trackPoint.getAltitudeGain(), importedTrackPoint.getAltitudeGain(), 0.01);
|
||||
}
|
||||
if (verifyElevationLoss) {
|
||||
assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01);
|
||||
if (verifyAltitudeLoss) {
|
||||
assertEquals(trackPoint.getAltitudeLoss(), importedTrackPoint.getAltitudeLoss(), 0.01);
|
||||
}
|
||||
if (verifyDistance) {
|
||||
assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance(), 0.01);
|
||||
assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss, boolean verifyDistance) {
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyAltitudeGainAndLoss, boolean verifyDistance) {
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
|
||||
assertNotNull(importedTrack.getTrackStatistics());
|
||||
@@ -400,35 +465,36 @@ public class ExportImportTest {
|
||||
|
||||
// Distance
|
||||
if (verifyDistance) {
|
||||
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
|
||||
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance());
|
||||
}
|
||||
|
||||
// Speed
|
||||
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed(), 0.01);
|
||||
assertEquals(trackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed(), 0.01);
|
||||
assertEquals(trackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed(), 0.01);
|
||||
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed());
|
||||
assertEquals(trackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(trackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed());
|
||||
}
|
||||
|
||||
// Elevation
|
||||
assertEquals(trackStatistics.getMinElevation(), importedTrackStatistics.getMinElevation(), 0.01);
|
||||
assertEquals(trackStatistics.getMaxElevation(), importedTrackStatistics.getMaxElevation(), 0.01);
|
||||
if (verifyElevationGainAndLoss) {
|
||||
assertEquals(trackStatistics.getTotalElevationGain(), importedTrackStatistics.getTotalElevationGain(), 0.01);
|
||||
assertEquals(trackStatistics.getTotalElevationLoss(), importedTrackStatistics.getTotalElevationLoss(), 0.01);
|
||||
// Altitude
|
||||
assertEquals(trackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01);
|
||||
assertEquals(trackStatistics.getMaxAltitude(), importedTrackStatistics.getMaxAltitude(), 0.01);
|
||||
if (verifyAltitudeGainAndLoss) {
|
||||
assertEquals(trackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), 0.01);
|
||||
assertEquals(trackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
|
||||
} else {
|
||||
assertFalse(importedTrackStatistics.hasTotalElevationGain());
|
||||
assertFalse(importedTrackStatistics.hasTotalElevationLoss());
|
||||
assertFalse(importedTrackStatistics.hasTotalAltitudeGain());
|
||||
assertFalse(importedTrackStatistics.hasTotalAltitudeLoss());
|
||||
}
|
||||
}
|
||||
|
||||
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float elevationGain, float heartRate, float cyclingCadence, float power, float distance) {
|
||||
TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time));
|
||||
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
|
||||
TrackPoint tp = new TrackPoint(latitude, longitude, Altitude.WGS84.of(altitude), Instant.ofEpochMilli(time));
|
||||
tp.setAccuracy(accuracy);
|
||||
tp.setSpeed(speed);
|
||||
tp.setSpeed(Speed.of(speed));
|
||||
tp.setHeartRate_bpm(heartRate);
|
||||
tp.setCyclingCadence_rpm(cyclingCadence);
|
||||
tp.setPower(power);
|
||||
tp.setElevationGain(elevationGain);
|
||||
tp.setAltitudeGain(altitudeGain);
|
||||
tp.setAltitudeLoss(altitudeGain); //TODO
|
||||
tp.setSensorDistance(distance);
|
||||
return tp;
|
||||
}
|
||||
|
||||
+8
-7
@@ -11,6 +11,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
@@ -48,9 +49,9 @@ public class LegacyImportTest {
|
||||
*/
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_statistics_marker() {
|
||||
public void kml_with_statistics_marker() throws IOException {
|
||||
// given
|
||||
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_statistics_marker);
|
||||
|
||||
// when
|
||||
@@ -90,9 +91,9 @@ public class LegacyImportTest {
|
||||
*/
|
||||
@LargeTest
|
||||
@Test(expected = ImportParserException.class)
|
||||
public void kml_without_locations() {
|
||||
public void kml_without_locations() throws IOException {
|
||||
// given
|
||||
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
|
||||
|
||||
// when
|
||||
@@ -104,9 +105,9 @@ public class LegacyImportTest {
|
||||
*/
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx_with_pause_resume() {
|
||||
public void gpx_with_pause_resume() throws IOException {
|
||||
// given
|
||||
GpxFileTrackImporter trackImporter = new GpxFileTrackImporter(context);
|
||||
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context));
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_gpx_pause_resume);
|
||||
|
||||
// when
|
||||
@@ -151,7 +152,7 @@ public class LegacyImportTest {
|
||||
if (altitude == null) {
|
||||
assertFalse(trackPoint.hasAltitude());
|
||||
} else {
|
||||
assertEquals(altitude, (Double) trackPoint.getAltitude());
|
||||
assertEquals(altitude, (Double) trackPoint.getAltitude().toM());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -11,11 +11,12 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -41,8 +42,8 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||
when(trackPointMock.hasAccuracy()).thenReturn(true);
|
||||
when(trackPointMock.getAccuracy()).thenReturn(999f);
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(0d);
|
||||
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
|
||||
when(trackPointMock.getSpeed()).thenReturn(Speed.of(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
|
||||
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
|
||||
+50
-58
@@ -31,13 +31,14 @@ import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -83,6 +84,10 @@ public class TrackRecordingServiceTest {
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
@@ -113,7 +118,8 @@ public class TrackRecordingServiceTest {
|
||||
@After
|
||||
public void tearDown() throws TimeoutException {
|
||||
// Reset service (if some previous test failed)
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
@@ -146,36 +152,38 @@ public class TrackRecordingServiceTest {
|
||||
// when
|
||||
Intent startIntent = createStartIntent(context);
|
||||
mServiceRule.startService(startIntent);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
|
||||
.getService();
|
||||
|
||||
// then
|
||||
// Test if we start in no-recording mode by default.
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_oldTracks() throws TimeoutException {
|
||||
// given
|
||||
createDummyTrack(trackId, false);
|
||||
createDummyTrack(trackId);
|
||||
|
||||
// when
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_serviceRestart_whileRecording() throws TimeoutException {
|
||||
// given
|
||||
createDummyTrack(trackId, true);
|
||||
createDummyTrack(trackId);
|
||||
|
||||
//when
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
service.resumeTrack(trackId);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
@@ -185,7 +193,8 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testRecording_start() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
// when
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
@@ -201,7 +210,8 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testRecording_stop() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
@@ -219,7 +229,8 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testRecording_pauseAndResume() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
@@ -233,7 +244,6 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(3, trackPoints.size());
|
||||
@@ -246,7 +256,8 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
service.endCurrentTrack();
|
||||
@@ -259,7 +270,6 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(4, trackPoints.size());
|
||||
@@ -273,7 +283,8 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testRecording_stopPausedTrack() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
service.pauseCurrentTrack();
|
||||
@@ -285,7 +296,6 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(2, trackPoints.size());
|
||||
@@ -293,48 +303,29 @@ public class TrackRecordingServiceTest {
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
}
|
||||
|
||||
@Ignore("Sometimes fails on CI.")
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_orphanedRecordingTrack() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
|
||||
// when
|
||||
// Just set recording track to a bogus value.
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, 123L);
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testStartNewTrack_alreadyRecording() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
Track.Id trackId = service.getRecordingTrackId();
|
||||
|
||||
// when
|
||||
Track.Id newTrackId = service.startNewTrack();
|
||||
|
||||
// then
|
||||
assertNotNull(trackId);
|
||||
assertNull(newTrackId);
|
||||
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testEndCurrentTrack_noRecording() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// when
|
||||
@@ -342,15 +333,15 @@ public class TrackRecordingServiceTest {
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(PreferencesUtils.isRecording(sharedPreferences, context));
|
||||
assertNull(service.getRecordingTrackId());
|
||||
assertFalse(service.isRecording());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// when
|
||||
@@ -364,57 +355,58 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
newTrackPoint(service);
|
||||
Track.Id trackId = service.getRecordingTrackId();
|
||||
|
||||
// when
|
||||
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||
|
||||
// then
|
||||
assertNotEquals(-1L, markerId);
|
||||
assertNotEquals(new Marker.Id(-1L), markerId);
|
||||
Marker wpt = contentProviderUtils.getMarker(markerId);
|
||||
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
|
||||
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
||||
assertEquals(trackId, wpt.getTrackId());
|
||||
assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
assertEquals(0.0, wpt.getLength().toM(), 0.01);
|
||||
assertNotNull(wpt.getLocation());
|
||||
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
private void addTrack(Track track, boolean isRecording) {
|
||||
private void addTrack(Track track) {
|
||||
assertNotNull(track.getId());
|
||||
contentProviderUtils.insertTrack(track);
|
||||
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
|
||||
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, !isRecording);
|
||||
}
|
||||
|
||||
// NOTE: Do not use to create a track that is currently recording.
|
||||
private void createDummyTrack(Track.Id id, boolean isRecording) {
|
||||
private void createDummyTrack(Track.Id id) {
|
||||
Track dummyTrack = new Track();
|
||||
dummyTrack.setId(id);
|
||||
dummyTrack.setName("Dummy Track");
|
||||
TrackStatistics trackStatistics = new TrackStatistics();
|
||||
Instant now = Instant.now();
|
||||
trackStatistics.setStartTime(now.minusSeconds(5L));
|
||||
trackStatistics.setStopTime(now.minusSeconds(1L));
|
||||
dummyTrack.setTrackStatistics(trackStatistics);
|
||||
addTrack(dummyTrack, isRecording);
|
||||
addTrack(dummyTrack);
|
||||
}
|
||||
|
||||
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||
private static void newTrackPoint(TrackRecordingService trackRecordingService) throws InterruptedException {
|
||||
newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||
static void newTrackPoint(TrackRecordingService 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 newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
|
||||
private static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
|
||||
Location location = new Location("");
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
|
||||
+10
-2
@@ -13,6 +13,7 @@ import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
@@ -44,6 +45,7 @@ import static org.junit.Assert.assertNotNull;
|
||||
* So, if it is executed on a real device, data might be lost.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
//TODO Implement as mock test; no need to store data in database
|
||||
public class TrackRecordingServiceTestLocation {
|
||||
|
||||
@Rule
|
||||
@@ -55,7 +57,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private TrackRecordingServiceInterface service;
|
||||
private TrackRecordingService service;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
@@ -63,6 +65,11 @@ public class TrackRecordingServiceTestLocation {
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws TimeoutException {
|
||||
// Set up the mock content resolver
|
||||
@@ -77,7 +84,8 @@ public class TrackRecordingServiceTestLocation {
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
|
||||
.getService();
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
+38
-19
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
@@ -35,7 +36,6 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,8 @@ public class TrackRecordingServiceTestLooper {
|
||||
@After
|
||||
public void tearDown() throws TimeoutException {
|
||||
// Reset service (if some previous test failed)
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
|
||||
.getService();
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
@@ -99,28 +100,36 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, 500);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.recording_gps_accuracy_key), 500);
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.voice_frequency_key), Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, -1);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.voice_frequency_key), -1);
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, 1);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.voice_frequency_key), 1);
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -128,29 +137,36 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.max_recording_distance_key), Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, 50);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.max_recording_distance_key), 50);
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
||||
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.recording_distance_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, 2);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.recording_distance_interval_key), 2);
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -171,22 +187,27 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.min_recording_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingInterval() throws TimeoutException {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, 2);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.min_recording_interval_key), 2);
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
||||
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putInt(context.getString(R.string.recording_gps_accuracy_key), Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)));
|
||||
editor.commit();
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -199,7 +220,8 @@ public class TrackRecordingServiceTestLooper {
|
||||
}
|
||||
|
||||
private void fullRecordingSession() throws TimeoutException {
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
|
||||
.getService();
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// Start a track.
|
||||
@@ -209,8 +231,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
assertNotNull(track);
|
||||
assertEquals(trackId, track.getId());
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
long startTime = System.currentTimeMillis();
|
||||
@@ -234,7 +254,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
// Stop the track. Validate if it has correct data.
|
||||
service.endCurrentTrack();
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
assertNotNull(track);
|
||||
assertEquals(trackId, track.getId());
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package de.dennisguse.opentracks.services.sensors;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* NOTE: Test data is completely artificial.
|
||||
*/
|
||||
public class AltitudeSumManagerTest {
|
||||
|
||||
private final AltitudeSumManager subject = new AltitudeSumManager();
|
||||
|
||||
private static void addSensorValue(AltitudeSumManager altitudeSumManager, float[] values) {
|
||||
for (float f : values) {
|
||||
altitudeSumManager.onSensorValueChanged(f);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAltitudeGainLoss_downhill() {
|
||||
// given
|
||||
subject.setConnected(true);
|
||||
|
||||
// then
|
||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1015.14f, 1015.15f});
|
||||
|
||||
// then
|
||||
Assert.assertEquals(0f, subject.getAltitudeGain_m(), 0.01);
|
||||
Assert.assertEquals(48.0, subject.getAltitudeLoss_m(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sensorUnavailable() {
|
||||
// given
|
||||
subject.setConnected(false);
|
||||
|
||||
// then
|
||||
subject.onSensorValueChanged(999f);
|
||||
|
||||
// then
|
||||
Assert.assertNull(subject.getAltitudeGain_m());
|
||||
Assert.assertNull(subject.getAltitudeLoss_m());
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package de.dennisguse.opentracks.services.sensors;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* NOTE: Test data is completely artificial.
|
||||
*/
|
||||
public class ElevationSumManagerTest {
|
||||
|
||||
private final ElevationSumManager elevationSumManager = new ElevationSumManager();
|
||||
|
||||
private static void addSensorValue(ElevationSumManager elevationSumManager, float[] values) {
|
||||
for (float f : values) {
|
||||
elevationSumManager.onSensorValueChanged(f);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
elevationSumManager.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getElevationGainLoss_downhill() {
|
||||
// given
|
||||
elevationSumManager.setConnected(true);
|
||||
|
||||
// then
|
||||
addSensorValue(elevationSumManager, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1015.14f, 1015.15f});
|
||||
|
||||
// then
|
||||
Assert.assertEquals(0f, elevationSumManager.getElevationGain_m(), 0.01);
|
||||
Assert.assertEquals(48.0, elevationSumManager.getElevationLoss_m(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sensorUnavailable() {
|
||||
// given
|
||||
elevationSumManager.setConnected(false);
|
||||
|
||||
// then
|
||||
elevationSumManager.onSensorValueChanged(999f);
|
||||
|
||||
// then
|
||||
Assert.assertNull(elevationSumManager.getElevationGain_m());
|
||||
Assert.assertNull(elevationSumManager.getElevationLoss_m());
|
||||
}
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.services.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for {@link AnnouncementPeriodicTaskFactory}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AnnouncementPeriodicTaskFactoryTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
|
||||
PeriodicTask task = factory.create(context);
|
||||
assertTrue(task instanceof AnnouncementPeriodicTask);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package de.dennisguse.opentracks.services.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PeriodicTaskExecutorTest {
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void calculateNextTaskDistance() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
|
||||
PeriodicTaskExecutor periodicTaskExecutor = new PeriodicTaskExecutor(service, new AnnouncementPeriodicTask.Factory());
|
||||
periodicTaskExecutor.setMetricUnits(true);
|
||||
periodicTaskExecutor.setTaskFrequency(-5);
|
||||
|
||||
// when
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setTotalDistance(Distance.of(13000));
|
||||
assertEquals(Distance.of(15000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
|
||||
|
||||
statistics.setTotalDistance(Distance.of(15100));
|
||||
assertEquals(Distance.of(20000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,14 +15,18 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@@ -32,7 +36,7 @@ import static org.junit.Assert.assertNull;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackStatisticsTest {
|
||||
|
||||
private TrackStatistics statistics;
|
||||
@@ -56,13 +60,13 @@ public class TrackStatisticsTest {
|
||||
assertEquals(Duration.ofSeconds(0), statistics.getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(0), statistics.getTotalTime());
|
||||
|
||||
assertNull(statistics.getTotalElevationGain());
|
||||
assertNull(statistics.getTotalElevationLoss());
|
||||
assertEquals(Double.NEGATIVE_INFINITY, statistics.getMaxElevation(), 0.0);
|
||||
assertEquals(Double.POSITIVE_INFINITY, statistics.getMinElevation(), 0.0);
|
||||
assertEquals(0.0, statistics.getMaxSpeed(), 0.0);
|
||||
assertEquals(0.0, statistics.getAverageSpeed(), 0.0);
|
||||
assertEquals(0.0, statistics.getAverageMovingSpeed(), 0.0);
|
||||
assertNull(statistics.getTotalAltitudeGain());
|
||||
assertNull(statistics.getTotalAltitudeLoss());
|
||||
assertEquals(Double.NEGATIVE_INFINITY, statistics.getMaxAltitude(), 0.0);
|
||||
assertEquals(Double.POSITIVE_INFINITY, statistics.getMinAltitude(), 0.0);
|
||||
assertEquals(0.0, statistics.getMaxSpeed().toMPS(), 0.0);
|
||||
assertEquals(0.0, statistics.getAverageSpeed().toMPS(), 0.0);
|
||||
assertEquals(0.0, statistics.getAverageMovingSpeed().toMPS(), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,16 +81,16 @@ public class TrackStatisticsTest {
|
||||
statistics2.setTotalTime(Duration.ofMillis(1000)); // Result: 1500+1000
|
||||
statistics.setMovingTime(Duration.ofMillis(700));
|
||||
statistics2.setMovingTime(Duration.ofMillis(600)); // Result: 700+600
|
||||
statistics.setTotalDistance(750.0);
|
||||
statistics2.setTotalDistance(350.0); // Result: 750+350
|
||||
statistics.setTotalElevationGain(50.0f);
|
||||
statistics2.setTotalElevationGain(850.0f); // Result: 850+50
|
||||
statistics.setMaxSpeed(60.0); // Resulting max speed
|
||||
statistics2.setMaxSpeed(30.0);
|
||||
statistics.setMaxElevation(1250.0);
|
||||
statistics.setMinElevation(1200.0); // Resulting min elevation
|
||||
statistics2.setMaxElevation(3575.0); // Resulting max elevation
|
||||
statistics2.setMinElevation(2800.0);
|
||||
statistics.setTotalDistance(Distance.of(750.0));
|
||||
statistics2.setTotalDistance(Distance.of(350.0)); // Result: 750+350
|
||||
statistics.setTotalAltitudeGain(50.0f);
|
||||
statistics2.setTotalAltitudeGain(850.0f); // Result: 850+50
|
||||
statistics.setMaxSpeed(Speed.of(60.0)); // Resulting max speed
|
||||
statistics2.setMaxSpeed(Speed.of(30.0));
|
||||
statistics.setMaxAltitude(1250.0);
|
||||
statistics.setMinAltitude(1200.0); // Resulting min altitude
|
||||
statistics2.setMaxAltitude(3575.0); // Resulting max altitude
|
||||
statistics2.setMinAltitude(2800.0);
|
||||
|
||||
// when
|
||||
statistics.merge(statistics2);
|
||||
@@ -96,24 +100,24 @@ public class TrackStatisticsTest {
|
||||
assertEquals(Instant.ofEpochMilli(4000), statistics.getStopTime());
|
||||
assertEquals(Duration.ofMillis(2500), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofMillis(1300), statistics.getMovingTime());
|
||||
assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
|
||||
assertEquals(900.0, statistics.getTotalElevationGain(), 0.001);
|
||||
assertEquals(statistics.getTotalDistance() / statistics.getMovingTime().getSeconds(), statistics.getMaxSpeed(), 0.001);
|
||||
assertEquals(1200.0, statistics.getMinElevation(), 0.001);
|
||||
assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
|
||||
assertEquals(1100.0, statistics.getTotalDistance().toM(), 0.001);
|
||||
assertEquals(900.0, statistics.getTotalAltitudeGain(), 0.001);
|
||||
assertEquals(Speed.of(statistics.getTotalDistance(), statistics.getMovingTime()).toMPS(), statistics.getMaxSpeed().toMPS(), 0.001);
|
||||
assertEquals(1200.0, statistics.getMinAltitude(), 0.001);
|
||||
assertEquals(3575.0, statistics.getMaxAltitude(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAverageSpeed() {
|
||||
statistics.setTotalDistance(1000.0);
|
||||
statistics.setTotalDistance(Distance.of(1000.0));
|
||||
statistics.setTotalTime(Duration.ofMillis(50000));
|
||||
assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
|
||||
assertEquals(20.0, statistics.getAverageSpeed().toMPS(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAverageMovingSpeed() {
|
||||
statistics.setTotalDistance(1000.0);
|
||||
statistics.setTotalDistance(Distance.of(1000.0));
|
||||
statistics.setMovingTime(Duration.ofMillis(20000));
|
||||
assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
|
||||
assertEquals(50.0, statistics.getAverageMovingSpeed().toMPS(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
+66
-35
@@ -9,6 +9,9 @@ import org.junit.runner.RunWith;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -18,7 +21,7 @@ import static org.junit.Assert.assertEquals;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackStatisticsUpdaterTest {
|
||||
|
||||
private static final int GPS_DISTANCE = 50;
|
||||
private static final Distance GPS_DISTANCE = Distance.of(50);
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_TestingTrack() {
|
||||
@@ -31,18 +34,18 @@ public class TrackStatisticsUpdaterTest {
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = subject.getTrackStatistics();
|
||||
assertEquals(85.35, statistics.getTotalDistance(), 0.01);
|
||||
assertEquals(Duration.ofMillis(13999), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(6), statistics.getMovingTime());
|
||||
assertEquals(99.58, statistics.getTotalDistance().toM(), 0.01);
|
||||
assertEquals(Duration.ofSeconds(14), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(7), statistics.getMovingTime());
|
||||
|
||||
assertEquals(2.5, statistics.getMinElevation(), 0.01);
|
||||
assertEquals(27.5, statistics.getMaxElevation(), 0.01);
|
||||
assertEquals(27, statistics.getTotalElevationGain(), 0.01);
|
||||
assertEquals(27.0, statistics.getTotalElevationLoss(), 0.01);
|
||||
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
|
||||
assertEquals(27.5, statistics.getMaxAltitude(), 0.01);
|
||||
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
|
||||
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
|
||||
|
||||
assertEquals(14.226, statistics.getMaxSpeed(), 0.01);
|
||||
assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01);
|
||||
assertEquals(6.566, statistics.getAverageSpeed(), 0.01);
|
||||
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
|
||||
assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01);
|
||||
assertEquals(7.11, statistics.getAverageSpeed().toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,8 +54,8 @@ public class TrackStatisticsUpdaterTest {
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
@@ -60,7 +63,7 @@ public class TrackStatisticsUpdaterTest {
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -69,9 +72,9 @@ public class TrackStatisticsUpdaterTest {
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
tp3.setSpeed(5f);
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
tp3.setSpeed(Speed.of(5f));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
@@ -79,7 +82,7 @@ public class TrackStatisticsUpdaterTest {
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,15 +91,15 @@ public class TrackStatisticsUpdaterTest {
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
tp2.setSpeed(5f);
|
||||
TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
tp2.setSpeed(5f);
|
||||
TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000));
|
||||
tp2.setSpeed(5f);
|
||||
tp4.setSensorDistance(5f);
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp3 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp4 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
tp4.setSensorDistance(Distance.of(5f));
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
tp5.setSensorDistance(10f);
|
||||
tp5.setSensorDistance(Distance.of(10f));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
@@ -104,14 +107,14 @@ public class TrackStatisticsUpdaterTest {
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(110.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
assertEquals(110.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(125.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
assertEquals(125.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,12 +123,12 @@ public class TrackStatisticsUpdaterTest {
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
TrackPoint tp4 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(4000));
|
||||
tp4.setSensorDistance(5f);
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
TrackPoint tp4 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||
tp4.setSensorDistance(Distance.of(5f));
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
tp5.setSensorDistance(10f);
|
||||
tp5.setSensorDistance(Distance.of(10f));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
@@ -133,14 +136,42 @@ public class TrackStatisticsUpdaterTest {
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(15, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
assertEquals(15, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_moving_and_sensor_disconnecting() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
tp3.setSensorDistance(Distance.of(5f));
|
||||
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(5, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(59.18, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
|
||||
@@ -11,6 +11,8 @@ import org.junit.runner.RunWith;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -25,13 +27,13 @@ public class AnnouncementUtilsTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric() {
|
||||
public void getAnnouncement_metric_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
|
||||
@@ -41,16 +43,16 @@ public class AnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_metric() {
|
||||
public void getAnnouncement_withInterval_metric_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000);
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
// when
|
||||
@@ -59,4 +61,112 @@ public class AnnouncementUtilsTest {
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour Lap speed of 51.2 kilometers per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_metric_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer Lap time of 1 minute 10 seconds per kilometer", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_imperial_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_imperial_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour Lap speed of 31.8 miles per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_imperial_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_imperial_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class EGM2008UtilsTest {
|
||||
|
||||
private static final double MAX_BILINEAR_ERROR = 0.478;
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void fileVerification() throws IOException {
|
||||
// given
|
||||
int expectedLength = 18671444;
|
||||
int expectedHeaderLength = 404;
|
||||
|
||||
String expectedHeader = "P5\n" +
|
||||
"# Geoid file in PGM format for the GeographicLib::Geoid class\n" +
|
||||
"# Description WGS84 EGM2008, 5-minute grid\n" +
|
||||
"# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008\n" +
|
||||
"# DateTime 2009-08-29 18:45:00\n" +
|
||||
"# MaxBilinearError 0.478\n" +
|
||||
"# RMSBilinearError 0.012\n" +
|
||||
"# MaxCubicError 0.294\n" +
|
||||
"# RMSCubicError 0.005\n" +
|
||||
"# Offset -108\n" +
|
||||
"# Scale 0.003\n" +
|
||||
"# Origin 90N 0E\n" +
|
||||
"# AREA_OR_POINT Point\n" +
|
||||
"# Vertical_Datum WGS84\n" +
|
||||
"4320 2161\n" +
|
||||
"65535" +
|
||||
"\n";
|
||||
|
||||
// when
|
||||
try (InputStream inputStream = context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA)) {
|
||||
assertEquals(expectedLength, inputStream.available());
|
||||
|
||||
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII);
|
||||
char[] data = new char[expectedHeaderLength];
|
||||
int length = reader.read(data);
|
||||
|
||||
// then
|
||||
assertEquals(expectedHeaderLength, length);
|
||||
assertEquals(expectedHeader, new String(data));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Northpole() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(90);
|
||||
trackPoint.setLongitude(0.1);
|
||||
trackPoint.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-14.8980, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Southpole() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(-90);
|
||||
trackPoint.setLongitude(0);
|
||||
trackPoint.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Southpole2() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(-90);
|
||||
trackPoint.setLongitude(180);
|
||||
trackPoint.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Southpole3() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(-90);
|
||||
trackPoint.setLongitude(-180);
|
||||
trackPoint.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_0() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(0);
|
||||
trackPoint.setLongitude(0);
|
||||
trackPoint.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-17.2260, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Berlin() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(52.530644);
|
||||
trackPoint.setLongitude(13.383068);
|
||||
trackPoint.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-39.4865, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Berlin_Caching() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint1.setLatitude(52.530644);
|
||||
trackPoint1.setLongitude(13.383068);
|
||||
trackPoint1.setAltitude(0);
|
||||
|
||||
TrackPoint trackPoint2 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint2.setLatitude(52.530000);
|
||||
trackPoint2.setLongitude(13.380000);
|
||||
trackPoint2.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint1.getLocation());
|
||||
|
||||
// then
|
||||
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()), altitude_egm2008.correctAltitude(trackPoint2.getLocation()), 0.0001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_MaxUndulation() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(-8.417);
|
||||
trackPoint.setLongitude(147.367);
|
||||
trackPoint.setAltitude(0);
|
||||
|
||||
// when
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIndices() {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(90);
|
||||
trackPoint.setLongitude(0);
|
||||
assertEquals(new EGM2008Utils.Indices(0, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||
|
||||
trackPoint.setLatitude(0);
|
||||
trackPoint.setLongitude(0);
|
||||
assertEquals(new EGM2008Utils.Indices(1080, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||
|
||||
trackPoint.setLatitude(-90);
|
||||
trackPoint.setLongitude(180);
|
||||
assertEquals(new EGM2008Utils.Indices(2160, 4320 / 2), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||
|
||||
trackPoint.setLatitude(-90);
|
||||
trackPoint.setLongitude(-180);
|
||||
assertEquals(new EGM2008Utils.Indices(2160, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUndulationRaw_ok() throws IOException {
|
||||
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA))) {
|
||||
assertEquals(40966, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(0, 0)));
|
||||
assertEquals(41742, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(1081, 0)));
|
||||
assertEquals(25950, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(2160, 4319)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = EOFException.class)
|
||||
public void getUndulationRaw_error() throws IOException {
|
||||
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA))) {
|
||||
assertEquals(0, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(2161, 4320)), 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,14 @@ package de.dennisguse.opentracks.util;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class ExportUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -9,7 +9,7 @@ public class PressureSensorUtilsTest {
|
||||
|
||||
// real data at 500Hz stationary
|
||||
@Test
|
||||
public void elevationChanges_none() {
|
||||
public void altitudeChanges_none() {
|
||||
// given
|
||||
float[] sensorValues_hPa = new float[]{1015.6876f, 1015.699f, 1015.70905f, 1015.71075f, 1015.7224f, 1015.72f, 1015.7373f, 1015.7481f, 1015.74133f, 1015.73553f, 1015.7462f, 1015.74896f, 1015.752f, 1015.754f, 1015.7456f, 1015.73303f, 1015.7288f, 1015.7239f, 1015.7298f, 1015.7387f, 1015.72534f, 1015.72577f, 1015.72797f, 1015.71796f, 1015.7113f, 1015.7097f, 1015.7096f, 1015.7013f, 1015.7072f, 1015.71063f, 1015.7088f, 1015.7115f, 1015.713f, 1015.7121f, 1015.7062f, 1015.7062f, 1015.6994f, 1015.69617f, 1015.68945f, 1015.698f, 1015.6913f, 1015.69696f, 1015.69293f, 1015.6955f, 1015.6928f, 1015.6939f, 1015.68787f, 1015.6881f, 1015.68536f, 1015.68726f, 1015.6879f, 1015.6904f, 1015.6937f, 1015.69476f, 1015.7046f, 1015.7019f, 1015.7079f, 1015.71124f, 1015.71216f, 1015.71436f, 1015.7171f, 1015.7186f, 1015.7186f, 1015.7246f, 1015.72046f, 1015.7211f, 1015.72375f, 1015.7194f, 1015.72125f, 1015.7161f, 1015.71185f, 1015.7188f, 1015.721f, 1015.7252f, 1015.72266f, 1015.7236f, 1015.72614f, 1015.7266f, 1015.7344f, 1015.7376f, 1015.7337f, 1015.7253f, 1015.71875f, 1015.7219f, 1015.7219f, 1015.7317f, 1015.7284f, 1015.73444f, 1015.73584f, 1015.7369f, 1015.73254f, 1015.7369f, 1015.7336f, 1015.73254f, 1015.73676f, 1015.74176f, 1015.74506f, 1015.7478f, 1015.755f, 1015.7515f, 1015.7543f, 1015.7459f, 1015.7468f, 1015.7492f, 1015.74585f, 1015.74756f, 1015.74756f, 1015.75085f, 1015.754f, 1015.75336f, 1015.7568f, 1015.75165f, 1015.761f, 1015.7527f, 1015.75684f, 1015.7635f, 1015.7585f, 1015.7552f, 1015.7475f, 1015.74506f, 1015.7542f, 1015.75586f, 1015.74176f, 1015.74414f, 1015.7501f, 1015.751f, 1015.7478f, 1015.755f, 1015.75836f, 1015.7592f, 1015.75256f, 1015.7559f, 1015.76654f, 1015.76154f, 1015.766f, 1015.77014f, 1015.76904f, 1015.76013f, 1015.7674f, 1015.76514f, 1015.7674f, 1015.7635f, 1015.76404f, 1015.7607f, 1015.754f, 1015.7574f, 1015.7607f, 1015.7607f, 1015.7607f, 1015.754f, 1015.7567f, 1015.7616f, 1015.7643f, 1015.7666f, 1015.7609f, 1015.766f, 1015.76764f, 1015.755f, 1015.75757f, 1015.74927f, 1015.74927f, 1015.7426f, 1015.745f, 1015.745f, 1015.7409f, 1015.7376f, 1015.7326f, 1015.74164f, 1015.74664f, 1015.745f, 1015.7459f, 1015.7502f, 1015.7493f, 1015.7476f, 1015.7493f, 1015.751f, 1015.7601f, 1015.76434f, 1015.766f, 1015.756f, 1015.7617f, 1015.7623f, 1015.76404f, 1015.76733f, 1015.7607f, 1015.7623f, 1015.76337f, 1015.7584f, 1015.7573f, 1015.7502f, 1015.7534f, 1015.7534f, 1015.7601f, 1015.75684f, 1015.7518f, 1015.75574f, 1015.7524f, 1015.7507f, 1015.7484f, 1015.7502f, 1015.7534f, 1015.7623f, 1015.75903f, 1015.75574f, 1015.75574f, 1015.7573f, 1015.7524f, 1015.75574f, 1015.75574f, 1015.7607f, 1015.7573f, 1015.754f, 1015.74677f, 1015.754f, 1015.75574f, 1015.75903f, 1015.7573f, 1015.7551f, 1015.7573f, 1015.754f, 1015.7507f, 1015.7374f, 1015.739f, 1015.73566f, 1015.73566f, 1015.7367f, 1015.7417f, 1015.7417f, 1015.739f, 1015.74835f, 1015.74567f, 1015.7517f, 1015.74066f, 1015.7374f, 1015.739f, 1015.7417f, 1015.73065f, 1015.72894f, 1015.73505f, 1015.73334f, 1015.74005f, 1015.74005f, 1015.7367f, 1015.7434f, 1015.744f, 1015.7423f, 1015.7384f, 1015.739f, 1015.7374f, 1015.7374f, 1015.7374f, 1015.7341f, 1015.73505f, 1015.7341f, 1015.7267f, 1015.72833f, 1015.7234f, 1015.7167f, 1015.725f, 1015.7217f, 1015.7234f, 1015.73004f, 1015.7324f, 1015.7423f, 1015.74005f, 1015.73505f, 1015.73004f, 1015.739f, 1015.7417f, 1015.74567f, 1015.74066f, 1015.74146f, 1015.74146f, 1015.73816f, 1015.7274f, 1015.72906f, 1015.7274f, 1015.7341f, 1015.739f, 1015.744f, 1015.7417f, 1015.7374f, 1015.7324f, 1015.73816f, 1015.74146f, 1015.7374f, 1015.7341f, 1015.7365f, 1015.7365f, 1015.74146f, 1015.74414f, 1015.7475f, 1015.7448f, 1015.74146f, 1015.74585f, 1015.7475f, 1015.75146f, 1015.75146f, 1015.7591f, 1015.75476f, 1015.7614f, 1015.75476f, 1015.7492f, 1015.74817f, 1015.7498f, 1015.7515f, 1015.75586f, 1015.7509f, 1015.7448f, 1015.7525f, 1015.7581f, 1015.7592f};
|
||||
float firstSensorValue = sensorValues_hPa[0];
|
||||
@@ -20,7 +20,7 @@ public class PressureSensorUtilsTest {
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate a sudden drop of elevation.
|
||||
// Simulate a sudden drop of altitude.
|
||||
// test data
|
||||
@Test
|
||||
public void computeChanges_downhill() {
|
||||
@@ -28,22 +28,22 @@ public class PressureSensorUtilsTest {
|
||||
float firstSensorValue = sensorValues_hPa[0];
|
||||
|
||||
// when
|
||||
float elevationGain_m = 0;
|
||||
float elevationLoss_m = 0;
|
||||
float altitudeGain_m = 0;
|
||||
float altitudeLoss = 0;
|
||||
float lastUsedPressureValue_hPa = firstSensorValue;
|
||||
|
||||
for (float v : sensorValues_hPa) {
|
||||
PressureSensorUtils.ElevationChange elevationChange = PressureSensorUtils.computeChanges_m(lastUsedPressureValue_hPa, v);
|
||||
if (elevationChange != null) {
|
||||
elevationGain_m += elevationChange.getElevationGain_m();
|
||||
elevationLoss_m += elevationChange.getElevationLoss_m();
|
||||
lastUsedPressureValue_hPa = elevationChange.getCurrentSensorValue_hPa();
|
||||
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChanges_m(lastUsedPressureValue_hPa, v);
|
||||
if (altitudeChange != null) {
|
||||
altitudeGain_m += altitudeChange.getAltitudeGain_m();
|
||||
altitudeLoss += altitudeChange.getAltitudeLoss_m();
|
||||
lastUsedPressureValue_hPa = altitudeChange.getCurrentSensorValue_hPa();
|
||||
}
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals(0f, elevationGain_m, 0.01);
|
||||
assertEquals(15.0f, elevationLoss_m, 0.01);
|
||||
assertEquals(0f, altitudeGain_m, 0.01);
|
||||
assertEquals(15.0f, altitudeLoss, 0.01);
|
||||
assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,9 @@ import java.time.Duration;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -70,19 +73,18 @@ public class StringUtilsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#formatDistance(android.content.Context, double,
|
||||
* boolean)}.
|
||||
* Tests {@link StringUtils#formatDistance(android.content.Context, Distance, boolean)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFormatDistance() {
|
||||
// A large number in metric
|
||||
assertEquals("5.00 km", StringUtils.formatDistance(context, 5000, true));
|
||||
assertEquals("5.00 km", StringUtils.formatDistance(context, Distance.of(5000), true));
|
||||
// A large number in imperial
|
||||
assertEquals("3.11 mi", StringUtils.formatDistance(context, 5000, false));
|
||||
assertEquals("3.11 mi", StringUtils.formatDistance(context, Distance.of(5000), false));
|
||||
// A small number in metric
|
||||
assertEquals("100.00 m", StringUtils.formatDistance(context, 100, true));
|
||||
assertEquals("100.00 m", StringUtils.formatDistance(context, Distance.of(100), true));
|
||||
// A small number in imperial
|
||||
assertEquals("328.08 ft", StringUtils.formatDistance(context, 100, false));
|
||||
assertEquals("328.08 ft", StringUtils.formatDistance(context, Distance.of(100), false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,22 +166,22 @@ public class StringUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts() {
|
||||
assertEquals("4:59", StringUtils.getSpeedParts(context, 3.34, true, false).first);
|
||||
assertEquals("5:00", StringUtils.getSpeedParts(context, 3.33, true, false).first);
|
||||
assertEquals("4:59", StringUtils.getSpeedParts(context, Speed.of(3.34), true, false).first);
|
||||
assertEquals("5:00", StringUtils.getSpeedParts(context, Speed.of(3.33), true, false).first);
|
||||
|
||||
assertEquals("11.9", StringUtils.getSpeedParts(context, 3.31, true, true).first);
|
||||
assertEquals("7.5", StringUtils.getSpeedParts(context, 3.34, false, true).first);
|
||||
assertEquals("11.9", StringUtils.getSpeedParts(context, Speed.of(3.31), true, true).first);
|
||||
assertEquals("7.5", StringUtils.getSpeedParts(context, Speed.of(3.34), false, true).first);
|
||||
|
||||
assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second);
|
||||
assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second);
|
||||
assertEquals("min/km", StringUtils.getSpeedParts(context, Speed.zero(), true, false).second);
|
||||
assertEquals("min/mi", StringUtils.getSpeedParts(context, Speed.zero(), false, false).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSpeed() {
|
||||
assertEquals("4:59 min/km", StringUtils.formatSpeed(context, 3.34, true, false));
|
||||
assertEquals("8:02 min/mi", StringUtils.formatSpeed(context, 3.34, false, false));
|
||||
assertEquals("4:59 min/km", StringUtils.formatSpeed(context, Speed.of(3.34), true, false));
|
||||
assertEquals("8:02 min/mi", StringUtils.formatSpeed(context, Speed.of(3.34), false, false));
|
||||
|
||||
assertEquals("12.02 km/h", StringUtils.formatSpeed(context, 3.34, true, true));
|
||||
assertEquals("7.47 mph", StringUtils.formatSpeed(context, 3.34, false, true));
|
||||
assertEquals("12.02 km/h", StringUtils.formatSpeed(context, Speed.of(3.34), true, true));
|
||||
assertEquals("7.47 mph", StringUtils.formatSpeed(context, Speed.of(3.34), false, true));
|
||||
}
|
||||
}
|
||||
|
||||
+40
-38
@@ -13,6 +13,8 @@ import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
@@ -25,17 +27,17 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private static Track createTrack(Context context, long totalDistance, long totalTime, String category) {
|
||||
private static Track createTrack(Context context, Distance totalDistance, Duration totalTime, String category) {
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setStartTime(Instant.ofEpochMilli(1000L)); // Resulting start time
|
||||
statistics.setStopTime(Instant.ofEpochMilli(1000L + totalTime));
|
||||
statistics.setTotalTime(Duration.ofMillis(totalTime));
|
||||
statistics.setMovingTime(Duration.ofMillis(totalTime));
|
||||
statistics.setStopTime(statistics.getStartTime().plus(totalTime));
|
||||
statistics.setTotalTime(totalTime);
|
||||
statistics.setMovingTime(totalTime);
|
||||
statistics.setTotalDistance(totalDistance);
|
||||
statistics.setTotalElevationGain(50.0f);
|
||||
statistics.setMaxSpeed(50.0); // Resulting max speed
|
||||
statistics.setMaxElevation(1250.0);
|
||||
statistics.setMinElevation(1200.0); // Resulting min elevation
|
||||
statistics.setTotalAltitudeGain(50.0f);
|
||||
statistics.setMaxSpeed(Speed.of(50.0)); // Resulting max speed
|
||||
statistics.setMaxAltitude(1250.0);
|
||||
statistics.setMinAltitude(1200.0); // Resulting min altitude
|
||||
|
||||
Track track = new Track();
|
||||
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||
@@ -48,8 +50,8 @@ public class AggregatedStatisticsTest {
|
||||
public void testAggregate() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
Distance totalDistance = Distance.of(10000);
|
||||
Duration totalTime = Duration.ofMillis(2400000);
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
Track track = createTrack(context, totalDistance, totalTime, biking);
|
||||
|
||||
@@ -62,16 +64,16 @@ public class AggregatedStatisticsTest {
|
||||
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance());
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate_mountainBiking() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
Distance totalDistance = Distance.of(10000);
|
||||
Duration totalTime = Duration.ofMillis(2400000);
|
||||
String mountainBiking = context.getString(R.string.activity_type_mountain_biking);
|
||||
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
|
||||
|
||||
@@ -86,8 +88,8 @@ public class AggregatedStatisticsTest {
|
||||
public void testAggregate_trailRunning() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
Distance totalDistance = Distance.of(10000);
|
||||
Duration totalTime = Duration.ofMillis(2400000);
|
||||
String trailRunning = context.getString(R.string.activity_type_trail_running);
|
||||
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
|
||||
|
||||
@@ -102,8 +104,8 @@ public class AggregatedStatisticsTest {
|
||||
public void testAggregate_twoBikingTracks() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
Distance totalDistance = Distance.of(10000);
|
||||
Duration totalTime = Duration.ofMillis(2400000);
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
List<Track> tracks = List.of(createTrack(context, totalDistance, totalTime, biking), createTrack(context, totalDistance, totalTime, biking));
|
||||
|
||||
@@ -116,20 +118,20 @@ public class AggregatedStatisticsTest {
|
||||
assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
|
||||
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate_threeDifferentTracks() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
Distance totalDistance = Distance.of(10000);
|
||||
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
String running = context.getString(R.string.activity_type_running);
|
||||
String walking = context.getString(R.string.activity_type_walking);
|
||||
long totalTime = 2400000;
|
||||
Duration totalTime = Duration.ofMillis(2400000);
|
||||
List<Track> tracks = List.of(
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, running),
|
||||
@@ -150,20 +152,20 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance());
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance());
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance());
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +173,8 @@ public class AggregatedStatisticsTest {
|
||||
public void testAggregate_severalTracksWithSeveralActivities() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
Distance totalDistance = Distance.of(10000);
|
||||
Duration totalTime = Duration.ofMillis(2400000);
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
String running = context.getString(R.string.activity_type_running);
|
||||
String walking = context.getString(R.string.activity_type_walking);
|
||||
@@ -213,29 +215,29 @@ public class AggregatedStatisticsTest {
|
||||
// Biking.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime * 5), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance.multipliedBy(5), statistics2.getTotalDistance());
|
||||
assertEquals(totalTime.multipliedBy(5), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Running.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
|
||||
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Walking.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
|
||||
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Driving.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance());
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Check order
|
||||
|
||||
+95
-93
@@ -6,6 +6,7 @@ import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -18,121 +19,122 @@ import static org.junit.Assert.assertEquals;
|
||||
@RunWith(JUnit4.class)
|
||||
public class IntervalStatisticsTest {
|
||||
|
||||
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
|
||||
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
|
||||
|
||||
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
|
||||
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second;
|
||||
}
|
||||
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
|
||||
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second;
|
||||
}
|
||||
|
||||
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
|
||||
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
|
||||
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
for (TrackPoint tp : trackPoints) {
|
||||
trackStatisticsUpdater.addTrackPoint(tp, 0);
|
||||
trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0));
|
||||
}
|
||||
return trackStatisticsUpdater.getTrackStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_1() {
|
||||
// With 50 points and interval distance of 1000m.
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_1() {
|
||||
// With 50 points and interval distance of 1000m.
|
||||
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(50);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(50);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_2() {
|
||||
// With 200 points and interval distance of 1000m.
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_2() {
|
||||
// With 200 points and interval distance of 1000m.
|
||||
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_3() {
|
||||
// With 200 points and interval distance of 3000m.
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_3() {
|
||||
// With 200 points and interval distance of 3000m.
|
||||
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 3000f;
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 3000f;
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_4() {
|
||||
// With 1000 points and interval distance of 3000m.
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_4() {
|
||||
// With 1000 points and interval distance of 3000m.
|
||||
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(1000);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 3000f;
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(1000);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 3000f;
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_5() {
|
||||
// With 10000 points and interval distance of 1000m.
|
||||
/**
|
||||
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
|
||||
*/
|
||||
@Test
|
||||
public void testBuild_5() {
|
||||
// With 10000 points and interval distance of 1000m.
|
||||
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(10000);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
// given
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(10000);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval) {
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval);
|
||||
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
||||
double totalDistance = 0d;
|
||||
float totalTime = 0L;
|
||||
float totalGain = 0f;
|
||||
for (IntervalStatistics.Interval i : intervalList) {
|
||||
totalDistance += i.getDistance_m();
|
||||
totalTime += i.getDistance_m() / i.getSpeed_ms();
|
||||
totalGain += i.getGain_m();
|
||||
}
|
||||
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval) {
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(distanceInterval));
|
||||
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
||||
Distance totalDistance = Distance.of(0);
|
||||
float totalTime = 0L;
|
||||
float totalGain = 0f;
|
||||
for (IntervalStatistics.Interval i : intervalList) {
|
||||
totalDistance = totalDistance.plus(i.getDistance());
|
||||
totalTime += i.getDistance().toM() / i.getSpeed().toMPS();
|
||||
totalGain += i.getGain_m();
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals(trackStatistics.getTotalDistance(), totalDistance, 0.01);
|
||||
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
|
||||
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance() / distanceInterval));
|
||||
assertEquals(totalGain, trackPoints.size() * TestDataUtil.ELEVATION_GAIN, 0.1);
|
||||
for (int i = 0; i < intervalList.size() - 1; i++) {
|
||||
assertEquals(intervalList.get(i).getDistance_m(), distanceInterval, 0.001);
|
||||
totalDistance -= intervalList.get(i).getDistance_m();
|
||||
}
|
||||
assertEquals(intervalList.get(intervalList.size() - 1).getDistance_m(), totalDistance, 0.01);
|
||||
}
|
||||
// then
|
||||
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
|
||||
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
|
||||
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
|
||||
assertEquals(totalGain, trackPoints.size() * TestDataUtil.ALTITUDE_GAIN, 0.1);
|
||||
|
||||
for (int i = 0; i < intervalList.size() - 1; i++) {
|
||||
assertEquals(intervalList.get(i).getDistance().toM(), distanceInterval, 0.001);
|
||||
totalDistance = totalDistance.minus(intervalList.get(i).getDistance());
|
||||
}
|
||||
assertEquals(intervalList.get(intervalList.size() - 1).getDistance().toM(), totalDistance.toM(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:hasFragileUserData="true"
|
||||
tools:replace="android:hasFragileUserData" />
|
||||
</manifest>
|
||||
@@ -51,7 +51,7 @@ limitations under the License.
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:hasFragileUserData="true"
|
||||
android:hasFragileUserData="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
@@ -59,10 +59,6 @@ limitations under the License.
|
||||
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".TrackDeleteActivity"
|
||||
android:theme="@style/TrackDeleteActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:label="@string/menu_help" />
|
||||
@@ -350,5 +346,18 @@ limitations under the License.
|
||||
android:name=".io.file.importer.ImportService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<service
|
||||
android:name=".services.TrackDeleteService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/delete_service" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -32,9 +31,4 @@ public class AboutActivity extends AbstractActivity {
|
||||
viewBinding = AboutBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* An abstract class for the following common tasks across
|
||||
* {@link TrackListActivity}, {@link TrackRecordedActivity}, and {@link TrackRecordedActivity}:
|
||||
* <p>
|
||||
* - share track <br>
|
||||
* - delete tracks <br>
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
|
||||
|
||||
private static final String TAG = AbstractListActivity.class.getSimpleName();
|
||||
|
||||
protected static final int GPS_REQUEST_CODE = 6;
|
||||
private static final int DELETE_REQUEST_CODE = 3;
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == DELETE_REQUEST_CODE) {
|
||||
onTrackDeleted();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tracks.
|
||||
*
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
protected void deleteTracks(Track.Id... trackIds) {
|
||||
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmDeleteDone(Track.Id... trackIds) {
|
||||
boolean stopRecording = false;
|
||||
|
||||
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(PreferencesUtils.getSharedPreferences(this), this);
|
||||
for (Track.Id trackId : trackIds) {
|
||||
if (trackId.equals(recordingTrackId)) {
|
||||
stopRecording = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stopRecording) {
|
||||
getTrackRecordingServiceConnection().stopRecording(this, false);
|
||||
}
|
||||
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
|
||||
intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
|
||||
startActivityForResult(intent, DELETE_REQUEST_CODE);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track recording service connection.
|
||||
* For stopping the current recording if need to delete the current recording track.
|
||||
*/
|
||||
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection();
|
||||
|
||||
/**
|
||||
* Called after {@link TrackDeleteActivity} returns its result.
|
||||
*/
|
||||
abstract protected void onTrackDeleted();
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
|
||||
import de.dennisguse.opentracks.services.TrackDeleteService;
|
||||
import de.dennisguse.opentracks.services.TrackDeleteServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
|
||||
/**
|
||||
* An abstract class for the following common tasks across
|
||||
* {@link TrackListActivity} and {@link TrackRecordedActivity}:
|
||||
* <p>
|
||||
* - delete tracks <br>
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
//TODO Check if this class is still such a good idea; inheritance might limit maintainability
|
||||
public abstract class AbstractTrackDeleteActivity extends AbstractActivity implements ConfirmDeleteCaller, TrackDeleteServiceConnection.Listener {
|
||||
|
||||
private static final String TAG = AbstractTrackDeleteActivity.class.getSimpleName();
|
||||
|
||||
private TrackDeleteServiceConnection trackDeleteServiceConnection;
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
trackDeleteServiceConnection = new TrackDeleteServiceConnection(this);
|
||||
trackDeleteServiceConnection.bind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (trackDeleteServiceConnection != null) {
|
||||
trackDeleteServiceConnection.unbind(this);
|
||||
trackDeleteServiceConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tracks.
|
||||
*
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
protected void deleteTracks(Track.Id... trackIds) {
|
||||
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmDeleteDone(Track.Id... trackIds) {
|
||||
boolean stopRecording = false;
|
||||
|
||||
onDeleteConfirmed();
|
||||
|
||||
for (Track.Id trackId : trackIds) {
|
||||
if (trackId.equals(getRecordingTrackId())) {
|
||||
stopRecording = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stopRecording) {
|
||||
getTrackRecordingServiceConnection().stopRecording(this);
|
||||
}
|
||||
|
||||
trackDeleteServiceConnection = new TrackDeleteServiceConnection(this);
|
||||
trackDeleteServiceConnection.startAndBind(this, new ArrayList<>(Arrays.asList(trackIds)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track recording service connection.
|
||||
* For stopping the current recording if need to delete the current recording track.
|
||||
*/
|
||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every time a track is deleted.
|
||||
*/
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) {
|
||||
trackDeleteServiceConnection.unbind(this);
|
||||
trackDeleteServiceConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onDeleteConfirmed();
|
||||
|
||||
@Nullable
|
||||
protected Track.Id getRecordingTrackId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected() {
|
||||
TrackDeleteService service = trackDeleteServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
return;
|
||||
}
|
||||
service.getDeletingStatusObservable().observe(AbstractTrackDeleteActivity.this, status -> onTrackDeleteStatus(status));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.ControllerFragmentBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Track controller for record, pause, resume, and stop.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
//TODO Move buttons into separate components
|
||||
public class ControllerFragment extends Fragment implements View.OnTouchListener {
|
||||
|
||||
private static final String TAG = ControllerFragment.class.getSimpleName();
|
||||
|
||||
@Deprecated
|
||||
//TODO TrackRecordingService should provide LifeData for time (replace internal timer)
|
||||
private static final Duration UI_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
||||
@Deprecated
|
||||
private Handler handlerUpdateTotalTime;
|
||||
|
||||
private Runnable buttonDelay;
|
||||
private int buttonDelayDuration;
|
||||
|
||||
private ControllerFragmentBinding viewBinding;
|
||||
private TransitionDrawable transition;
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackRecordingService.RecordingStatus recordingStatus;
|
||||
private Duration totalTime;
|
||||
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "could not get TrackRecordingService");
|
||||
return;
|
||||
}
|
||||
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(ControllerFragment.this, status -> onRecordingStatusChanged(status));
|
||||
}
|
||||
};
|
||||
|
||||
// A runnable to update the total time.
|
||||
@Deprecated
|
||||
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && recordingStatus.isRecordingAndNotPaused()) {
|
||||
updateTotalTime();
|
||||
setTotalTime();
|
||||
handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
buttonDelayDuration = getResources().getInteger(R.integer.buttonDelayMillis);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
handlerUpdateTotalTime = new Handler();
|
||||
recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
viewBinding = ControllerFragmentBinding.inflate(inflater, container, false);
|
||||
|
||||
viewBinding.controllerRecord.setOnTouchListener(this);
|
||||
viewBinding.controllerRecord.setOnClickListener((view) -> {
|
||||
if (buttonDelay != null || recordingStatus.isRecordingAndNotPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getCallback().recordStart();
|
||||
});
|
||||
|
||||
viewBinding.controllerStop.setOnTouchListener(this);
|
||||
transition = (TransitionDrawable) viewBinding.controllerContainer.getBackground();
|
||||
|
||||
onRecordingStatusChanged(recordingStatus);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
viewBinding = null;
|
||||
transition = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection = null;
|
||||
handlerUpdateTotalTime = null;
|
||||
recordingStatus = null;
|
||||
}
|
||||
|
||||
//TODO Move into a separate object
|
||||
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
||||
@Override
|
||||
public boolean onTouch(final View view, MotionEvent event) {
|
||||
if (!isResumed()) {
|
||||
Log.w(TAG, "The UI is already gone, no need to update anything; seen on Android 7.1.2");
|
||||
return false;
|
||||
}
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
||||
buttonDelay = null;
|
||||
view.setPressed(false);
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
showStatusSetDefaultText();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Trigger button pressed animation
|
||||
view.setPressed(true);
|
||||
|
||||
//To pause a recording
|
||||
if (viewBinding.controllerRecord.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& recordingStatus.isRecordingAndNotPaused()) {
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(getContext(), 150);
|
||||
viewBinding.controllerStatus.setText(R.string.hold_to_pause);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
view.performClick();
|
||||
getCallback().recordPause();
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
ActivityUtils.vibrate(getContext(), 1000);
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
//To stop a recording
|
||||
if (viewBinding.controllerStop.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& recordingStatus.isRecording()) {
|
||||
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(getContext(), 150);
|
||||
viewBinding.controllerStatus.setText(R.string.hold_to_stop);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
getCallback().recordStop();
|
||||
ActivityUtils.vibrate(getContext(), 1000);
|
||||
|
||||
transition.resetTransition();
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
viewBinding.controllerContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
viewBinding.controllerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void updateTotalTime() {
|
||||
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
totalTime = trackRecordingService.getTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void setTotalTime() {
|
||||
if (totalTime != null) {
|
||||
viewBinding.controllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
|
||||
}
|
||||
}
|
||||
|
||||
private void showStatusSetDefaultText() {
|
||||
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
||||
}
|
||||
|
||||
private Callback getCallback() {
|
||||
return (Callback) getActivity();
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void recordStart();
|
||||
|
||||
void recordPause();
|
||||
|
||||
void recordStop();
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
|
||||
if (!recordingStatus.isRecording()) {
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
}
|
||||
if (recordingStatus.isRecordingAndNotPaused()) {
|
||||
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UI_UPDATE_INTERVAL.toMillis());
|
||||
}
|
||||
|
||||
viewBinding.controllerRecord.setImageResource(recordingStatus.isRecordingAndNotPaused() ? R.drawable.ic_button_pause : R.drawable.button_record);
|
||||
viewBinding.controllerRecord.setContentDescription(getContext().getString(recordingStatus.isRecordingAndNotPaused() ? R.string.image_pause : R.string.image_record));
|
||||
|
||||
viewBinding.controllerStop.setEnabled(recordingStatus.isRecording());
|
||||
|
||||
viewBinding.controllerStatus.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
||||
if (recordingStatus.isRecording()) {
|
||||
viewBinding.controllerStatus.setTextColor(getContext().getResources().getColor(recordingStatus.isPaused() ? android.R.color.white : R.color.recording_text));
|
||||
showStatusSetDefaultText();
|
||||
}
|
||||
|
||||
viewBinding.controllerTotalTime.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
if (recordingStatus.isRecording()) {
|
||||
updateTotalTime();
|
||||
setTotalTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,9 @@ package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -42,9 +41,10 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.databinding.MarkerListBinding;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Activity to show a list of markers in a track.
|
||||
@@ -59,25 +59,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
|
||||
private Track.Id recordingTrackId;
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, MarkerListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, MarkerListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
|
||||
}
|
||||
}
|
||||
};
|
||||
private Track track;
|
||||
private MarkerResourceCursorAdapter resourceCursorAdapter;
|
||||
|
||||
@@ -85,6 +68,22 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
private final MarkerLoaderCallback loaderCallbacks = new MarkerLoaderCallback();
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
private final Runnable bindCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "could not get TrackRecordingService");
|
||||
return;
|
||||
}
|
||||
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(MarkerListActivity.this, status -> onRecordingStatusChanged(status));
|
||||
}
|
||||
};
|
||||
|
||||
// Callback when an item is selected in the contextual action mode
|
||||
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||
@Override
|
||||
@@ -112,10 +111,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
super.onCreate(savedInstanceState);
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
track = trackId != null ? contentProviderUtils.getTrack(trackId) : null;
|
||||
|
||||
@@ -132,18 +129,20 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
viewBinding.markerList.setOnScrollListener(scrollVisibleViews);
|
||||
viewBinding.markerList.setAdapter(resourceCursorAdapter);
|
||||
ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
trackRecordingServiceConnection.bind(this);
|
||||
this.invalidateOptionsMenu();
|
||||
loadData(getIntent());
|
||||
}
|
||||
@@ -151,7 +150,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,7 +160,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
viewBinding = null;
|
||||
resourceCursorAdapter = null;
|
||||
contentProviderUtils = null;
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,14 +175,14 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.marker_list_search);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, null);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingTrackId) && !recordingTrackPaused);
|
||||
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingStatus.getTrackId()) && !recordingStatus.isPaused());
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -263,15 +261,16 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
}
|
||||
|
||||
if (loaderCallbacks.getSearchQuery() != null) {
|
||||
loaderCallbacks.setSearch(null);
|
||||
return;
|
||||
}
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@@ -296,6 +295,10 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
}
|
||||
|
||||
private class MarkerLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private String searchQuery = null;
|
||||
|
||||
@@ -24,10 +24,10 @@ public class Startup extends Application {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
//Include version information into stack traces.
|
||||
// Include version information into stack traces.
|
||||
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
|
||||
|
||||
//Set default values of preferences on first start.
|
||||
// Set default values of preferences on first start.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(sharedPreferences, 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);
|
||||
@@ -36,13 +36,7 @@ public class Startup extends Application {
|
||||
|
||||
ActivityUtils.applyNightMode(sharedPreferences, this);
|
||||
|
||||
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
|
||||
if (PreferencesUtils.isRecording(sharedPreferences, this)) {
|
||||
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
|
||||
PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
|
||||
//In debug builds: show thread and VM warnings.
|
||||
// In debug builds: show thread and VM warnings.
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Enabling strict mode");
|
||||
StrictMode.enableDefaults();
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.TrackControllerBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* Track controller for record, pause, resume, and stop.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class TrackController implements View.OnTouchListener {
|
||||
|
||||
private static final String TAG = TrackController.class.getSimpleName();
|
||||
|
||||
private final Activity activity;
|
||||
private final TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private final Handler handlerUpdateTotalTime = new Handler();
|
||||
private final boolean alwaysShow;
|
||||
|
||||
private Runnable buttonDelay;
|
||||
private final int buttonDelayDuration;
|
||||
|
||||
private final TrackControllerBinding viewBinding;
|
||||
private final TransitionDrawable transition;
|
||||
|
||||
private boolean isRecording;
|
||||
private boolean isRecordingPaused;
|
||||
private Duration totalTime;
|
||||
private boolean isResumed = false;
|
||||
|
||||
private final Callback callback;
|
||||
|
||||
// A runnable to update the total time.
|
||||
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed && isRecording && !isRecordingPaused) {
|
||||
updateTotalTime();
|
||||
setTotalTime();
|
||||
handlerUpdateTotalTime.postDelayed(this, UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
TrackController(Activity activity, TrackControllerBinding viewBinding, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, Callback callback) {
|
||||
this.activity = activity;
|
||||
this.viewBinding = viewBinding;
|
||||
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
|
||||
this.alwaysShow = alwaysShow;
|
||||
this.callback = callback;
|
||||
|
||||
buttonDelayDuration = activity.getResources().getInteger(R.integer.buttonDelayMillis);
|
||||
|
||||
viewBinding.trackControllerRecord.setOnTouchListener(this);
|
||||
viewBinding.trackControllerRecord.setOnClickListener((view) -> {
|
||||
if (buttonDelay != null || (isRecording && !isRecordingPaused)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback.recordStart();
|
||||
});
|
||||
|
||||
viewBinding.trackControllerStop.setOnTouchListener(this);
|
||||
transition = (TransitionDrawable) viewBinding.trackControllerContainer.getBackground();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
||||
@Override
|
||||
public boolean onTouch(final View view, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
||||
buttonDelay = null;
|
||||
view.setPressed(false);
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
showStatusSetDefaultText();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Trigger button pressed animation
|
||||
view.setPressed(true);
|
||||
|
||||
//To pause a recording
|
||||
if (viewBinding.trackControllerRecord.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& isRecording && !isRecordingPaused) {
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(activity, 150);
|
||||
viewBinding.trackControllerStatus.setText(R.string.hold_to_pause);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
view.performClick();
|
||||
callback.recordStart(); //TODO Should be recordPause();
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
ActivityUtils.vibrate(activity, 1000);
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
//To stop a recording
|
||||
if (viewBinding.trackControllerStop.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& isRecording) {
|
||||
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(activity, 150);
|
||||
viewBinding.trackControllerStatus.setText(R.string.hold_to_stop);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
callback.recordStop();
|
||||
ActivityUtils.vibrate(activity, 1000);
|
||||
|
||||
transition.resetTransition();
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void update(boolean recording, boolean paused) {
|
||||
if (!isResumed) {
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
return;
|
||||
}
|
||||
isRecording = recording;
|
||||
isRecordingPaused = paused;
|
||||
boolean visible = alwaysShow || isRecording;
|
||||
viewBinding.trackControllerContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (!visible) {
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRecording && !isRecordingPaused) {
|
||||
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
|
||||
viewBinding.trackControllerRecord.setImageResource(isRecording && !isRecordingPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
|
||||
viewBinding.trackControllerRecord.setContentDescription(activity.getString(isRecording && !isRecordingPaused ? R.string.image_pause : R.string.image_record));
|
||||
|
||||
viewBinding.trackControllerStop.setEnabled(isRecording);
|
||||
|
||||
viewBinding.trackControllerStatus.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
viewBinding.trackControllerStatus.setTextColor(activity.getResources().getColor(isRecordingPaused ? android.R.color.white : R.color.recording_text));
|
||||
showStatusSetDefaultText();
|
||||
}
|
||||
|
||||
viewBinding.trackControllerTotalTime.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
if (isRecording) {
|
||||
updateTotalTime();
|
||||
setTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
void onResume(boolean recording, boolean paused) {
|
||||
isResumed = true;
|
||||
update(recording, paused);
|
||||
}
|
||||
|
||||
void onPause() {
|
||||
isResumed = false;
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
viewBinding.trackControllerContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
viewBinding.trackControllerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void updateTotalTime() {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
totalTime = trackRecordingService.getTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void setTotalTime() {
|
||||
if (totalTime != null) {
|
||||
viewBinding.trackControllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
|
||||
}
|
||||
}
|
||||
|
||||
private void showStatusSetDefaultText() {
|
||||
viewBinding.trackControllerStatus.setText(isRecordingPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void recordStart();
|
||||
|
||||
void recordStop();
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.databinding.TrackDeleteBinding;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
/**
|
||||
* An activity for deleting tracks.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class TrackDeleteActivity extends AbstractActivity {
|
||||
|
||||
public static final String EXTRA_TRACK_IDS = "track_ids";
|
||||
|
||||
private List<Track.Id> trackIds;
|
||||
|
||||
private Thread deleteThread;
|
||||
|
||||
private TrackDeleteBinding viewBinding;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
Intent intent = getIntent();
|
||||
trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS);
|
||||
deleteThread = new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(TrackDeleteActivity.this);
|
||||
|
||||
PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null);
|
||||
contentProviderUtils.deleteTracks(TrackDeleteActivity.this, trackIds);
|
||||
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
if (Thread.interrupted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
runOnUiThread(TrackDeleteActivity.this::onAsyncTaskCompleted);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
deleteThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
deleteThread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackDeleteBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
public void onAsyncTaskCompleted() {
|
||||
viewBinding.progressbar.setVisibility(View.INVISIBLE);
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
@@ -48,12 +49,13 @@ import androidx.loader.content.Loader;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
@@ -69,16 +71,16 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback {
|
||||
public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, ControllerFragment.Callback {
|
||||
|
||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||
|
||||
protected static final int GPS_REQUEST_CODE = 6;
|
||||
|
||||
// The following are set in onCreate
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackController trackController;
|
||||
private ResourceCursorAdapter resourceCursorAdapter;
|
||||
private GpsStatusValue gpsStatusValue;
|
||||
|
||||
private TrackListBinding viewBinding;
|
||||
|
||||
@@ -86,7 +88,9 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
// Preferences
|
||||
private boolean metricUnits = true;
|
||||
private Track.Id recordingTrackId = null;
|
||||
|
||||
private GpsStatusValue gpsStatusValue = TrackRecordingService.STATUS_GPS_DEFAULT;
|
||||
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
|
||||
// Callback when an item is selected in the contextual action mode
|
||||
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||
@@ -105,29 +109,16 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
}
|
||||
};
|
||||
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackListActivity.this);
|
||||
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(() -> {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
loaderCallbacks.restart();
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -141,33 +132,27 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
|
||||
// After binding changes (e.g., becomes available), update the total time in trackController.
|
||||
runOnUiThread(() -> trackController.update(isRecording, recordingTrackPaused));
|
||||
|
||||
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.d(TAG, "service not available to start gps or a new recording");
|
||||
gpsStatusValue = GpsStatusValue.GPS_NONE;
|
||||
Log.e(TAG, "service not available to start gps or a new recording");
|
||||
return;
|
||||
}
|
||||
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(TrackListActivity.this, status -> onRecordingStatusChanged(status));
|
||||
|
||||
// Get GPS status and listen GPS status changes.
|
||||
gpsStatusValue = service.getGpsStatus();
|
||||
updateGpsMenuItem(true, isRecording);
|
||||
service.addListener(newStatus -> {
|
||||
gpsStatusValue = newStatus;
|
||||
updateGpsMenuItem(true, isRecording);
|
||||
});
|
||||
service.getGpsStatusObservable()
|
||||
.observe(TrackListActivity.this, status -> onGpsStatusChanged(status));
|
||||
|
||||
if (isGpsStarted()) {
|
||||
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||
|
||||
if (service.getGpsStatusObservable().getValue().isGpsStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
service.startGps();
|
||||
gpsStatusValue = GpsStatusValue.GPS_ENABLED;
|
||||
updateGpsMenuItem(true, isRecording);
|
||||
//TODO Not cool to do this in a callback that might be called more than once!
|
||||
service.tryStartGps();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -179,25 +164,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
super.onCreate(savedInstanceState);
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
gpsStatusValue = GpsStatusValue.GPS_NONE;
|
||||
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, true, this);
|
||||
|
||||
|
||||
// Show trackController when search dialog is dismissed
|
||||
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
||||
if (searchManager != null) {
|
||||
searchManager.setOnDismissListener(() -> trackController.show());
|
||||
}
|
||||
|
||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||
if (trackId == recordingTrackId.getId()) {
|
||||
if (recordingStatus.isRecording() && trackId == recordingStatus.getTrackId().getId()) {
|
||||
// Is recording -> open record activity.
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
@@ -227,18 +200,18 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
|
||||
|
||||
Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
|
||||
boolean isRecording = trackId.equals(recordingTrackId);
|
||||
boolean isRecording = trackId.equals(recordingStatus.getTrackId());
|
||||
String icon = cursor.getString(iconIndex);
|
||||
int iconId = TrackIconUtils.getIconDrawable(icon);
|
||||
String name = cursor.getString(nameIndex);
|
||||
String totalTime = StringUtils.formatElapsedTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits);
|
||||
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits);
|
||||
int markerCount = cursor.getInt(markerCountIndex);
|
||||
long startTime = cursor.getLong(startTimeIndex);
|
||||
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused,
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingStatus.isPaused(),
|
||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||
startTime, true, category, description, false);
|
||||
}
|
||||
@@ -266,14 +239,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// Update UI
|
||||
trackController.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
@@ -281,7 +246,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Update UI
|
||||
this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||
trackController.onResume(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -314,7 +278,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
getMenuInflater().inflate(R.menu.track_list, menu);
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
||||
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||
searchView.setOnCloseListener(() -> {
|
||||
searchView.clearFocus();
|
||||
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
return true;
|
||||
});
|
||||
|
||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
@@ -322,9 +292,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
boolean isGpsStarted = isGpsStarted();
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
updateMenuItems(isGpsStarted, isRecording);
|
||||
updateGpsMenuItem(gpsStatusValue.isGpsStarted(), recordingStatus.isRecording());
|
||||
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
searchView.setQuery("", false);
|
||||
@@ -340,13 +308,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
} else {
|
||||
// Invoke trackRecordingService
|
||||
if (!isGpsStarted()) {
|
||||
if (!gpsStatusValue.isGpsStarted()) {
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
bindChangedCallback.run();
|
||||
} else {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
trackRecordingService.stopGps();
|
||||
trackRecordingService.stopGpsAndShutdown();
|
||||
}
|
||||
trackRecordingServiceConnection.unbindAndStop(this);
|
||||
}
|
||||
@@ -373,6 +341,12 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_search) {
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
searchView.setIconified(false);
|
||||
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@@ -384,17 +358,23 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overridePendingTransition(int enterAnim, int exitAnim) {
|
||||
//Disable animations as it is weird going into searchMode; looks okay for SplashScreen.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
}
|
||||
|
||||
if (loaderCallbacks.getSearchQuery() != null) {
|
||||
loaderCallbacks.setSearch(null);
|
||||
return;
|
||||
}
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@@ -414,25 +394,20 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
loaderCallbacks.setSearch(searchQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
// Hide trackController when search dialog is shown
|
||||
trackController.hide();
|
||||
return super.onSearchRequested();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||
return trackRecordingServiceConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTrackDeleted() {
|
||||
protected void onDeleteConfirmed() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
private boolean isGpsStarted() {
|
||||
return gpsStatusValue != GpsStatusValue.GPS_NONE && gpsStatusValue != GpsStatusValue.GPS_DISABLED;
|
||||
@Nullable
|
||||
@Override
|
||||
protected Track.Id getRecordingTrackId() {
|
||||
return recordingStatus.getTrackId();
|
||||
}
|
||||
|
||||
private void requestGPSPermissions() {
|
||||
@@ -441,22 +416,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the menu items with not fixed icon for gps option.
|
||||
*
|
||||
* @param isGpsStarted true if gps is started
|
||||
* @param isRecording true if recording
|
||||
*/
|
||||
private void updateMenuItems(boolean isGpsStarted, boolean isRecording) {
|
||||
updateGpsMenuItem(isGpsStarted, isRecording);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the menu items with the icon specified.
|
||||
*
|
||||
* @param isGpsStarted true if gps is started
|
||||
* @param isRecording true if recording
|
||||
*/
|
||||
//TODO Check if if can be avoided to call this outside of onGpsStatusChanged()
|
||||
private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) {
|
||||
if (startGpsMenuItem != null) {
|
||||
startGpsMenuItem.setVisible(!isRecording);
|
||||
@@ -495,7 +461,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (itemId == R.id.list_context_menu_edit) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||
@@ -507,6 +472,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
deleteTracks(trackIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_select_all) {
|
||||
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||
viewBinding.trackList.setItemChecked(i, true);
|
||||
@@ -572,27 +538,36 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
@Override
|
||||
public void recordStart() {
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
if (recordingStatus.getTrackId() == null) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
updateGpsMenuItem(false, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
startActivity(newIntent);
|
||||
} else if (recordingTrackPaused) {
|
||||
} else if (recordingStatus.isPaused()) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false, true);
|
||||
updateGpsMenuItem(false, true);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordPause() {
|
||||
updateGpsMenuItem(false, true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStop() {
|
||||
updateMenuItems(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this, true);
|
||||
updateGpsMenuItem(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||
}
|
||||
|
||||
public void onGpsStatusChanged(GpsStatusValue newStatus) {
|
||||
gpsStatusValue = newStatus;
|
||||
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
@@ -24,6 +25,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -40,6 +42,8 @@ import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.IntervalsFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
||||
import de.dennisguse.opentracks.services.TrackDeleteService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
@@ -52,7 +56,8 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
|
||||
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
|
||||
public class TrackRecordedActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
|
||||
|
||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||
|
||||
@@ -65,17 +70,47 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
|
||||
// The following are set in onCreate.
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
private TrackRecordedBinding viewBinding;
|
||||
|
||||
private Track.Id trackId;
|
||||
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
private final Runnable bindCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "could not get TrackRecordingService");
|
||||
return;
|
||||
}
|
||||
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(TrackRecordedActivity.this, status -> onRecordingStatusChanged(status));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackRecordedActivity.this, R.string.recording_distance_interval_key, key)) {
|
||||
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordedActivity.this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
handleIntent(getIntent());
|
||||
|
||||
trackDataHub = new TrackDataHub(this);
|
||||
@@ -88,12 +123,18 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||
}
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
|
||||
|
||||
postponeEnterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
trackDataHub.start();
|
||||
}
|
||||
|
||||
@@ -107,12 +148,16 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
if (trackId != null) {
|
||||
trackDataHub.loadTrack(trackId);
|
||||
}
|
||||
|
||||
trackRecordingServiceConnection.bind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
trackDataHub.stop();
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,7 +188,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(PreferencesUtils.getSharedPreferences(this), this));
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!recordingStatus.isRecording());
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
setTitle(track != null ? track.getName() : "");
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
@@ -198,14 +243,23 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||
// Not needed.
|
||||
return null;
|
||||
protected Track.Id getRecordingTrackId() {
|
||||
return recordingStatus.getTrackId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTrackDeleted() {
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||
super.onTrackDeleteStatus(deleteStatus);
|
||||
Log.e("probando", "trackId: " + trackId);
|
||||
if (deleteStatus.isDeleted(trackId)) {
|
||||
runOnUiThread(this::finish);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDeleteConfirmed() {
|
||||
runOnUiThread(this::finish);
|
||||
}
|
||||
|
||||
@@ -250,7 +304,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
case 0:
|
||||
return StatisticsRecordedFragment.newInstance(trackId);
|
||||
case 1:
|
||||
return IntervalsFragment.newInstance();
|
||||
return IntervalsFragment.newInstance(true);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 3:
|
||||
@@ -281,8 +335,12 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
}
|
||||
}
|
||||
|
||||
public void startPostponedEnterTransitionWith(View viewIcon, View viewName) {
|
||||
public void startPostponedEnterTransitionWith(View viewIcon) {
|
||||
ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON);
|
||||
startPostponedEnterTransition();
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.IntervalsFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
@@ -43,7 +43,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
||||
*/
|
||||
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
||||
//This mode of communication should be removed anyhow.
|
||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback {
|
||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, ControllerFragment.Callback {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
|
||||
@@ -56,29 +56,28 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackDataHub trackDataHub;
|
||||
private TrackController trackController;
|
||||
|
||||
private TrackRecordingBinding viewBinding;
|
||||
|
||||
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
|
||||
@Deprecated
|
||||
private Track.Id trackId;
|
||||
|
||||
// Preferences
|
||||
private boolean recordingTrackPaused;
|
||||
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// After binding changes (is available), update the total time in trackController.
|
||||
runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
|
||||
|
||||
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.d(TAG, "could not get TrackRecordingService");
|
||||
Log.w(TAG, "could not get TrackRecordingService");
|
||||
return;
|
||||
}
|
||||
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(TrackRecordingActivity.this, status -> onRecordingStatusChanged(status));
|
||||
|
||||
if (!service.isRecording()) {
|
||||
// Starts or resumes a track.
|
||||
if (trackId == null) {
|
||||
// trackId isn't initialized -> leads a new recording.
|
||||
trackId = service.startNewTrack();
|
||||
@@ -89,8 +88,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
// A recording track is on.
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackController.update(true, false);
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
trackDataHub.setRecordingStatus(recordingStatus);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -98,29 +96,21 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackRecordingActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||
setLockscreenPolicy();
|
||||
}
|
||||
|
||||
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 (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_distance_interval_key, key)) {
|
||||
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordingActivity.this));
|
||||
}
|
||||
if (key == null) return;
|
||||
|
||||
runOnUiThread(() -> {
|
||||
TrackRecordingActivity.this.invalidateOptionsMenu();
|
||||
trackController.update(true, recordingTrackPaused);
|
||||
});
|
||||
runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -145,8 +135,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
}
|
||||
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
trackDataHub = new TrackDataHub(this);
|
||||
|
||||
@@ -157,8 +145,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
if (savedInstanceState != null) {
|
||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||
}
|
||||
|
||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, false, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,11 +203,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
super.onResume();
|
||||
|
||||
// Update UI
|
||||
this.invalidateOptionsMenu();
|
||||
invalidateOptionsMenu();
|
||||
|
||||
if (trackId != null) {
|
||||
//TODO Pass recordingStatus directly to them
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
trackDataHub.setRecordingStatus(recordingStatus);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -240,12 +227,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
outState.putParcelable(EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
trackController.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
@@ -272,7 +253,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
updateMenuItems(recordingTrackPaused);
|
||||
updateMenuItems(recordingStatus.isPaused());
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -345,22 +326,19 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
@Override
|
||||
public void recordStart() {
|
||||
if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
updateMenuItems(false);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordPause() {
|
||||
updateMenuItems(true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStop() {
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this, true);
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
@@ -382,7 +360,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
case 0:
|
||||
return StatisticsRecordingFragment.newInstance();
|
||||
case 1:
|
||||
return IntervalsFragment.IntervalsRecordingFragment.newInstance();
|
||||
return IntervalsFragment.newInstance(false);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 3:
|
||||
@@ -412,4 +390,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
|
||||
trackDataHub.setRecordingStatus(recordingStatus);
|
||||
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class ExecutorListViewService<T> {
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
Future future = executorService.submit(runnable);
|
||||
Future<?> future = executorService.submit(runnable);
|
||||
try {
|
||||
future.get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
|
||||
@@ -4,73 +4,87 @@ import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||
|
||||
public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.Interval> {
|
||||
public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<IntervalStatistics.Interval> intervalList;
|
||||
private final Context context;
|
||||
private final StackMode stackMode;
|
||||
private final boolean metricUnits;
|
||||
private final String category;
|
||||
private boolean metricUnits;
|
||||
private boolean isReportSpeed;
|
||||
|
||||
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) {
|
||||
super(context, R.layout.interval_stats_list_item, intervalList);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
|
||||
this.category = category;
|
||||
public IntervalStatisticsAdapter(Context context, StackMode stackMode, boolean metricUnits, boolean isReportSpeed) {
|
||||
this.metricUnits = metricUnits;
|
||||
this.context = context;
|
||||
this.stackMode = stackMode;
|
||||
this.isReportSpeed = isReportSpeed;
|
||||
}
|
||||
|
||||
//TODO Check preference handling! Should not be accessed in getView()
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View intervalView, @NonNull ViewGroup parent) {
|
||||
int actualPosition = stackMode == StackMode.STACK_FROM_TOP ? position : getCount() - 1 - position;
|
||||
IntervalStatistics.Interval interval = getItem(actualPosition);
|
||||
ViewHolder viewHolder;
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.interval_stats_list_item, parent, false);
|
||||
return new IntervalStatisticsAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
if (intervalView == null) {
|
||||
viewHolder = new ViewHolder();
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
int actualPosition = stackMode == StackMode.STACK_FROM_TOP ? position : getItemCount() - 1 - position;
|
||||
int nextPosition = actualPosition + 1;
|
||||
boolean isLast = actualPosition == getItemCount() - 1;
|
||||
IntervalStatisticsAdapter.ViewHolder viewHolder = (IntervalStatisticsAdapter.ViewHolder) holder;
|
||||
IntervalStatistics.Interval interval = intervalList.get(actualPosition);
|
||||
viewHolder.itemView.setTag(actualPosition);
|
||||
|
||||
intervalView = LayoutInflater.from(getContext()).inflate(R.layout.interval_stats_list_item, parent, false);
|
||||
|
||||
viewHolder.distance = intervalView.findViewById(R.id.interval_item_distance);
|
||||
viewHolder.rate = intervalView.findViewById(R.id.interval_item_rate);
|
||||
viewHolder.gain = intervalView.findViewById(R.id.interval_item_gain);
|
||||
viewHolder.loss = intervalView.findViewById(R.id.interval_item_loss);
|
||||
|
||||
intervalView.setTag(viewHolder);
|
||||
Distance sumDistance;
|
||||
if (isLast && actualPosition > 0) {
|
||||
sumDistance = intervalList.get(actualPosition - 1).getDistance()
|
||||
.multipliedBy(actualPosition)
|
||||
.plus(interval.getDistance());
|
||||
} else {
|
||||
viewHolder = (ViewHolder) intervalView.getTag();
|
||||
sumDistance = interval.getDistance().multipliedBy(nextPosition);
|
||||
}
|
||||
viewHolder.distance.setText(StringUtils.formatDistance(context, sumDistance, metricUnits));
|
||||
|
||||
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed(), metricUnits, isReportSpeed));
|
||||
|
||||
viewHolder.gain.setText(StringUtils.formatDistance(context, Distance.of(interval.getGain_m()), metricUnits));
|
||||
viewHolder.loss.setText(StringUtils.formatDistance(context, Distance.of(interval.getLoss_m()), metricUnits));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (intervalList == null) {
|
||||
return 0;
|
||||
}
|
||||
return intervalList.size();
|
||||
}
|
||||
|
||||
public List<IntervalStatistics.Interval> swapData(List<IntervalStatistics.Interval> data, boolean metricUnits, boolean isReportSpeed) {
|
||||
if (intervalList == data && this.metricUnits == metricUnits && this.isReportSpeed == isReportSpeed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
float sumDistance_m;
|
||||
if (actualPosition + 1 == getCount() && actualPosition > 0) {
|
||||
sumDistance_m = actualPosition * getItem(actualPosition - 1).getDistance_m() + interval.getDistance_m();
|
||||
} else {
|
||||
sumDistance_m = (actualPosition + 1) * interval.getDistance_m();
|
||||
}
|
||||
viewHolder.distance.setText(StringUtils.formatDistance(getContext(), sumDistance_m, metricUnits));
|
||||
this.metricUnits = metricUnits;
|
||||
this.isReportSpeed = isReportSpeed;
|
||||
intervalList = data;
|
||||
|
||||
if (PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category)) {
|
||||
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, true));
|
||||
} else {
|
||||
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, false));
|
||||
if (data != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
viewHolder.gain.setText(StringUtils.formatDistance(getContext(), interval.getGain_m(), metricUnits));
|
||||
viewHolder.loss.setText(StringUtils.formatDistance(getContext(), interval.getLoss_m(), metricUnits));
|
||||
|
||||
return intervalView;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,10 +95,18 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
||||
STACK_FROM_TOP
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private TextView distance;
|
||||
private TextView rate;
|
||||
private TextView gain;
|
||||
private TextView loss;
|
||||
private static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView distance;
|
||||
TextView rate;
|
||||
TextView gain;
|
||||
TextView loss;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
distance = itemView.findViewById(R.id.interval_item_distance);
|
||||
rate = itemView.findViewById(R.id.interval_item_rate);
|
||||
gain = itemView.findViewById(R.id.interval_item_gain);
|
||||
loss = itemView.findViewById(R.id.interval_item_loss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
|
||||
private final Activity activity;
|
||||
|
||||
//TODO Should be Marker.Id
|
||||
private final ExecutorListViewService<Long> executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||
private final ExecutorListViewService<Long> executorService = new ExecutorListViewService<>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||
|
||||
private boolean scroll = false;
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ package de.dennisguse.opentracks.chart;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class ChartPoint {
|
||||
@@ -13,51 +14,38 @@ public class ChartPoint {
|
||||
private double timeOrDistance;
|
||||
|
||||
//Y-axis
|
||||
private double elevation;
|
||||
private double speed;
|
||||
private double pace;
|
||||
private double heartRate = Double.NaN;
|
||||
private double cadence = Double.NaN;
|
||||
private double power = Double.NaN;
|
||||
private final double altitude;
|
||||
private Double speed;
|
||||
private Double pace;
|
||||
private Double heartRate;
|
||||
private Double cadence;
|
||||
private Double power;
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
ChartPoint(double elevation) {
|
||||
this.elevation = elevation;
|
||||
ChartPoint(double altitude) {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, TrackPoint trackPoint, boolean chartByDistance, boolean metricUnits) {
|
||||
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
|
||||
|
||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, double smoothedAltitude_m, boolean chartByDistance, boolean metricUnits) {
|
||||
if (chartByDistance) {
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
timeOrDistance = distance;
|
||||
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(metricUnits);
|
||||
} else {
|
||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||
}
|
||||
|
||||
elevation = trackStatisticsUpdater.getSmoothedElevation();
|
||||
if (!metricUnits) {
|
||||
elevation *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
altitude = Distance.of(smoothedAltitude_m).toM_FT(metricUnits);
|
||||
|
||||
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MPS_TO_KMH;
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
speed = smoothedSpeed.to(metricUnits);
|
||||
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRate = (double) trackPoint.getHeartRate_bpm();
|
||||
}
|
||||
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||
if (trackPoint != null) {
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRate = trackPoint.getHeartRate_bpm();
|
||||
}
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
cadence = trackPoint.getCyclingCadence_rpm();
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
power = trackPoint.getPower();
|
||||
}
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
cadence = (double) trackPoint.getCyclingCadence_rpm();
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
power = (double) trackPoint.getPower();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,39 +53,32 @@ public class ChartPoint {
|
||||
return timeOrDistance;
|
||||
}
|
||||
|
||||
public double getElevation() {
|
||||
return elevation;
|
||||
public Double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
public double getSpeed() {
|
||||
public Double getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public double getPace() {
|
||||
public Double getPace() {
|
||||
return pace;
|
||||
}
|
||||
|
||||
public boolean hasHeartRate() {
|
||||
return Double.isNaN(heartRate);
|
||||
}
|
||||
|
||||
public double getHeartRate() {
|
||||
public Double getHeartRate() {
|
||||
return heartRate;
|
||||
}
|
||||
|
||||
public boolean hasCadence() {
|
||||
return Double.isNaN(cadence);
|
||||
}
|
||||
|
||||
public double getCadence() {
|
||||
public Double getCadence() {
|
||||
return cadence;
|
||||
}
|
||||
|
||||
public boolean hasPower() {
|
||||
return Double.isNaN(power);
|
||||
}
|
||||
|
||||
public double getPower() {
|
||||
public Double getPower() {
|
||||
return power;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChartPoint{" + "timeOrDistance=" + timeOrDistance + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +132,10 @@ abstract class ChartValueSeries {
|
||||
}
|
||||
}
|
||||
|
||||
abstract double extractDataFromChartPoint(@NonNull ChartPoint chartPoint);
|
||||
abstract Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint);
|
||||
|
||||
boolean isChartPointValid(@NonNull ChartPoint chartPoint) {
|
||||
return !Double.isNaN(extractDataFromChartPoint(chartPoint));
|
||||
return extractDataFromChartPoint(chartPoint) != null;
|
||||
}
|
||||
|
||||
protected abstract boolean drawIfChartPointHasNoData();
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.core.view.GestureDetectorCompat;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.MarkerDetailActivity;
|
||||
@@ -50,7 +51,6 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.ThemeUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* Visualization of the chart.
|
||||
@@ -199,15 +199,15 @@ public class ChartView extends View {
|
||||
Integer.MIN_VALUE,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
|
||||
R.string.description_elevation_metric,
|
||||
R.string.description_elevation_imperial,
|
||||
R.color.chart_elevation_fill,
|
||||
R.color.chart_elevation_border,
|
||||
R.string.description_altitude_metric,
|
||||
R.string.description_altitude_imperial,
|
||||
R.color.chart_altitude_fill,
|
||||
R.color.chart_altitude_border,
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getElevation();
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getAltitude();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,7 +227,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getSpeed();
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getPace();
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getHeartRate();
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getCadence();
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getPower();
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
updateDimensions();
|
||||
updatePaths();
|
||||
updateSeries();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,7 +471,7 @@ public class ChartView extends View {
|
||||
private void zoomIn() {
|
||||
if (canZoomIn()) {
|
||||
zoomLevel++;
|
||||
updatePaths();
|
||||
updateSeries();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -486,7 +486,7 @@ public class ChartView extends View {
|
||||
scrollX = maxWidth;
|
||||
scrollTo(scrollX, 0);
|
||||
}
|
||||
updatePaths();
|
||||
updateSeries();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -853,75 +853,49 @@ public class ChartView extends View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates paths.
|
||||
* The path needs to be updated any time after the data or the dimensions change.
|
||||
*/
|
||||
private void updatePaths() {
|
||||
private void updateSeries() {
|
||||
synchronized (chartPoints) {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
chartValueSeries.getPath().reset();
|
||||
}
|
||||
drawPaths();
|
||||
closePaths();
|
||||
seriesList.stream().forEach(this::updateSerie);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawPaths() {
|
||||
boolean[] hasMoved = new boolean[seriesList.size()];
|
||||
private void updateSerie(ChartValueSeries series) {
|
||||
final int yCorner = topBorder + effectiveHeight;
|
||||
final Path path = series.getPath();
|
||||
|
||||
for (ChartPoint dataPoint : chartPoints) {
|
||||
for (int i = 0; i < seriesList.size(); i++) {
|
||||
ChartValueSeries chartValueSeries = seriesList.get(i);
|
||||
boolean drawFirstPoint = false;
|
||||
path.rewind();
|
||||
|
||||
if (!chartValueSeries.isChartPointValid(dataPoint)) {
|
||||
continue;
|
||||
}
|
||||
Iterator<ChartPoint> iterator = chartPoints.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ChartPoint point = iterator.next();
|
||||
if (!series.isChartPointValid(point)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double value = chartValueSeries.extractDataFromChartPoint(dataPoint);
|
||||
Path path = chartValueSeries.getPath();
|
||||
int x = getX(dataPoint.getTimeOrDistance());
|
||||
int y = getY(chartValueSeries, value);
|
||||
if (!hasMoved[i]) {
|
||||
hasMoved[i] = true;
|
||||
path.moveTo(x, y);
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
double value = series.extractDataFromChartPoint(point);
|
||||
int x = getX(point.getTimeOrDistance());
|
||||
int y = getY(series, value);
|
||||
|
||||
// start from lower left corner
|
||||
if (!drawFirstPoint) {
|
||||
path.moveTo(x, yCorner);
|
||||
drawFirstPoint = true;
|
||||
}
|
||||
|
||||
// draw graph
|
||||
path.lineTo(x, y);
|
||||
|
||||
// last point: move to lower right
|
||||
if (!iterator.hasNext()) {
|
||||
path.lineTo(x, yCorner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void closePaths() {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
int first = getFirstPopulatedChartDataIndex(chartValueSeries);
|
||||
|
||||
if (first != -1) {
|
||||
int xCorner = getX(chartPoints.get(first).getTimeOrDistance());
|
||||
int yCorner = topBorder + effectiveHeight;
|
||||
Path path = chartValueSeries.getPath();
|
||||
// Bottom right corner
|
||||
path.lineTo(getX(chartPoints.get(chartPoints.size() - 1).getTimeOrDistance()), yCorner);
|
||||
// Bottom left corner
|
||||
path.lineTo(xCorner, yCorner);
|
||||
// Top right corner
|
||||
double value = chartValueSeries.extractDataFromChartPoint(chartPoints.get(first));
|
||||
path.lineTo(xCorner, getY(chartValueSeries, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the first data point containing data for a series.
|
||||
*
|
||||
* @return -1 if no data point contains data for the series.
|
||||
*/
|
||||
private int getFirstPopulatedChartDataIndex(ChartValueSeries chartValueSeries) {
|
||||
for (int i = 0; i < chartPoints.size(); i++) {
|
||||
if (chartValueSeries.isChartPointValid(chartPoints.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
// back to lower left corner
|
||||
path.close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -973,7 +947,7 @@ public class ChartView extends View {
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
updateEffectiveDimensions();
|
||||
updatePaths();
|
||||
updateSeries();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,10 +979,9 @@ public class ChartView extends View {
|
||||
|
||||
private double getMarkerXValue(Marker marker) {
|
||||
if (chartByDistance) {
|
||||
double length_km = marker.getLength() * UnitConversions.M_TO_KM;
|
||||
return metricUnits ? length_km : length_km * UnitConversions.KM_TO_MI;
|
||||
return marker.getLength().toKM_Miles(metricUnits);
|
||||
} else {
|
||||
return marker.getDuration();
|
||||
return marker.getDuration().toMillis();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import androidx.annotation.VisibleForTesting;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
@@ -127,24 +129,24 @@ public class DescriptionGenerator {
|
||||
// Fastest pace
|
||||
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
|
||||
|
||||
// Max elevation
|
||||
if (stats.hasElevationMax()) {
|
||||
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
|
||||
// Max altitude
|
||||
if (stats.hasAltitudeMax()) {
|
||||
writeAltitude(stats.getMaxAltitude(), builder, R.string.description_max_altitude, lineBreak);
|
||||
}
|
||||
|
||||
// Min elevation
|
||||
if (stats.hasElevationMin()) {
|
||||
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
|
||||
// Min altitude
|
||||
if (stats.hasAltitudeMin()) {
|
||||
writeAltitude(stats.getMinAltitude(), builder, R.string.description_min_altitude, lineBreak);
|
||||
}
|
||||
|
||||
// Elevation gain
|
||||
if (stats.hasTotalElevationGain()) {
|
||||
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||
// Altitude gain
|
||||
if (stats.hasTotalAltitudeGain()) {
|
||||
writeAltitude(stats.getTotalAltitudeGain(), builder, R.string.description_altitude_gain, lineBreak);
|
||||
}
|
||||
|
||||
// Elevation loss
|
||||
if (stats.hasTotalElevationLoss()) {
|
||||
writeElevation(stats.getTotalElevationLoss(), builder, R.string.description_elevation_loss, lineBreak);
|
||||
// Altitude loss
|
||||
if (stats.hasTotalAltitudeLoss()) {
|
||||
writeAltitude(stats.getTotalAltitudeLoss(), builder, R.string.description_altitude_loss, lineBreak);
|
||||
}
|
||||
|
||||
// Recorded time
|
||||
@@ -163,9 +165,9 @@ public class DescriptionGenerator {
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) {
|
||||
double distanceInKm = distance * UnitConversions.M_TO_KM;
|
||||
double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI;
|
||||
void writeDistance(Distance distance, StringBuilder builder, int resId, String lineBreak) {
|
||||
double distanceInKm = distance.toKM();
|
||||
double distanceInMi = distance.toMI();
|
||||
builder.append(context.getString(resId, distanceInKm, distanceInMi));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
@@ -193,23 +195,18 @@ public class DescriptionGenerator {
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
double speedInKmHr = speed * UnitConversions.MPS_TO_KMH;
|
||||
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
|
||||
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
|
||||
void writeSpeed(Speed speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
builder.append(context.getString(resId, speed.toKMH(), speed.toMPH()));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes pace.
|
||||
*
|
||||
* @param speed speed in meters per second
|
||||
* @param builder StringBuilder to append pace
|
||||
* @param resId resource id of pace string
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
void writePace(Speed speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
|
||||
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
|
||||
|
||||
@@ -221,18 +218,16 @@ public class DescriptionGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes elevation.
|
||||
*
|
||||
* @param elevation elevation in meters
|
||||
* @param builder StringBuilder to append elevation
|
||||
* @param resId resource id of elevation string
|
||||
* @param lineBreak line break string
|
||||
* @param altitude_m altitude_m in meters
|
||||
* @param builder StringBuilder to append
|
||||
* @param resId resource id of altitude string
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeElevation(double elevation, StringBuilder builder, int resId, String lineBreak) {
|
||||
long elevationInM = Math.round(elevation);
|
||||
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
|
||||
builder.append(context.getString(resId, elevationInM, elevationInFt));
|
||||
void writeAltitude(double altitude_m, StringBuilder builder, int resId, String lineBreak) {
|
||||
long altitudeInM = Math.round(altitude_m);
|
||||
long altitudeInFt = Math.round(altitude_m * UnitConversions.M_TO_FT);
|
||||
builder.append(context.getString(resId, altitudeInM, altitudeInFt));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.os.Handler;
|
||||
@@ -28,10 +27,12 @@ import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -40,7 +41,9 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.EGM2008Utils;
|
||||
|
||||
/**
|
||||
* Track data hub.
|
||||
@@ -50,7 +53,8 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
//TODO register contentobserver only for exact URL (incl. trackId) to not filter here.
|
||||
public class TrackDataHub {
|
||||
|
||||
/**
|
||||
* Target number of track points displayed by the diagrams (recommended).
|
||||
@@ -71,21 +75,23 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int targetNumPoints;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private EGM2008Utils.EGM2008Correction egm2008Correction;
|
||||
|
||||
private boolean started;
|
||||
private HandlerThread handlerThread;
|
||||
private Handler handler;
|
||||
|
||||
// Preference values
|
||||
private Track.Id selectedTrackId;
|
||||
private Track.Id recordingTrackId;
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
private Distance recordingDistanceInterval;
|
||||
|
||||
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
|
||||
// Track points sampling state
|
||||
private int numLoadedPoints;
|
||||
private TrackPoint.Id firstSeenTrackPointId;
|
||||
private TrackPoint.Id lastSeenTrackPointId;
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
|
||||
// Registered listeners
|
||||
private ContentObserver tracksTableObserver;
|
||||
@@ -140,17 +146,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
||||
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
handler.post(() -> {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@@ -159,9 +154,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
return;
|
||||
}
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||
sharedPreferences = null;
|
||||
|
||||
started = false;
|
||||
|
||||
//Unregister listeners
|
||||
@@ -175,6 +167,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
handlerThread = null;
|
||||
}
|
||||
handler = null;
|
||||
trackStatisticsUpdater = null;
|
||||
}
|
||||
|
||||
public void loadTrack(final @NonNull Track.Id trackId) {
|
||||
@@ -215,26 +208,14 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
* Returns true if the selected track is recording.
|
||||
*/
|
||||
public boolean isSelectedTrackRecording() {
|
||||
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && PreferencesUtils.isRecording(recordingTrackId);
|
||||
return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the selected track is paused.
|
||||
*/
|
||||
public boolean isSelectedTrackPaused() {
|
||||
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && recordingTrackPaused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||
handler.post(() -> {
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context);
|
||||
}
|
||||
});
|
||||
return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId()) && recordingStatus.isPaused();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,6 +363,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
break;
|
||||
}
|
||||
|
||||
correctAltitude(trackPoint);
|
||||
|
||||
if (localFirstSeenTrackPointId == null) {
|
||||
localFirstSeenTrackPointId = trackPointId;
|
||||
}
|
||||
@@ -391,15 +374,16 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||
}
|
||||
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint);
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint, trackStatisticsUpdater.getTrackStatistics(), trackStatisticsUpdater.getSmoothedSpeed(), trackStatisticsUpdater.getSmoothedAltitude());
|
||||
}
|
||||
} else {
|
||||
for (TrackDataListener trackDataListener : sampledOutListeners) {
|
||||
trackDataListener.onSampledOutTrackPoint(trackPoint);
|
||||
trackDataListener.onSampledOutTrackPoint(trackPoint, trackStatisticsUpdater.getTrackStatistics());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,11 +403,29 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
|
||||
if (trackPoint != null) {
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
listener.onNewTrackPointsDone(trackPoint);
|
||||
listener.onNewTrackPointsDone(trackPoint, trackStatisticsUpdater.getTrackStatistics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void correctAltitude(TrackPoint trackPoint) {
|
||||
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
|
||||
Log.d(TAG, "No altitude correction necessary.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (egm2008Correction == null || !egm2008Correction.canCorrect(trackPoint.getLocation())) {
|
||||
try {
|
||||
egm2008Correction = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Could not load altitude correction for " + trackPoint, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
trackPoint.setAltitude(Altitude.EGM2008.of(egm2008Correction.correctAltitude(trackPoint.getLocation())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the track points sampling states.
|
||||
*/
|
||||
@@ -431,5 +433,14 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
numLoadedPoints = 0;
|
||||
firstSeenTrackPointId = null;
|
||||
lastSeenTrackPointId = null;
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
}
|
||||
|
||||
public void setRecordingStatus(TrackRecordingService.RecordingStatus recordingStatus) {
|
||||
this.recordingStatus = recordingStatus;
|
||||
}
|
||||
|
||||
public void setRecordingDistanceInterval(Distance recordingDistanceInterval) {
|
||||
this.recordingDistanceInterval = recordingDistanceInterval;
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@ package de.dennisguse.opentracks.content;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* Listener for track data changes.
|
||||
@@ -47,20 +49,23 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledInTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, double smoothedAltitude_m) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a sampled out track point is read.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
default void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when finish sending new track points.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint)} and {@link #onSampledOutTrackPoint(TrackPoint)}.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint, TrackStatistics, Speed, double)} and {@link #onSampledOutTrackPoint(TrackPoint, TrackStatistics)}.
|
||||
*/
|
||||
void onNewTrackPointsDone(@NonNull TrackPoint lastTrackPoint);
|
||||
default void onNewTrackPointsDone(@NonNull TrackPoint lastTrackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to clear previously sent markers.
|
||||
|
||||
@@ -41,10 +41,10 @@ class TrackDataManager {
|
||||
}
|
||||
|
||||
void unregisterTrackDataListener(TrackDataListener trackDataListener) {
|
||||
dataListenerTracks.add(trackDataListener);
|
||||
dataListenerMarkers.add(trackDataListener);
|
||||
dataListenerTrackPoints_SampledIn.add(trackDataListener);
|
||||
dataListenerTrackPoints_SampledOut.add(trackDataListener);
|
||||
dataListenerTracks.remove(trackDataListener);
|
||||
dataListenerMarkers.remove(trackDataListener);
|
||||
dataListenerTrackPoints_SampledIn.remove(trackDataListener);
|
||||
dataListenerTrackPoints_SampledOut.remove(trackDataListener);
|
||||
}
|
||||
|
||||
boolean hasListeners() {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
public abstract class Altitude {
|
||||
|
||||
private final double altitude_m;
|
||||
|
||||
private Altitude(double altitude_m) {
|
||||
this.altitude_m = altitude_m;
|
||||
}
|
||||
|
||||
public double toM() {
|
||||
return altitude_m;
|
||||
}
|
||||
|
||||
public static class WGS84 extends Altitude {
|
||||
|
||||
private WGS84(double altitude_m) {
|
||||
super(altitude_m);
|
||||
}
|
||||
|
||||
|
||||
public static Altitude of(double altitude_m) {
|
||||
return new WGS84(altitude_m);
|
||||
}
|
||||
}
|
||||
|
||||
public static class EGM2008 extends Altitude {
|
||||
|
||||
private EGM2008(double altitude_m) {
|
||||
super(altitude_m);
|
||||
}
|
||||
|
||||
|
||||
public static Altitude of(double altitude_m) {
|
||||
return new EGM2008(altitude_m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class Distance {
|
||||
|
||||
public static Distance of(double distance_m) {
|
||||
return new Distance(distance_m);
|
||||
}
|
||||
|
||||
public static Distance of(String distance_m) {
|
||||
return of(Float.parseFloat(distance_m));
|
||||
}
|
||||
|
||||
public static Distance ofMile(double distance_mile) {
|
||||
return of(distance_mile * UnitConversions.MI_TO_M);
|
||||
}
|
||||
|
||||
public static Distance ofKilometer(double distance_km) {
|
||||
return of(distance_km * UnitConversions.KM_TO_M);
|
||||
}
|
||||
|
||||
public static Distance ofMM(double distance_mm) {
|
||||
return of(distance_mm * UnitConversions.MM_TO_M);
|
||||
}
|
||||
|
||||
public static Distance one(boolean metricUnit) {
|
||||
if (metricUnit) {
|
||||
return Distance.ofKilometer(1);
|
||||
} else {
|
||||
return Distance.ofMile(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static Distance invalid() {
|
||||
return of(Double.NaN);
|
||||
}
|
||||
|
||||
private final double distance_m;
|
||||
|
||||
private Distance(double distance_m) {
|
||||
this.distance_m = distance_m;
|
||||
}
|
||||
|
||||
public Distance plus(@NonNull Distance distance) {
|
||||
return new Distance(distance_m + distance.distance_m);
|
||||
}
|
||||
|
||||
public Distance minus(@NonNull Distance distance) {
|
||||
return new Distance(distance_m - distance.distance_m);
|
||||
}
|
||||
|
||||
public Distance multipliedBy(double factor) {
|
||||
return new Distance(factor * distance_m);
|
||||
}
|
||||
|
||||
public Distance dividedBy(double divisor) {
|
||||
return multipliedBy(1 / divisor);
|
||||
}
|
||||
|
||||
public double dividedBy(@NonNull Distance divisor) {
|
||||
return distance_m / divisor.distance_m;
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return distance_m == 0;
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
return Double.isNaN(distance_m) || Double.isInfinite(distance_m);
|
||||
}
|
||||
|
||||
public boolean lessThan(@NonNull Distance distance) {
|
||||
return !greaterThan(distance);
|
||||
}
|
||||
|
||||
public boolean greaterThan(@NonNull Distance distance) {
|
||||
return distance_m > distance.distance_m;
|
||||
}
|
||||
|
||||
public boolean greaterOrEqualThan(@NonNull Distance distance) {
|
||||
return distance_m >= distance.distance_m;
|
||||
}
|
||||
|
||||
public double toM() {
|
||||
return distance_m;
|
||||
}
|
||||
|
||||
public double toKM() {
|
||||
return distance_m * UnitConversions.M_TO_KM;
|
||||
}
|
||||
|
||||
public double toFT() {
|
||||
return distance_m * UnitConversions.M_TO_FT;
|
||||
}
|
||||
|
||||
public double toMI() {
|
||||
return toKM() * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public double toKM_Miles(boolean metricUnit) {
|
||||
return metricUnit ? toKM() : toMI();
|
||||
}
|
||||
|
||||
public double toM_FT(boolean metricUnit) {
|
||||
return metricUnit ? toM() : toFT();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Distance distance = (Distance) o;
|
||||
return Double.compare(distance.distance_m, distance_m) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(distance_m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Distance{" +
|
||||
"distance_m=" + distance_m +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,19 @@ import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//TODO All data should be final; no default values.
|
||||
public final class Marker {
|
||||
|
||||
private Id id;
|
||||
@@ -46,12 +50,12 @@ public final class Marker {
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Float accuracy;
|
||||
private Double altitude_m;
|
||||
private Altitude altitude;
|
||||
private Float bearing;
|
||||
|
||||
//TODO It is the distance from the track starting point; rename to something more meaningful
|
||||
private double length = 0.0;
|
||||
private long duration = 0; //TODO Duration
|
||||
private Distance length;
|
||||
private Duration duration;
|
||||
|
||||
@Deprecated //TODO Make an URI instead of String
|
||||
private String photoUrl = "";
|
||||
@@ -61,6 +65,7 @@ public final class Marker {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Deprecated //TODO Marker cannot be created without length AND duration!
|
||||
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||
this.trackId = trackId;
|
||||
|
||||
@@ -72,18 +77,21 @@ public final class Marker {
|
||||
this.latitude = trackPoint.getLatitude();
|
||||
this.longitude = trackPoint.getLongitude();
|
||||
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
||||
if (trackPoint.hasAltitude()) this.altitude_m = trackPoint.getAltitude();
|
||||
if (trackPoint.hasAltitude()) this.altitude = trackPoint.getAltitude();
|
||||
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
||||
|
||||
this.length = Distance.of(0); //TODO Not cool!
|
||||
this.duration = Duration.ofMillis(0); //TODO Not cool!
|
||||
}
|
||||
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, long duration, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackStatistics statistics, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
this(trackId, trackPoint);
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
this.icon = icon;
|
||||
this.length = length;
|
||||
this.duration = duration;
|
||||
this.length = statistics.getTotalDistance();
|
||||
this.duration = statistics.getTotalTime();
|
||||
this.photoUrl = photoUrl;
|
||||
}
|
||||
|
||||
@@ -159,7 +167,7 @@ public final class Marker {
|
||||
location.setAccuracy(accuracy);
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude_m);
|
||||
location.setAltitude(altitude.toM());
|
||||
}
|
||||
|
||||
return location;
|
||||
@@ -194,15 +202,15 @@ public final class Marker {
|
||||
}
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return altitude_m != null;
|
||||
return altitude != null;
|
||||
}
|
||||
|
||||
public Double getAltitude() {
|
||||
return altitude_m;
|
||||
public Altitude getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
public void setAltitude(double altitude_m) {
|
||||
this.altitude_m = altitude_m;
|
||||
public void setAltitude(Altitude altitude) {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
@@ -217,19 +225,19 @@ public final class Marker {
|
||||
this.bearing = bearing;
|
||||
}
|
||||
|
||||
public double getLength() {
|
||||
public Distance getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(double length) {
|
||||
public void setLength(Distance length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
public Duration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
public void setDuration(@NonNull Duration duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public interface MarkerColumns extends BaseColumns {
|
||||
String LONGITUDE = "longitude"; // longitude
|
||||
String LATITUDE = "latitude"; // latitude
|
||||
String TIME = "time"; // time
|
||||
String ALTITUDE = "elevation"; // altitude
|
||||
String ALTITUDE = "elevation"; // altitude //TODO RENAME column
|
||||
String ACCURACY = "accuracy"; // accuracy
|
||||
String BEARING = "bearing"; // bearing
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class Speed {
|
||||
|
||||
public static Speed of(Distance distance, Duration duration) {
|
||||
if (duration.isZero()) {
|
||||
return zero();
|
||||
}
|
||||
|
||||
return new Speed(distance.toM() / (duration.toMillis() * UnitConversions.MS_TO_S));
|
||||
}
|
||||
|
||||
public static Speed of(double speed_mps) {
|
||||
return new Speed(speed_mps);
|
||||
}
|
||||
|
||||
public static Speed of(String speed_mps) {
|
||||
return of(Float.parseFloat(speed_mps));
|
||||
}
|
||||
|
||||
public static Speed invalid() {
|
||||
return of(Double.NaN);
|
||||
}
|
||||
|
||||
public static Speed zero() {
|
||||
return of(0.0);
|
||||
}
|
||||
|
||||
public static Speed max(Speed speed1, Speed speed2) {
|
||||
if (speed1.greaterThan(speed2)) {
|
||||
return speed1;
|
||||
}
|
||||
|
||||
return speed2;
|
||||
}
|
||||
|
||||
public static Speed absDiff(Speed speed1, Speed speed2) {
|
||||
//TODO Why Math.abs? Seems to be a leftover.
|
||||
return Speed.of(Math.abs(speed1.speed_mps - speed2.speed_mps));
|
||||
}
|
||||
|
||||
|
||||
// Anything faster than that (in meters per second) will be considered moving.
|
||||
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||
|
||||
private final double speed_mps;
|
||||
|
||||
private Speed(double speed_mps) {
|
||||
this.speed_mps = speed_mps;
|
||||
}
|
||||
|
||||
public Speed mul(double factor) {
|
||||
return new Speed(factor * speed_mps);
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return speed_mps == 0;
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
return Double.isNaN(speed_mps) || Double.isInfinite(speed_mps);
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return !isInvalid() && speed_mps >= MAX_NO_MOVEMENT_SPEED;
|
||||
}
|
||||
|
||||
public boolean lessThan(Speed speed) {
|
||||
return !greaterThan(speed);
|
||||
}
|
||||
|
||||
public boolean greaterThan(Speed speed) {
|
||||
return speed_mps > speed.speed_mps;
|
||||
}
|
||||
|
||||
public boolean greaterOrEqualThan(Speed speed) {
|
||||
return speed_mps >= speed.speed_mps;
|
||||
}
|
||||
|
||||
public double toMPS() {
|
||||
return speed_mps;
|
||||
}
|
||||
|
||||
public double toKMH() {
|
||||
return speed_mps * UnitConversions.MPS_TO_KMH;
|
||||
}
|
||||
|
||||
public double toMPH() {
|
||||
return toKMH() * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public Duration toPace(boolean metricUnit) {
|
||||
if (isZero()) {
|
||||
return Duration.ofSeconds(0);
|
||||
}
|
||||
|
||||
double distance = speed_mps * (metricUnit ? UnitConversions.M_TO_KM : UnitConversions.M_TO_MI);
|
||||
return Duration.ofSeconds(Math.round(1 / distance));
|
||||
}
|
||||
|
||||
public double to(boolean metricUnit) {
|
||||
return to(metricUnit ? Unit.KMH : Unit.MPH);
|
||||
}
|
||||
|
||||
public double to(Unit unit) {
|
||||
switch (unit) {
|
||||
case KMH:
|
||||
return toKMH();
|
||||
case MPH:
|
||||
return toMPH();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public enum Unit {
|
||||
KMH,
|
||||
MPH,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Speed speed = (Speed) o;
|
||||
return Double.compare(speed.speed_mps, speed_mps) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(speed_mps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Speed{" +
|
||||
"speed_mps=" + speed_mps +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -101,11 +101,12 @@ public class Track {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
return trackStatistics;
|
||||
}
|
||||
|
||||
public void setTrackStatistics(TrackStatistics trackStatistics) {
|
||||
public void setTrackStatistics(@NonNull TrackStatistics trackStatistics) {
|
||||
this.trackStatistics = trackStatistics;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -45,19 +46,16 @@ public class TrackPoint {
|
||||
|
||||
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
||||
|
||||
// Anything faster than that (in meters per second) will be considered moving.
|
||||
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||
|
||||
private TrackPoint.Id id;
|
||||
|
||||
private Instant time;
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Float accuracy;
|
||||
private Double altitude_m;
|
||||
private Float speed_mps;
|
||||
private Altitude altitude;
|
||||
private Speed speed;
|
||||
private Float bearing;
|
||||
private Float sensorDistance_m;
|
||||
private Distance sensorDistance_m;
|
||||
|
||||
public enum Type {
|
||||
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
|
||||
@@ -93,8 +91,8 @@ public class TrackPoint {
|
||||
private Float heartRate_bpm = null;
|
||||
private Float cyclingCadence_rpm = null;
|
||||
private Float power = null;
|
||||
private Float elevationGain = null;
|
||||
private Float elevationLoss = null;
|
||||
private Float altitudeGain_m = null;
|
||||
private Float altitudeLoss_m = null;
|
||||
|
||||
public TrackPoint(@NonNull Type type) {
|
||||
this.type = type;
|
||||
@@ -105,8 +103,8 @@ public class TrackPoint {
|
||||
|
||||
this.latitude = location.getLatitude();
|
||||
this.longitude = location.getLongitude();
|
||||
this.altitude_m = location.getAltitude();
|
||||
this.speed_mps = location.getSpeed();
|
||||
this.altitude = Altitude.WGS84.of(location.getAltitude());
|
||||
this.speed = Speed.of(location.getSpeed());
|
||||
this.accuracy = location.getAccuracy();
|
||||
|
||||
setTime(Instant.now());
|
||||
@@ -117,11 +115,11 @@ public class TrackPoint {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public TrackPoint(double latitude, double longitude, Double altitude, Instant time) {
|
||||
public TrackPoint(double latitude, double longitude, Altitude altitude, Instant time) {
|
||||
this(Type.TRACKPOINT);
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude_m = altitude;
|
||||
this.altitude = altitude;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@@ -215,34 +213,34 @@ public class TrackPoint {
|
||||
location.setAccuracy(accuracy);
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude_m);
|
||||
location.setAltitude(altitude.toM());
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
public boolean hasElevationGain() {
|
||||
return elevationGain != null;
|
||||
public boolean hasAltitudeGain() {
|
||||
return altitudeGain_m != null;
|
||||
}
|
||||
|
||||
public float getElevationGain() {
|
||||
return elevationGain;
|
||||
public float getAltitudeGain() {
|
||||
return altitudeGain_m;
|
||||
}
|
||||
|
||||
public void setElevationGain(Float elevationGain) {
|
||||
this.elevationGain = elevationGain;
|
||||
public void setAltitudeGain(Float altitudeGain_m) {
|
||||
this.altitudeGain_m = altitudeGain_m;
|
||||
}
|
||||
|
||||
public boolean hasElevationLoss() {
|
||||
return elevationLoss != null;
|
||||
public boolean hasAltitudeLoss() {
|
||||
return altitudeLoss_m != null;
|
||||
}
|
||||
|
||||
public float getElevationLoss() {
|
||||
return elevationLoss;
|
||||
public float getAltitudeLoss() {
|
||||
return altitudeLoss_m;
|
||||
}
|
||||
|
||||
public void setElevationLoss(Float elevationLoss) {
|
||||
this.elevationLoss = elevationLoss;
|
||||
public void setAltitudeLoss(Float altitudeLoss_m) {
|
||||
this.altitudeLoss_m = altitudeLoss_m;
|
||||
}
|
||||
|
||||
public Instant getTime() {
|
||||
@@ -260,31 +258,36 @@ public class TrackPoint {
|
||||
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return altitude_m != null;
|
||||
return altitude != null;
|
||||
}
|
||||
|
||||
public double getAltitude() {
|
||||
return altitude_m;
|
||||
public Altitude getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
public void setAltitude(double altitude) {
|
||||
this.altitude_m = altitude;
|
||||
@VisibleForTesting
|
||||
public void setAltitude(double altitude_m) {
|
||||
this.altitude = Altitude.WGS84.of(altitude_m);
|
||||
}
|
||||
|
||||
public void setAltitude(Altitude altitude) {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
public boolean hasSpeed() {
|
||||
return speed_mps != null;
|
||||
return speed != null;
|
||||
}
|
||||
|
||||
public float getSpeed() {
|
||||
return speed_mps;
|
||||
public Speed getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public void setSpeed(Float speed) {
|
||||
this.speed_mps = speed;
|
||||
public void setSpeed(Speed speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return hasSpeed() && getSpeed() >= MAX_NO_MOVEMENT_SPEED;
|
||||
return hasSpeed() && getSpeed().isMoving();
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
@@ -311,12 +314,12 @@ public class TrackPoint {
|
||||
this.accuracy = horizontalAccuracy;
|
||||
}
|
||||
|
||||
public float distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
if (hasSensorDistance()) {
|
||||
return getSensorDistance();
|
||||
}
|
||||
|
||||
return getLocation().distanceTo(previous.getLocation());
|
||||
return Distance.of(getLocation().distanceTo(previous.getLocation()));
|
||||
}
|
||||
|
||||
public boolean fulfillsAccuracy(int poorAccuracy) {
|
||||
@@ -336,11 +339,11 @@ public class TrackPoint {
|
||||
return sensorDistance_m != null;
|
||||
}
|
||||
|
||||
public Float getSensorDistance() {
|
||||
public Distance getSensorDistance() {
|
||||
return sensorDistance_m;
|
||||
}
|
||||
|
||||
public void setSensorDistance(Float distance_m) {
|
||||
public void setSensorDistance(Distance distance_m) {
|
||||
this.sensorDistance_m = distance_m;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
String SENSOR_CADENCE = "sensor_cadence";
|
||||
String SENSOR_DISTANCE = "sensor_distance"; //DISTANCE from previous TrackPoint
|
||||
String SENSOR_POWER = "sensor_power";
|
||||
String ELEVATION_GAIN = "elevation_gain";
|
||||
String ELEVATION_LOSS = "elevation_loss";
|
||||
String ALTITUDE_GAIN = "elevation_gain";
|
||||
String ALTITUDE_LOSS = "elevation_loss";
|
||||
|
||||
// Alias for sensor statistics
|
||||
String ALIAS_AVG_HR = "avg_hr";
|
||||
@@ -75,8 +75,8 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
+ SENSOR_HEARTRATE + " FLOAT, "
|
||||
+ SENSOR_CADENCE + " FLOAT, "
|
||||
+ SENSOR_POWER + " FLOAT, "
|
||||
+ ELEVATION_GAIN + " FLOAT, "
|
||||
+ ELEVATION_LOSS + " FLOAT, "
|
||||
+ ALTITUDE_GAIN + " FLOAT, "
|
||||
+ ALTITUDE_LOSS + " FLOAT, "
|
||||
+ TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), "
|
||||
+ SENSOR_DISTANCE + " FLOAT, "
|
||||
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
|
||||
|
||||
@@ -52,10 +52,10 @@ public interface TracksColumns extends BaseColumns {
|
||||
String AVGSPEED = "avgspeed"; // average speed
|
||||
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
|
||||
String MAXSPEED = "maxspeed"; // maximum speed
|
||||
String MINELEVATION = "minelevation"; // minimum elevation
|
||||
String MAXELEVATION = "maxelevation"; // maximum elevation
|
||||
String ELEVATIONGAIN = "elevationgain"; // elevation gain
|
||||
String ELEVATIONLOSS = "elevationloss"; // elevation loss
|
||||
String MIN_ALTITUDE = "minelevation"; // minimum altitude //TODO RENAME column
|
||||
String MAX_ALTITUDE = "maxelevation"; // maximum altitude //TODO RENAME column
|
||||
String ALTITUDE_GAIN = "elevationgain"; // altitude gain //TODO RENAME column
|
||||
String ALTITUDE_LOSS = "elevationloss"; // altitude loss //TODO RENAME column
|
||||
String ICON = "icon"; // track activity type icon
|
||||
|
||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
@@ -72,12 +72,12 @@ public interface TracksColumns extends BaseColumns {
|
||||
+ AVGSPEED + " FLOAT, "
|
||||
+ AVGMOVINGSPEED + " FLOAT, "
|
||||
+ MAXSPEED + " FLOAT, "
|
||||
+ MINELEVATION + " FLOAT, "
|
||||
+ MAXELEVATION + " FLOAT, "
|
||||
+ ELEVATIONGAIN + " FLOAT, "
|
||||
+ MIN_ALTITUDE + " FLOAT, "
|
||||
+ MAX_ALTITUDE + " FLOAT, "
|
||||
+ ALTITUDE_GAIN + " FLOAT, "
|
||||
+ ICON + " TEXT, "
|
||||
+ UUID + " BLOB, "
|
||||
+ ELEVATIONLOSS + " FLOAT)";
|
||||
+ ALTITUDE_LOSS + " FLOAT)";
|
||||
|
||||
String CREATE_TABLE_INDEX = "CREATE UNIQUE INDEX " + TABLE_NAME + "_" + UUID + "_index ON " + TABLE_NAME + "(" + UUID + ")";
|
||||
|
||||
|
||||
+4
-4
@@ -21,8 +21,8 @@ class CachedTrackPointsIndexes {
|
||||
final int sensorCadenceIndex;
|
||||
final int sensorDistanceIndex;
|
||||
final int sensorPowerIndex;
|
||||
final int elevationGainIndex;
|
||||
final int elevationLossIndex;
|
||||
final int altitudeGainIndex;
|
||||
final int altitudeLossIndex;
|
||||
|
||||
CachedTrackPointsIndexes(Cursor cursor) {
|
||||
idIndex = cursor.getColumnIndex(TrackPointsColumns._ID);
|
||||
@@ -38,7 +38,7 @@ class CachedTrackPointsIndexes {
|
||||
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
|
||||
sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE);
|
||||
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
|
||||
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
|
||||
elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS);
|
||||
altitudeGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_GAIN);
|
||||
altitudeLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_LOSS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,11 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
@@ -58,7 +61,6 @@ import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
public class ContentProviderUtils {
|
||||
|
||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||
private static final int MAX_LATITUDE = 90000000;
|
||||
|
||||
// The authority (the first part of the URI) for the app's content provider.
|
||||
@VisibleForTesting
|
||||
@@ -97,10 +99,10 @@ public class ContentProviderUtils {
|
||||
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
|
||||
int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME);
|
||||
int maxSpeedIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED);
|
||||
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
|
||||
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
|
||||
int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
|
||||
int elevationLossIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONLOSS);
|
||||
int minAltitudeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MIN_ALTITUDE);
|
||||
int maxAltitudeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAX_ALTITUDE);
|
||||
int altitudeGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ALTITUDE_GAIN);
|
||||
int altitudeLossIndex = cursor.getColumnIndexOrThrow(TracksColumns.ALTITUDE_LOSS);
|
||||
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
|
||||
|
||||
Track track = new Track();
|
||||
@@ -127,7 +129,7 @@ public class ContentProviderUtils {
|
||||
trackStatistics.setStopTime(Instant.ofEpochMilli(cursor.getLong(stopTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(totalDistanceIndex)) {
|
||||
trackStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
|
||||
trackStatistics.setTotalDistance(Distance.of(cursor.getFloat(totalDistanceIndex)));
|
||||
}
|
||||
if (!cursor.isNull(totalTimeIndex)) {
|
||||
trackStatistics.setTotalTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
@@ -136,19 +138,19 @@ public class ContentProviderUtils {
|
||||
trackStatistics.setMovingTime(Duration.ofMillis(cursor.getLong(movingTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(maxSpeedIndex)) {
|
||||
trackStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex));
|
||||
trackStatistics.setMaxSpeed(Speed.of(cursor.getFloat(maxSpeedIndex)));
|
||||
}
|
||||
if (!cursor.isNull(minElevationIndex)) {
|
||||
trackStatistics.setMinElevation(cursor.getFloat(minElevationIndex));
|
||||
if (!cursor.isNull(minAltitudeIndex)) {
|
||||
trackStatistics.setMinAltitude(cursor.getFloat(minAltitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(maxElevationIndex)) {
|
||||
trackStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex));
|
||||
if (!cursor.isNull(maxAltitudeIndex)) {
|
||||
trackStatistics.setMaxAltitude(cursor.getFloat(maxAltitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(elevationGainIndex)) {
|
||||
trackStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
|
||||
if (!cursor.isNull(altitudeGainIndex)) {
|
||||
trackStatistics.setTotalAltitudeGain(cursor.getFloat(altitudeGainIndex));
|
||||
}
|
||||
if (!cursor.isNull(elevationLossIndex)) {
|
||||
trackStatistics.setTotalElevationLoss(cursor.getFloat(elevationLossIndex));
|
||||
if (!cursor.isNull(altitudeLossIndex)) {
|
||||
trackStatistics.setTotalAltitudeLoss(cursor.getFloat(altitudeLossIndex));
|
||||
}
|
||||
if (!cursor.isNull(iconIndex)) {
|
||||
track.setIcon(cursor.getString(iconIndex));
|
||||
@@ -158,8 +160,10 @@ public class ContentProviderUtils {
|
||||
|
||||
@VisibleForTesting
|
||||
public void deleteAllTracks(Context context) {
|
||||
//TODO Both calls should not be necessary
|
||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
|
||||
contentResolver.delete(MarkerColumns.CONTENT_URI, null, null);
|
||||
|
||||
// Delete tracks last since it triggers a database vaccum call
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||
|
||||
@@ -187,6 +191,7 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
//TODO Only use for tests; also move to tests.
|
||||
@VisibleForTesting
|
||||
public List<Track> getTracks() {
|
||||
ArrayList<Track> tracks = new ArrayList<>();
|
||||
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
|
||||
@@ -200,9 +205,6 @@ public class ContentProviderUtils {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trackId the track id.
|
||||
*/
|
||||
public Track getTrack(Track.Id trackId) {
|
||||
if (trackId == null) {
|
||||
return null;
|
||||
@@ -215,9 +217,6 @@ public class ContentProviderUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trackUUID the track uuid.
|
||||
*/
|
||||
public Track getTrack(@NonNull UUID trackUUID) {
|
||||
String trackUUIDsearch = UUIDUtils.toHex(trackUUID);
|
||||
try (Cursor cursor = getTrackCursor("hex(" + TracksColumns.UUID + ")=?", new String[]{trackUUIDsearch}, null)) {
|
||||
@@ -278,16 +277,16 @@ public class ContentProviderUtils {
|
||||
if (trackStatistics.getStopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli());
|
||||
}
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance());
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed());
|
||||
values.put(TracksColumns.MINELEVATION, trackStatistics.getMinElevation());
|
||||
values.put(TracksColumns.MAXELEVATION, trackStatistics.getMaxElevation());
|
||||
values.put(TracksColumns.ELEVATIONGAIN, trackStatistics.getTotalElevationGain());
|
||||
values.put(TracksColumns.ELEVATIONLOSS, trackStatistics.getTotalElevationLoss());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS());
|
||||
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
|
||||
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain());
|
||||
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.getTotalAltitudeLoss());
|
||||
values.put(TracksColumns.ICON, track.getIcon());
|
||||
|
||||
return values;
|
||||
@@ -318,7 +317,7 @@ public class ContentProviderUtils {
|
||||
marker.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6);
|
||||
}
|
||||
if (!cursor.isNull(altitudeIndex)) {
|
||||
marker.setAltitude(cursor.getFloat(altitudeIndex));
|
||||
marker.setAltitude(Altitude.WGS84.of(cursor.getFloat(altitudeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(accuracyIndex)) {
|
||||
marker.setAccuracy(cursor.getFloat(accuracyIndex));
|
||||
@@ -343,10 +342,10 @@ public class ContentProviderUtils {
|
||||
marker.setIcon(cursor.getString(iconIndex));
|
||||
}
|
||||
if (!cursor.isNull(lengthIndex)) {
|
||||
marker.setLength(cursor.getFloat(lengthIndex));
|
||||
marker.setLength(Distance.of(cursor.getFloat(lengthIndex)));
|
||||
}
|
||||
if (!cursor.isNull(durationIndex)) {
|
||||
marker.setDuration(cursor.getLong(durationIndex));
|
||||
marker.setDuration(Duration.ofMillis(cursor.getLong(durationIndex)));
|
||||
}
|
||||
|
||||
if (!cursor.isNull(photoUrlIndex)) {
|
||||
@@ -362,9 +361,9 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return -1 if not able to get the next marker number.
|
||||
* @return null if not able to get the next marker number.
|
||||
*/
|
||||
public int getNextMarkerNumber(@NonNull Track.Id trackId) {
|
||||
public Integer getNextMarkerNumber(@NonNull Track.Id trackId) {
|
||||
String[] projection = {MarkerColumns._ID};
|
||||
String selection = MarkerColumns.TRACKID + "=?";
|
||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||
@@ -373,7 +372,7 @@ public class ContentProviderUtils {
|
||||
return cursor.getCount();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
public Marker getMarker(@NonNull Marker.Id markerId) {
|
||||
@@ -480,14 +479,14 @@ public class ContentProviderUtils {
|
||||
values.put(MarkerColumns.CATEGORY, marker.getCategory());
|
||||
values.put(MarkerColumns.ICON, marker.getIcon());
|
||||
values.put(MarkerColumns.TRACKID, marker.getTrackId().getId());
|
||||
values.put(MarkerColumns.LENGTH, marker.getLength());
|
||||
values.put(MarkerColumns.DURATION, marker.getDuration());
|
||||
values.put(MarkerColumns.LENGTH, marker.getLength().toM());
|
||||
values.put(MarkerColumns.DURATION, marker.getDuration().toMillis());
|
||||
|
||||
values.put(MarkerColumns.LONGITUDE, (int) (marker.getLongitude() * 1E6));
|
||||
values.put(MarkerColumns.LATITUDE, (int) (marker.getLatitude() * 1E6));
|
||||
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
|
||||
if (marker.hasAltitude()) {
|
||||
values.put(MarkerColumns.ALTITUDE, marker.getAltitude());
|
||||
values.put(MarkerColumns.ALTITUDE, marker.getAltitude().toM());
|
||||
}
|
||||
if (marker.hasAccuracy()) {
|
||||
values.put(MarkerColumns.ACCURACY, marker.getAccuracy());
|
||||
@@ -537,13 +536,13 @@ public class ContentProviderUtils {
|
||||
trackPoint.setTime(Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.altitudeIndex)) {
|
||||
trackPoint.setAltitude(cursor.getFloat(indexes.altitudeIndex));
|
||||
trackPoint.setAltitude(Altitude.WGS84.of(cursor.getFloat(indexes.altitudeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.accuracyIndex)) {
|
||||
trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.speedIndex)) {
|
||||
trackPoint.setSpeed(cursor.getFloat(indexes.speedIndex));
|
||||
trackPoint.setSpeed(Speed.of(cursor.getFloat(indexes.speedIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.bearingIndex)) {
|
||||
trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex));
|
||||
@@ -556,17 +555,17 @@ public class ContentProviderUtils {
|
||||
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
|
||||
trackPoint.setSensorDistance(cursor.getFloat(indexes.sensorDistanceIndex));
|
||||
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.sensorPowerIndex)) {
|
||||
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
|
||||
}
|
||||
|
||||
if (!cursor.isNull(indexes.elevationGainIndex)) {
|
||||
trackPoint.setElevationGain(cursor.getFloat(indexes.elevationGainIndex));
|
||||
if (!cursor.isNull(indexes.altitudeGainIndex)) {
|
||||
trackPoint.setAltitudeGain(cursor.getFloat(indexes.altitudeGainIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.elevationLossIndex)) {
|
||||
trackPoint.setElevationLoss(cursor.getFloat(indexes.elevationLossIndex));
|
||||
if (!cursor.isNull(indexes.altitudeLossIndex)) {
|
||||
trackPoint.setAltitudeLoss(cursor.getFloat(indexes.altitudeLossIndex));
|
||||
}
|
||||
|
||||
return trackPoint;
|
||||
@@ -606,6 +605,7 @@ public class ContentProviderUtils {
|
||||
* @param location the location
|
||||
* @return trackPoint id if the location is in the track. -1L otherwise.
|
||||
*/
|
||||
@Deprecated
|
||||
public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) {
|
||||
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
|
||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
|
||||
@@ -688,13 +688,13 @@ public class ContentProviderUtils {
|
||||
}
|
||||
values.put(TrackPointsColumns.TIME, trackPoint.getTime().toEpochMilli());
|
||||
if (trackPoint.hasAltitude()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude());
|
||||
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude().toM());
|
||||
}
|
||||
if (trackPoint.hasAccuracy()) {
|
||||
values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy());
|
||||
}
|
||||
if (trackPoint.hasSpeed()) {
|
||||
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed());
|
||||
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed().toMPS());
|
||||
}
|
||||
if (trackPoint.hasBearing()) {
|
||||
values.put(TrackPointsColumns.BEARING, trackPoint.getBearing());
|
||||
@@ -707,17 +707,17 @@ public class ContentProviderUtils {
|
||||
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
|
||||
}
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance());
|
||||
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM());
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
|
||||
}
|
||||
|
||||
if (trackPoint.hasElevationGain()) {
|
||||
values.put(TrackPointsColumns.ELEVATION_GAIN, trackPoint.getElevationGain());
|
||||
if (trackPoint.hasAltitudeGain()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE_GAIN, trackPoint.getAltitudeGain());
|
||||
}
|
||||
if (trackPoint.hasElevationLoss()) {
|
||||
values.put(TrackPointsColumns.ELEVATION_LOSS, trackPoint.getElevationLoss());
|
||||
if (trackPoint.hasAltitudeLoss()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE_LOSS, trackPoint.getAltitudeLoss());
|
||||
}
|
||||
|
||||
return values;
|
||||
@@ -737,6 +737,7 @@ public class ContentProviderUtils {
|
||||
return new TrackPointIterator(this, trackId, startTrackPointId);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
|
||||
try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
|
||||
@@ -6,6 +6,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.util.UintUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -119,24 +123,24 @@ public final class SensorDataCycling {
|
||||
return wheelRevolutionsTime;
|
||||
}
|
||||
|
||||
public void compute(DistanceSpeed previous, int wheel_circumference_mm) {
|
||||
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
|
||||
if (hasData() && previous != null && previous.hasData()) {
|
||||
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||
if (timeDiff_ms <= 0) {
|
||||
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
||||
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
value = null;
|
||||
} else {
|
||||
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
|
||||
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as it some of those seem to count backwards
|
||||
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as some of those seem to count backwards
|
||||
|
||||
double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S;
|
||||
float distance_m = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M);
|
||||
float distance_overall_m = distance_m;
|
||||
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
||||
Distance distanceOverall = distance;
|
||||
if (previous.hasValue()) {
|
||||
distance_overall_m += previous.getValue().distance_overall_m;
|
||||
distanceOverall = distance.plus(previous.getValue().distanceOverall);
|
||||
}
|
||||
float speed_mps = (float) (distance_m / timeDiff_s);
|
||||
value = new Data(distance_m, distance_overall_m, speed_mps);
|
||||
Speed speed_mps = Speed.of(distance, timeDiff);
|
||||
value = new Data(distance, distanceOverall, speed_mps);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +148,7 @@ public final class SensorDataCycling {
|
||||
@Override
|
||||
public void reset() {
|
||||
if (value != null) {
|
||||
value = new Data(value.distance_m, 0, value.speed_mps);
|
||||
value = new Data(value.distance, Distance.of(0), value.speed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,22 +171,22 @@ public final class SensorDataCycling {
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public final float distance_m;
|
||||
public final float distance_overall_m;
|
||||
public final float speed_mps;
|
||||
public final Distance distance;
|
||||
public final Distance distanceOverall;
|
||||
public final Speed speed;
|
||||
|
||||
private Data(float distance_m, float distance_overall_m, float speed_mps) {
|
||||
this.distance_m = distance_m;
|
||||
this.distance_overall_m = distance_overall_m;
|
||||
this.speed_mps = speed_mps;
|
||||
private Data(Distance distance, Distance distanceOverall, Speed speed) {
|
||||
this.distance = distance;
|
||||
this.distanceOverall = distanceOverall;
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"distance_m=" + distance_m +
|
||||
", distance_overall_m=" + distance_overall_m +
|
||||
", speed_mps=" + speed_mps +
|
||||
"distance_m=" + distance +
|
||||
", distance_overall_m=" + distanceOverall +
|
||||
", speed_mps=" + speed +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
|
||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m);
|
||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps);
|
||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distanceOverall);
|
||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed);
|
||||
}
|
||||
|
||||
if (cyclingPower != null && cyclingPower.hasValue()) {
|
||||
|
||||
@@ -37,7 +37,9 @@ import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.ChartBinding;
|
||||
@@ -66,12 +68,11 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private int recordingDistanceInterval;
|
||||
private Distance recordingDistanceInterval;
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
// Stats gathered from the received data
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
private final List<ChartPoint> pendingPoints = new ArrayList<>();
|
||||
private String category = "";
|
||||
|
||||
@@ -216,7 +217,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
if (isResumed()) {
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
pendingPoints.clear();
|
||||
chartView.reset();
|
||||
runOnUiThread(() -> {
|
||||
@@ -227,24 +227,15 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, double smoothedAltitude_m) {
|
||||
if (isResumed()) {
|
||||
pendingPoints.add(createPendingPoint(trackPoint));
|
||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude_m, chartByDistance, chartView.getMetricUnits());
|
||||
pendingPoints.add(point);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
if (trackStatisticsUpdater != null) {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused) {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused, @NonNull TrackStatistics alsoUnused) {
|
||||
if (isResumed()) {
|
||||
chartView.addChartPoints(pendingPoints);
|
||||
pendingPoints.clear();
|
||||
@@ -305,6 +296,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
* Returns true if the selected track is recording.
|
||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||
*/
|
||||
@Deprecated
|
||||
//TODO Should not be dynamic but instead set while instantiating, i.e., newFragment().
|
||||
private synchronized boolean isSelectedTrackRecording() {
|
||||
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
|
||||
}
|
||||
@@ -321,29 +314,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ChartPoint createPendingPoint(@NonNull TrackPoint trackPoint) {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
return new ChartPoint(trackStatisticsUpdater, trackPoint, chartByDistance, chartView.getMetricUnits());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setChartView(ChartView view) {
|
||||
chartView = view;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setMetricUnits(boolean value) {
|
||||
chartView.setMetricUnits(value);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setReportSpeed(boolean value) {
|
||||
chartView.setReportSpeed(value);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setChartByDistance(boolean value) {
|
||||
chartByDistance = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -14,6 +13,11 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackActivityDataHubInterface;
|
||||
@@ -21,11 +25,12 @@ import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||
|
||||
@@ -36,6 +41,8 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
private static final String TAG = IntervalsFragment.class.getSimpleName();
|
||||
|
||||
private static final String FROM_TOP_TO_BOTTOM_KEY = "fromTopToBottom";
|
||||
|
||||
private IntervalStatisticsModel viewModel;
|
||||
protected IntervalStatisticsAdapter.StackMode stackModeListView;
|
||||
private IntervalStatisticsModel.IntervalOption selectedInterval;
|
||||
@@ -47,22 +54,38 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
private String category;
|
||||
private boolean isReportSpeed;
|
||||
|
||||
private IntervalListViewBinding viewBinding;
|
||||
|
||||
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
if (adapter != null) {
|
||||
adapter.notifyDataSetChanged();
|
||||
uploadIntervals();
|
||||
if (spinnerAdapter != null) {
|
||||
spinnerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static Fragment newInstance() {
|
||||
return new IntervalsFragment();
|
||||
/**
|
||||
* Creates an instance of this class.
|
||||
*
|
||||
* @param fromTopToBottom If true then the intervals are shown from top to bottom (the first interval on top). Otherwise the intervals are shown from bottom to top.
|
||||
* @return IntervalsFragment instance.
|
||||
*/
|
||||
public static Fragment newInstance(boolean fromTopToBottom) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(FROM_TOP_TO_BOTTOM_KEY, fromTopToBottom);
|
||||
IntervalsFragment intervalsFragment = new IntervalsFragment();
|
||||
intervalsFragment.setArguments(bundle);
|
||||
return intervalsFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
stackModeListView = getArguments().getBoolean(FROM_TOP_TO_BOTTOM_KEY, true) ? IntervalStatisticsAdapter.StackMode.STACK_FROM_TOP : IntervalStatisticsAdapter.StackMode.STACK_FROM_BOTTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,14 +99,11 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
||||
|
||||
stackModeListView = IntervalStatisticsAdapter.StackMode.STACK_FROM_TOP;
|
||||
|
||||
viewModel = new IntervalStatisticsModel();
|
||||
adapter = new IntervalStatisticsAdapter(getContext(), stackModeListView, metricUnits, isReportSpeed);
|
||||
viewBinding.intervalList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
||||
viewBinding.intervalList.setAdapter(adapter);
|
||||
|
||||
spinnerAdapter = new ArrayAdapter<IntervalStatisticsModel.IntervalOption>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.values()) {
|
||||
@NonNull
|
||||
@@ -109,7 +129,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectedInterval = IntervalStatisticsModel.IntervalOption.values()[i];
|
||||
loadIntervals();
|
||||
uploadIntervals();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,6 +141,10 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class);
|
||||
loadIntervals();
|
||||
resumeTrackDataHub();
|
||||
}
|
||||
|
||||
@@ -128,7 +152,6 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@@ -155,10 +178,15 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
if (viewModel == null) {
|
||||
return;
|
||||
}
|
||||
LiveData<List<IntervalStatistics.Interval>> liveData = viewModel.getIntervalStats(metricUnits, selectedInterval);
|
||||
liveData.observe(getActivity(), intervalList -> adapter.swapData(intervalList, metricUnits, isReportSpeed));
|
||||
}
|
||||
|
||||
IntervalStatistics intervalStatistics = viewModel.getIntervalStats(metricUnits, selectedInterval);
|
||||
adapter = new IntervalStatisticsAdapter(getContext(), intervalStatistics.getIntervalList(), category, stackModeListView);
|
||||
viewBinding.intervalList.setAdapter(adapter);
|
||||
private synchronized void uploadIntervals() {
|
||||
if (viewModel == null) {
|
||||
return;
|
||||
}
|
||||
viewModel.upload(metricUnits, selectedInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,11 +213,11 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
// Set category.
|
||||
category = track != null ? track.getCategory() : "";
|
||||
String category = track != null ? track.getCategory() : "";
|
||||
|
||||
// Set rate label.
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category); //TODO Handle sharedPreferenceChangeListener
|
||||
viewBinding.intervalRate.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
||||
viewBinding.intervalRate.setText(isReportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -203,23 +231,23 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused, Speed unused2, double unused3) {
|
||||
if (isResumed()) {
|
||||
viewModel.add(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused) {
|
||||
if (isResumed()) {
|
||||
viewModel.add(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused) {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused, @NonNull TrackStatistics alsoUnused) {
|
||||
if (isResumed()) {
|
||||
runOnUiThread(this::loadIntervals);
|
||||
runOnUiThread(viewModel::onNewTrackPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,48 +277,4 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
fragmentActivity.runOnUiThread(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IntervalsRecordingFragment extends IntervalsFragment {
|
||||
// Refreshing intervals stats it's not so demanding so 5 seconds is enough to balance performance and user experience.
|
||||
private static final long UI_UPDATE_INTERVAL = 5 * UnitConversions.ONE_SECOND_MS;
|
||||
|
||||
private Handler intervalHandler;
|
||||
|
||||
private final Runnable intervalRunner = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
updateIntervals();
|
||||
intervalHandler.postDelayed(intervalRunner, UI_UPDATE_INTERVAL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static Fragment newInstance() {
|
||||
return new IntervalsRecordingFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
intervalHandler = new Handler();
|
||||
stackModeListView = IntervalStatisticsAdapter.StackMode.STACK_FROM_BOTTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
intervalHandler.post(intervalRunner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
intervalHandler.removeCallbacks(intervalRunner);
|
||||
}
|
||||
|
||||
private void updateIntervals() {
|
||||
loadIntervals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.MarkerEditActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
@@ -43,7 +45,6 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* A fragment to show marker details.
|
||||
@@ -55,7 +56,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
private static final String TAG = MarkerDetailFragment.class.getSimpleName();
|
||||
private static final String KEY_MARKER_ID = "markerId";
|
||||
|
||||
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND_MS;
|
||||
private static final Duration HIDE_TEXT_DELAY = Duration.ofSeconds(4);
|
||||
|
||||
private MenuItem shareMarkerImageMenuItem;
|
||||
|
||||
@@ -126,7 +127,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
viewBinding.markerDetailMarkerTextGradient.setVisibility(visibility);
|
||||
viewBinding.markerDetailMarkerInfo.setVisibility(visibility);
|
||||
if (visibility == View.VISIBLE) {
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY.toMillis());
|
||||
}
|
||||
});
|
||||
return viewBinding.getRoot();
|
||||
@@ -236,7 +237,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
if (hasPhoto) {
|
||||
handler.removeCallbacks(hideText);
|
||||
viewBinding.markerDetailMarkerPhoto.setImageURI(marker.getPhotoURI());
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY.toMillis());
|
||||
} else {
|
||||
viewBinding.markerDetailMarkerPhoto.setImageResource(MarkerUtils.ICON_ID);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -54,6 +55,8 @@ import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
*/
|
||||
public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
private static final String TAG = StatisticsRecordedFragment.class.getSimpleName();
|
||||
|
||||
private static final String TRACK_ID_KEY = "trackId";
|
||||
|
||||
public static StatisticsRecordedFragment newInstance(Track.Id trackId) {
|
||||
@@ -65,11 +68,13 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private TrackStatistics trackStatistics;
|
||||
private SensorStatistics sensorStatistics;
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
private String category = "";
|
||||
|
||||
private Track.Id trackId;
|
||||
@Nullable // Lazily loaded.
|
||||
private Track track;
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private StatisticsRecordedBinding viewBinding;
|
||||
@@ -86,9 +91,9 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && track != null) {
|
||||
updateUInecessary = true;
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
@@ -152,12 +157,17 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
trackStatistics = track.getTrackStatistics();
|
||||
if (track == null) {
|
||||
Log.e(TAG, "track cannot be null");
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
String newCategory = track.getCategory();
|
||||
if (!category.equals(newCategory)) {
|
||||
category = newCategory;
|
||||
boolean prefsChanged = this.track == null || (!this.track.getCategory().equals(track.getCategory()));
|
||||
this.track = track;
|
||||
if (prefsChanged) {
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
||||
}
|
||||
|
||||
@@ -165,7 +175,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
updateUI();
|
||||
updateSensorUI();
|
||||
|
||||
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon, viewBinding.statsNameValue);
|
||||
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -174,14 +184,14 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
private void loadTrackDescription(@NonNull Track track) {
|
||||
viewBinding.statsNameValue.setText(track.getName());
|
||||
viewBinding.statsDescriptionValue.setText(track.getDescription());
|
||||
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(getContext(), trackStatistics.getStartTime()));
|
||||
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(getContext(), track.getTrackStatistics().getStartTime()));
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = trackStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), trackStatistics.getTotalDistance(), preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
@@ -189,7 +199,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set activity type
|
||||
{
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), track.getCategory());
|
||||
viewBinding.statsActivityTypeIcon.setImageDrawable(ContextCompat.getDrawable(getContext(), TrackIconUtils.getIconDrawable(trackIconValue)));
|
||||
}
|
||||
|
||||
@@ -201,54 +211,49 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = trackStatistics.getAverageSpeed();
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageSpeed(), preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsAverageSpeedValue.setText(parts.first);
|
||||
viewBinding.statsAverageSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = trackStatistics.getMaxSpeed();
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getMaxSpeed(), preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMaxSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMaxSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = trackStatistics.getAverageMovingSpeed();
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageMovingSpeed(), preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMovingSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation gain and loss
|
||||
// Set altitude gain and loss
|
||||
{
|
||||
// Make elevation visible?
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(sharedPreferences, getContext());
|
||||
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
// Make altitude visible?
|
||||
boolean show = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
|
||||
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
|
||||
Float elevationGain_m = trackStatistics.getTotalElevationGain();
|
||||
Float elevationLoss_m = trackStatistics.getTotalElevationLoss();
|
||||
Float altitudeGain_m = trackStatistics.getTotalAltitudeGain();
|
||||
Float altitudeLoss_m = trackStatistics.getTotalAltitudeLoss();
|
||||
|
||||
Pair<String, String> parts;
|
||||
|
||||
parts = StringUtils.formatElevation(getContext(), elevationGain_m, preferenceMetricUnits);
|
||||
viewBinding.statsElevationGainValue.setText(parts.first);
|
||||
viewBinding.statsElevationGainUnit.setText(parts.second);
|
||||
parts = StringUtils.formatAltitude(getContext(), altitudeGain_m, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeGainValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeGainUnit.setText(parts.second);
|
||||
|
||||
parts = StringUtils.formatElevation(getContext(), elevationLoss_m, preferenceMetricUnits);
|
||||
viewBinding.statsElevationLossValue.setText(parts.first);
|
||||
viewBinding.statsElevationLossUnit.setText(parts.second);
|
||||
parts = StringUtils.formatAltitude(getContext(), altitudeLoss_m, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,19 +24,20 @@ import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
|
||||
/**
|
||||
@@ -49,12 +50,12 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
private static final String TAG = StatisticsRecordingFragment.class.getSimpleName();
|
||||
|
||||
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND_MS;
|
||||
private static final Duration UI_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
||||
|
||||
public static Fragment newInstance() {
|
||||
return new StatisticsRecordingFragment();
|
||||
}
|
||||
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
private Handler handlerUpdateUI;
|
||||
@@ -62,10 +63,8 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
private TrackPoint lastTrackPoint;
|
||||
|
||||
//TODO Should not be nullable
|
||||
private TrackStatistics lastTrackStatistics;
|
||||
|
||||
private String category = "";
|
||||
@Nullable // Lazily loaded.
|
||||
private Track track;
|
||||
|
||||
private StatisticsRecordingBinding viewBinding;
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
@@ -73,7 +72,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
private SharedPreferences sharedPreferences;
|
||||
private boolean preferenceMetricUnits;
|
||||
private boolean preferenceReportSpeed;
|
||||
private boolean preferenceShowElevation;
|
||||
private boolean preferenceShowAltitude;
|
||||
private boolean preferenceShowCoordinate;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
@@ -84,14 +83,14 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && track != null) {
|
||||
updateUInecessary = true;
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_elevation_key, key)) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_altitude_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceShowElevation = PreferencesUtils.isShowStatsElevation(sharedPreferences, getContext());
|
||||
preferenceShowAltitude = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_coordinate_key, key)) {
|
||||
@@ -111,7 +110,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
private final Runnable updateUIeachSecond = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && isSelectedTrackRecording()) {
|
||||
if (!isSelectedTrackPaused() && lastTrackStatistics != null) {
|
||||
if (!isSelectedTrackPaused() && track != null) {
|
||||
synchronized (this) {
|
||||
if (lastTrackPoint != null && lastTrackPoint.hasLocation() && !lastTrackPoint.isRecent()) {
|
||||
lastTrackPoint = null;
|
||||
@@ -122,7 +121,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
updateSensorDataUI();
|
||||
}
|
||||
|
||||
handlerUpdateUI.postDelayed(this, UI_UPDATE_INTERVAL);
|
||||
handlerUpdateUI.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -140,7 +139,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(category));
|
||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(track != null ? track.getCategory() : "")); //TODO "" would be useless as Track was not yet loaded.
|
||||
|
||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||
@@ -197,12 +196,10 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
String newCategory = track != null ? track.getCategory() : "";
|
||||
if (!category.equals(newCategory)) {
|
||||
category = newCategory;
|
||||
if ((this.track == null && track != null) || (this.track != null && track != null && !this.track.getCategory().equals(track.getCategory()))) {
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
||||
}
|
||||
this.track = track;
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
@@ -215,17 +212,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint newLastTrackPoint) {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint newLastTrackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
@@ -303,15 +290,15 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
/**
|
||||
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
|
||||
* Also sets elevation gain and loss.
|
||||
* Also sets altitude gain and loss.
|
||||
*/
|
||||
private void updateSensorDataUI() {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
|
||||
if (trackRecordingService == null) {
|
||||
Log.d(TAG, "Cannot get the track recording service.");
|
||||
} else {
|
||||
SensorDataSet sensorDataSet = trackRecordingService.getSensorData();
|
||||
SensorDataSet sensorDataSet = trackRecordingService.getSensorDataSet();
|
||||
if (sensorDataSet != null) {
|
||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
||||
if (sensorDataSet.getHeartRate() != null) {
|
||||
@@ -326,44 +313,44 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
setSpeedSensorData(sensorDataSet);
|
||||
}
|
||||
//TODO Check if we can distribute the total elevation gain and loss via trackStatistics instead of doing some computation in the UI layer.
|
||||
setTotalElevationGain(trackRecordingService.getElevationGain_m());
|
||||
setTotalElevationLoss(trackRecordingService.getElevationLoss_m());
|
||||
//TODO Check if we can get the total altitude gain and loss via trackStatistics instead of doing some computation in the UI layer.
|
||||
setTotalAltitudeGain(trackRecordingService.getAltitudeGain_m());
|
||||
setTotalAltitudeLoss(trackRecordingService.getAltitudeLoss_m());
|
||||
}
|
||||
}
|
||||
|
||||
// Set elevation gain
|
||||
private void setTotalElevationGain(Float elevationGain_m) {
|
||||
Float totalElevationGain = elevationGain_m;
|
||||
// Set altitude gain
|
||||
private void setTotalAltitudeGain(Float altitudeGain_m) {
|
||||
Float totalAltitudeGain = altitudeGain_m;
|
||||
|
||||
if (lastTrackStatistics != null && lastTrackStatistics.hasTotalElevationGain()) {
|
||||
if (elevationGain_m == null) {
|
||||
totalElevationGain = lastTrackStatistics.getTotalElevationGain();
|
||||
} else {
|
||||
totalElevationGain += lastTrackStatistics.getTotalElevationGain();
|
||||
if (track != null && track.getTrackStatistics().hasTotalAltitudeGain()) {
|
||||
if (altitudeGain_m == null) {
|
||||
totalAltitudeGain = 0f;
|
||||
}
|
||||
|
||||
totalAltitudeGain += track.getTrackStatistics().getTotalAltitudeGain();
|
||||
}
|
||||
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationGain, preferenceMetricUnits);
|
||||
viewBinding.statsElevationGainValue.setText(parts.first);
|
||||
viewBinding.statsElevationGainUnit.setText(parts.second);
|
||||
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), totalAltitudeGain, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeGainValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeGainUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation loss
|
||||
private void setTotalElevationLoss(Float elevationLoss_m) {
|
||||
Float totalElevationLoss = elevationLoss_m;
|
||||
// Set altitude loss
|
||||
private void setTotalAltitudeLoss(Float altitudeLoss_m) {
|
||||
Float totalAltitudeLoss = altitudeLoss_m;
|
||||
|
||||
if (lastTrackStatistics != null && lastTrackStatistics.hasTotalElevationLoss()) {
|
||||
if (elevationLoss_m == null) {
|
||||
totalElevationLoss = lastTrackStatistics.getTotalElevationLoss();
|
||||
if (track != null && track.getTrackStatistics().hasTotalAltitudeLoss()) {
|
||||
if (altitudeLoss_m == null) {
|
||||
totalAltitudeLoss = track.getTrackStatistics().getTotalAltitudeLoss();
|
||||
} else {
|
||||
totalElevationLoss += lastTrackStatistics.getTotalElevationLoss();
|
||||
totalAltitudeLoss += track.getTrackStatistics().getTotalAltitudeLoss();
|
||||
}
|
||||
}
|
||||
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationLoss, preferenceMetricUnits);
|
||||
viewBinding.statsElevationLossValue.setText(parts.first);
|
||||
viewBinding.statsElevationLossUnit.setText(parts.second);
|
||||
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), totalAltitudeLoss, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
private void setSpeedSensorData(SensorDataSet sensorDataSet) {
|
||||
@@ -371,35 +358,35 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
|
||||
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
setTotalDistance(data.getValue().distance_overall_m);
|
||||
setSpeed(data.getValue().speed_mps);
|
||||
setTotalDistance(data.getValue().distanceOverall);
|
||||
setSpeed(data.getValue().speed);
|
||||
}
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
setSpeed(data.getValue().speed_mps);
|
||||
setSpeed(data.getValue().speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStats() {
|
||||
|
||||
setTotalDistance(0);
|
||||
setTotalDistance(Distance.of(0)); //TODO Why?
|
||||
|
||||
// Set activity type
|
||||
{
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||
if (track != null) {
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), track.getCategory());
|
||||
viewBinding.statsActivityTypeIcon.setEnabled(isSelectedTrackRecording());
|
||||
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
|
||||
}
|
||||
|
||||
// Set time
|
||||
if (lastTrackStatistics != null) {
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(lastTrackStatistics.getMovingTime()));
|
||||
if (track != null) {
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(track.getTrackStatistics().getMovingTime()));
|
||||
updateTotalTime();
|
||||
}
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageSpeed() : Double.NaN;
|
||||
Speed speed = track != null ? track.getTrackStatistics().getAverageSpeed() : null;
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
@@ -409,7 +396,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getMaxSpeed();
|
||||
Speed speed = track != null ? track.getTrackStatistics().getMaxSpeed() : null;
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
@@ -420,7 +407,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
Speed speed = track != null ? track.getTrackStatistics().getAverageMovingSpeed() : null;
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
@@ -429,16 +416,16 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation gain and loss
|
||||
// Set altitude gain and loss
|
||||
{
|
||||
viewBinding.statsElevationGroup.setVisibility(preferenceShowElevation ? View.VISIBLE : View.GONE);
|
||||
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTotalTime() {
|
||||
Duration totalTime = lastTrackStatistics.getTotalTime();
|
||||
Duration totalTime = track.getTrackStatistics().getTotalTime();
|
||||
if (isSelectedTrackRecording()) {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
totalTime = trackRecordingService.getTotalTime();
|
||||
}
|
||||
@@ -449,18 +436,18 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
private void setLocationValues() {
|
||||
// Set speed/pace
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
Speed speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : null;
|
||||
setSpeed(speed);
|
||||
|
||||
// Set elevation
|
||||
viewBinding.statsElevationGroup.setVisibility(preferenceShowElevation ? View.VISIBLE : View.GONE);
|
||||
// Set altitude
|
||||
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (preferenceShowElevation) {
|
||||
// Current elevation
|
||||
Float altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? (float) lastTrackPoint.getAltitude() : null;
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, preferenceMetricUnits);
|
||||
viewBinding.statsElevationCurrentValue.setText(parts.first);
|
||||
viewBinding.statsElevationCurrentUnit.setText(parts.second);
|
||||
if (preferenceShowAltitude) {
|
||||
// Current altitude
|
||||
Float altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? (float) lastTrackPoint.getAltitude().toM() : null;
|
||||
Pair<String, String> parts = StringUtils.formatAltitude(getContext(), altitude, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeCurrentValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeCurrentUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
@@ -477,15 +464,15 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
}
|
||||
|
||||
private void setTotalDistance(double sensorDistanceSinceLastTrackpoint) {
|
||||
double totalDistance = lastTrackStatistics != null ? (lastTrackStatistics.getTotalDistance() + sensorDistanceSinceLastTrackpoint) : Double.NaN;
|
||||
private void setTotalDistance(Distance sensorDistanceSinceLastTrackpoint) {
|
||||
Distance totalDistance = track != null ? (track.getTrackStatistics().getTotalDistance().plus(sensorDistanceSinceLastTrackpoint)) : Distance.invalid();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
private void setSpeed(double speed) {
|
||||
private void setSpeed(Speed speed) {
|
||||
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
|
||||
@@ -46,7 +46,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
private static final String TAG = GPXTrackExporter.class.getSimpleName();
|
||||
|
||||
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat ALTITUDE_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 DISTANCE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
@@ -59,8 +59,8 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
* GPX readers expect to see fractional numbers with US-style punctuation.
|
||||
* That is, they want periods for decimal points, rather than commas.
|
||||
*/
|
||||
ELEVATION_FORMAT.setMaximumFractionDigits(1);
|
||||
ELEVATION_FORMAT.setGroupingUsed(false);
|
||||
ALTITUDE_FORMAT.setMaximumFractionDigits(1);
|
||||
ALTITUDE_FORMAT.setGroupingUsed(false);
|
||||
|
||||
COORDINATE_FORMAT.setMaximumFractionDigits(6);
|
||||
COORDINATE_FORMAT.setMaximumIntegerDigits(3);
|
||||
@@ -200,7 +200,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
|
||||
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\"");
|
||||
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
|
||||
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v2\"");
|
||||
printWriter.println("xmlns:pwr=\"http://www.garmin.com/xmlschemas/PowerExtension/v1\"");
|
||||
printWriter.println("xsi:schemaLocation=" +
|
||||
"\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
@@ -237,7 +237,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
|
||||
if (marker.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(marker.getAltitude()) + "</ele>");
|
||||
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
|
||||
}
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(marker.getTime()) + "</time>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
|
||||
@@ -281,16 +281,16 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
||||
|
||||
if (trackPoint.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
|
||||
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(trackPoint.getAltitude().toM()) + "</ele>");
|
||||
}
|
||||
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
|
||||
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence() || trackPoint.hasElevationGain() || trackPoint.hasElevationLoss()) {
|
||||
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
|
||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed()) + "</gpxtpx:speed>");
|
||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
@@ -305,16 +305,16 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower()) + "</pwr:PowerInWatts>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasElevationGain()) {
|
||||
printWriter.println("<opentracks:gain>" + ELEVATION_FORMAT.format(trackPoint.getElevationGain()) + "</opentracks:gain>");
|
||||
if (trackPoint.hasAltitudeGain()) {
|
||||
printWriter.println("<opentracks:gain>" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeGain()) + "</opentracks:gain>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasElevationLoss()) {
|
||||
printWriter.println("<opentracks:loss>" + ELEVATION_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:loss>");
|
||||
if (trackPoint.hasAltitudeLoss()) {
|
||||
printWriter.println("<opentracks:loss>" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeLoss()) + "</opentracks:loss>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
printWriter.println("<opentracks:distance>" + DISTANCE_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:distance>");
|
||||
printWriter.println("<opentracks:distance>" + DISTANCE_FORMAT.format(trackPoint.getSensorDistance().toM()) + "</opentracks:distance>");
|
||||
}
|
||||
|
||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
||||
|
||||
@@ -61,8 +61,8 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence";
|
||||
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
||||
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
||||
public static final String EXTENDED_DATA_TYPE_ELEVATION_GAIN = "elevation_gain";
|
||||
public static final String EXTENDED_DATA_TYPE_ELEVATION_LOSS = "elevation_loss";
|
||||
public static final String EXTENDED_DATA_TYPE_ALTITUDE_GAIN = "elevation_gain";
|
||||
public static final String EXTENDED_DATA_TYPE_ALTITUDE_LOSS = "elevation_loss";
|
||||
|
||||
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
||||
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
|
||||
@@ -79,8 +79,8 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
private final List<Float> powerList = new ArrayList<>();
|
||||
private final List<Float> cadenceList = new ArrayList<>();
|
||||
private final List<Float> heartRateList = new ArrayList<>();
|
||||
private final List<Float> elevationGainList = new ArrayList<>();
|
||||
private final List<Float> elevationLossList = new ArrayList<>();
|
||||
private final List<Float> altitudeGainList = new ArrayList<>();
|
||||
private final List<Float> altitudeLossList = new ArrayList<>();
|
||||
|
||||
@Deprecated // Figure out a better way to do this! (if needed)
|
||||
private TrackPoint startTrackPoint;
|
||||
@@ -344,8 +344,8 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
powerList.clear();
|
||||
cadenceList.clear();
|
||||
heartRateList.clear();
|
||||
elevationGainList.clear();
|
||||
elevationLossList.clear();
|
||||
altitudeGainList.clear();
|
||||
altitudeLossList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,11 +370,11 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
if (heartRateList.stream().anyMatch(Objects::nonNull)) {
|
||||
writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE);
|
||||
}
|
||||
if (elevationGainList.stream().anyMatch(Objects::nonNull)) {
|
||||
writeSimpleArrayData(elevationGainList, EXTENDED_DATA_TYPE_ELEVATION_GAIN);
|
||||
if (altitudeGainList.stream().anyMatch(Objects::nonNull)) {
|
||||
writeSimpleArrayData(altitudeGainList, EXTENDED_DATA_TYPE_ALTITUDE_GAIN);
|
||||
}
|
||||
if (elevationLossList.stream().anyMatch(Objects::nonNull)) {
|
||||
writeSimpleArrayData(elevationLossList, EXTENDED_DATA_TYPE_ELEVATION_LOSS);
|
||||
if (altitudeLossList.stream().anyMatch(Objects::nonNull)) {
|
||||
writeSimpleArrayData(altitudeLossList, EXTENDED_DATA_TYPE_ALTITUDE_LOSS);
|
||||
}
|
||||
}
|
||||
printWriter.println("</SchemaData>");
|
||||
@@ -395,16 +395,16 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
} else {
|
||||
printWriter.println("<gx:coord/>");
|
||||
}
|
||||
speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null);
|
||||
speedList.add(trackPoint.hasSpeed() ? (float) trackPoint.getSpeed().toMPS() : null);
|
||||
|
||||
if (exportSensorData) {
|
||||
distanceList.add(trackPoint.hasSensorDistance() ? trackPoint.getSensorDistance() : null);
|
||||
distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : null);
|
||||
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
|
||||
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
|
||||
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
|
||||
|
||||
elevationGainList.add(trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : null);
|
||||
elevationLossList.add(trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : null);
|
||||
altitudeGainList.add(trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : null);
|
||||
altitudeLossList.add(trackPoint.hasAltitudeLoss() ? trackPoint.getAltitudeLoss() : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -518,13 +518,12 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
return location.getBearing();
|
||||
}
|
||||
|
||||
private String getCoordinates(Location location, String separator) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(location.getLongitude()).append(separator).append(location.getLatitude());
|
||||
private static String getCoordinates(Location location, String separator) {
|
||||
String result = location.getLongitude() + separator + location.getLatitude();
|
||||
if (location.hasAltitude()) {
|
||||
builder.append(separator).append(location.getAltitude());
|
||||
result += separator + location.getAltitude();
|
||||
}
|
||||
return builder.toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+21
-95
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -29,8 +28,6 @@ import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
@@ -38,15 +35,16 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
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.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
@@ -59,13 +57,13 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter {
|
||||
abstract class AbstractFileTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
||||
|
||||
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int recordingDistanceInterval;
|
||||
private final Distance recordingDistanceInterval;
|
||||
|
||||
private final List<Track.Id> trackIds = new ArrayList<>();
|
||||
private final List<Marker> markers = new ArrayList<>();
|
||||
@@ -124,24 +122,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<Track.Id> importFile(InputStream inputStream) {
|
||||
try {
|
||||
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, this);
|
||||
return trackIds;
|
||||
} catch (IOException | SAXException | ParserConfigurationException | ParsingException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
if (trackIds.size() > 0) {
|
||||
cleanImport();
|
||||
}
|
||||
throw new ImportParserException(e);
|
||||
} catch (SQLiteConstraintException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
throw new ImportAlreadyExistsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onFileEnd() {
|
||||
// Add markers to the last imported track
|
||||
int size = trackIds.size();
|
||||
@@ -202,11 +182,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (trackPoint.getLatitude() == marker.getLatitude() && trackPoint.getLongitude() == marker.getLongitude()) {
|
||||
String markerDescription = marker.getDescription();
|
||||
String icon = context.getString(R.string.marker_icon_url);
|
||||
double length = trackStatisticsUpdater.getTrackStatistics().getTotalDistance();
|
||||
long duration = trackStatisticsUpdater.getTrackStatistics().getTotalTime().toMillis();
|
||||
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
||||
|
||||
// Insert marker
|
||||
Marker newMarker = new Marker(marker.getName(), markerDescription, marker.getCategory(), icon, track.getId(), length, duration, trackPoint, marker.getPhotoUrl());
|
||||
Marker newMarker = new Marker(marker.getName(), markerDescription, marker.getCategory(), icon, track.getId(), stats, trackPoint, marker.getPhotoUrl());
|
||||
contentProviderUtils.insertMarker(newMarker);
|
||||
}
|
||||
|
||||
@@ -374,17 +353,16 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
||||
float speed = trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint) / timeDifference.toMillis();
|
||||
trackPoint.setSpeed(speed);
|
||||
trackPoint.setSpeed(Speed.of(trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint), timeDifference));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
||||
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
|
||||
|
||||
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
|
||||
double distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
|
||||
if (distanceToLastTrackLocation > maxRecordingDistance) {
|
||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
|
||||
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
|
||||
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
}
|
||||
}
|
||||
@@ -429,7 +407,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
return null;
|
||||
}
|
||||
|
||||
private TrackPoint createTrackPoint() throws ParsingException {
|
||||
protected TrackPoint createTrackPoint() throws ParsingException {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
|
||||
try {
|
||||
@@ -449,68 +427,14 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||
}
|
||||
|
||||
|
||||
if (altitude != null) {
|
||||
try {
|
||||
trackPoint.setAltitude(Double.parseDouble(altitude));
|
||||
trackPoint.setAltitude(Altitude.WGS84.of(Double.parseDouble(altitude)));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (speed != null) {
|
||||
try {
|
||||
trackPoint.setSpeed(Float.parseFloat(speed));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(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 ParsingException(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 ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (power != null) {
|
||||
try {
|
||||
trackPoint.setPower(Float.parseFloat(power));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (gain != null) {
|
||||
try {
|
||||
trackPoint.setElevationGain(Float.parseFloat(gain));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation gain: %s", gain)), e);
|
||||
}
|
||||
}
|
||||
if (loss != null) {
|
||||
try {
|
||||
trackPoint.setElevationLoss(Float.parseFloat(loss));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation loss: %s", loss)), e);
|
||||
}
|
||||
}
|
||||
if (distance != null) {
|
||||
try {
|
||||
trackPoint.setSensorDistance(Float.parseFloat(distance));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
||||
}
|
||||
}
|
||||
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
@@ -537,10 +461,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up import.
|
||||
*/
|
||||
private void cleanImport() {
|
||||
@Override
|
||||
public List<Track.Id> getImportTrackIds() {
|
||||
return trackIds;
|
||||
}
|
||||
|
||||
public void cleanImport() {
|
||||
contentProviderUtils.deleteTracks(context, trackIds);
|
||||
}
|
||||
|
||||
@@ -570,7 +496,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
super(message);
|
||||
}
|
||||
|
||||
private ParsingException(@NonNull String message, Exception cause) {
|
||||
protected ParsingException(@NonNull String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,12 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
@@ -42,7 +47,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
private static final String TAG_DESCRIPTION = "desc";
|
||||
private static final String TAG_COMMENT = "cmt";
|
||||
private static final String TAG_ELEVATION = "ele";
|
||||
private static final String TAG_ALTITUDE = "ele";
|
||||
private static final String TAG_GPX = "gpx";
|
||||
private static final String TAG_NAME = "name";
|
||||
private static final String TAG_TIME = "time";
|
||||
@@ -79,6 +84,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
super(context, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHandler getHandler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||
switch (tag) {
|
||||
@@ -98,7 +108,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||
public void endElement(String uri, String localName, String tag) {
|
||||
switch (tag) {
|
||||
case TAG_GPX:
|
||||
onFileEnd();
|
||||
@@ -135,7 +145,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
time = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ELEVATION:
|
||||
case TAG_ALTITUDE:
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
}
|
||||
@@ -199,6 +209,66 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
category = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackPoint createTrackPoint() throws ParsingException {
|
||||
TrackPoint trackPoint = super.createTrackPoint();
|
||||
|
||||
if (speed != null) {
|
||||
try {
|
||||
trackPoint.setSpeed(Speed.of(speed));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(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 ParsingException(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 ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (power != null) {
|
||||
try {
|
||||
trackPoint.setPower(Float.parseFloat(power));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (gain != null) {
|
||||
try {
|
||||
trackPoint.setAltitudeGain(Float.parseFloat(gain));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s", gain)), e);
|
||||
}
|
||||
}
|
||||
if (loss != null) {
|
||||
try {
|
||||
trackPoint.setAltitudeLoss(Float.parseFloat(loss));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude loss: %s", loss)), e);
|
||||
}
|
||||
}
|
||||
if (distance != null) {
|
||||
try {
|
||||
trackPoint.setSensorDistance(Distance.of(distance));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
||||
}
|
||||
}
|
||||
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* On track point start.
|
||||
*
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.core.app.JobIntentService;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -47,23 +46,22 @@ public class ImportService extends JobIntentService {
|
||||
}
|
||||
|
||||
private void importFile(DocumentFile file) {
|
||||
TrackImporter trackImporter;
|
||||
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||
|
||||
String fileExtension = FileUtils.getExtension(file);
|
||||
try {
|
||||
|
||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new GpxFileTrackImporter(this);
|
||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new KmlFileTrackImporter(this);
|
||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new KmzTrackImporter(this, file.getUri());
|
||||
} else {
|
||||
Log.d(TAG, "Unsupported file format.");
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
||||
return;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = getContentResolver().openInputStream(file.getUri())) {
|
||||
ArrayList<Track.Id> trackIds = new ArrayList<>(trackImporter.importFile(inputStream));
|
||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new XMLImporter(new GpxFileTrackImporter(this)).importFile(this, file.getUri()));
|
||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new XMLImporter(new KmlFileTrackImporter(this)).importFile(this, file.getUri()));
|
||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new KmzTrackImporter().importFile(this, file.getUri()));
|
||||
} else {
|
||||
Log.d(TAG, "Unsupported file format.");
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trackIds.isEmpty()) {
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName()));
|
||||
|
||||
@@ -23,9 +23,12 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||
@@ -35,7 +38,6 @@ import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
//TODO Do not use AbstractFileTrackImporter as TrackPoint sensor generation is done differently here.
|
||||
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
|
||||
@@ -70,8 +72,8 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private final ArrayList<Float> cadenceList = new ArrayList<>();
|
||||
private final ArrayList<Float> heartRateList = new ArrayList<>();
|
||||
private final ArrayList<Float> powerList = new ArrayList<>();
|
||||
private final ArrayList<Float> elevationGainList = new ArrayList<>();
|
||||
private final ArrayList<Float> elevationLossList = new ArrayList<>();
|
||||
private final ArrayList<Float> altitudeGainList = new ArrayList<>();
|
||||
private final ArrayList<Float> altitudeLossList = new ArrayList<>();
|
||||
|
||||
public KmlFileTrackImporter(Context context) {
|
||||
this(context, new ContentProviderUtils(context));
|
||||
@@ -82,6 +84,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
super(context, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHandler getHandler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||
switch (tag) {
|
||||
@@ -193,7 +200,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
markerType = null;
|
||||
}
|
||||
|
||||
private void onMarkerEnd() throws SAXException {
|
||||
private void onMarkerEnd() {
|
||||
if (!MARKER_STYLE.equals(markerType)) {
|
||||
return;
|
||||
}
|
||||
@@ -221,8 +228,8 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
heartRateList.clear();
|
||||
cadenceList.clear();
|
||||
powerList.clear();
|
||||
elevationGainList.clear();
|
||||
elevationLossList.clear();
|
||||
altitudeGainList.clear();
|
||||
altitudeLossList.clear();
|
||||
}
|
||||
|
||||
protected void onTrackSegmentEnd() {
|
||||
@@ -231,11 +238,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
|
||||
if (i < speedList.size()) {
|
||||
trackPoint.setSpeed(speedList.get(i));
|
||||
if (i < speedList.size() && speedList.get(i) != null) {
|
||||
trackPoint.setSpeed(Speed.of(speedList.get(i)));
|
||||
}
|
||||
if (i < distanceList.size()) {
|
||||
trackPoint.setSensorDistance(distanceList.get(i));
|
||||
if (i < distanceList.size() && distanceList.get(i) != null) {
|
||||
trackPoint.setSensorDistance(Distance.of(distanceList.get(i)));
|
||||
}
|
||||
if (i < heartRateList.size()) {
|
||||
trackPoint.setHeartRate_bpm(heartRateList.get(i));
|
||||
@@ -246,11 +253,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
if (i < powerList.size()) {
|
||||
trackPoint.setPower(powerList.get(i));
|
||||
}
|
||||
if (i < elevationGainList.size()) {
|
||||
trackPoint.setElevationGain(elevationGainList.get(i));
|
||||
if (i < altitudeGainList.size()) {
|
||||
trackPoint.setAltitudeGain(altitudeGainList.get(i));
|
||||
}
|
||||
if (i < elevationLossList.size()) {
|
||||
trackPoint.setElevationLoss(elevationLossList.get(i));
|
||||
if (i < altitudeLossList.size()) {
|
||||
trackPoint.setAltitudeLoss(altitudeLossList.get(i));
|
||||
}
|
||||
|
||||
insertTrackPoint(trackPoint);
|
||||
@@ -260,7 +267,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
/**
|
||||
* On track point end. gx:coord end tag.
|
||||
*/
|
||||
private void onTrackPointEnd() throws SAXException {
|
||||
private void onTrackPointEnd() {
|
||||
// Add trackPoint to trackPoints
|
||||
if (content == null) {
|
||||
return;
|
||||
@@ -327,11 +334,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE:
|
||||
cadenceList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ELEVATION_GAIN:
|
||||
elevationGainList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN:
|
||||
altitudeGainList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ELEVATION_LOSS:
|
||||
elevationLossList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS:
|
||||
altitudeLossList.add(value);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
||||
|
||||
@@ -22,10 +22,9 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -40,46 +39,32 @@ import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Imports a KMZ file.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class KmzTrackImporter implements TrackImporter {
|
||||
public class KmzTrackImporter {
|
||||
|
||||
private static final String TAG = KmzTrackImporter.class.getSimpleName();
|
||||
|
||||
private static final List<String> KMZ_IMAGES_EXT = Arrays.asList("jpeg", "jpg", "png");
|
||||
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
private Context context;
|
||||
|
||||
private final Context context;
|
||||
private final Uri uriKmzFile;
|
||||
|
||||
/**
|
||||
* @param context the context
|
||||
* @param uriFile URI of the kmz file.
|
||||
*/
|
||||
public KmzTrackImporter(Context context, Uri uriFile) {
|
||||
@NonNull
|
||||
public List<Track.Id> importFile(Context context, Uri fileUri) throws IOException {
|
||||
this.context = context;
|
||||
this.uriKmzFile = uriFile;
|
||||
}
|
||||
List<Track.Id> trackIds = findAndParseKmlFile(fileUri);
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
List<Track.Id> importFile(InputStream inputStream) {
|
||||
List<Track.Id> trackIds = findAndParseKmlFile(inputStream);
|
||||
|
||||
ArrayList<Track.Id> trackIdsWithImages = new ArrayList<>();
|
||||
List<Track.Id> trackIdsWithImages = new ArrayList<>();
|
||||
|
||||
for (Track.Id trackId : trackIds) {
|
||||
if (copyKmzImages(trackId)) {
|
||||
if (copyKmzImages(fileUri, trackId)) {
|
||||
trackIdsWithImages.add(trackId);
|
||||
deleteOrphanImages(context, trackId);
|
||||
deleteOrphanImages(trackId);
|
||||
} else {
|
||||
cleanImport(context, trackId);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -92,8 +77,8 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
*
|
||||
* @return false if there are errors or true otherwise.
|
||||
*/
|
||||
private boolean copyKmzImages(Track.Id trackId) {
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(uriKmzFile);
|
||||
private boolean copyKmzImages(Uri uri, Track.Id trackId) throws IOException {
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||
ZipEntry zipEntry;
|
||||
|
||||
@@ -112,9 +97,6 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,13 +138,9 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
return KMZ_IMAGES_EXT.contains(fileExt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it.
|
||||
*
|
||||
* @param inputStream kmz input stream.
|
||||
*/
|
||||
private List<Track.Id> findAndParseKmlFile(InputStream inputStream) {
|
||||
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||
private List<Track.Id> findAndParseKmlFile(Uri uri) throws IOException {
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||
ZipEntry zipEntry;
|
||||
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||
|
||||
@@ -192,19 +170,15 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
throw new ImportParserException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all images that remained in external storage that doesn't have a marker associated.
|
||||
*
|
||||
* @param context the Context object.
|
||||
* @param trackId the id of the Track.
|
||||
*/
|
||||
private void deleteOrphanImages(Context context, Track.Id trackId) {
|
||||
private void deleteOrphanImages(Track.Id trackId) {
|
||||
// 1.- Gets all photo names in the markers of the track identified by id.
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
List<Marker> markers = contentProviderUtils.getMarkers(trackId);
|
||||
@@ -230,44 +204,17 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up import.
|
||||
*
|
||||
* @param trackId the trackId
|
||||
*/
|
||||
private void cleanImport(Context context, Track.Id trackId) {
|
||||
if (PreferencesUtils.isRecording(trackId)) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
}
|
||||
}
|
||||
private List<Track.Id> parseKml(ZipInputStream zipInputStream) throws IOException {
|
||||
XMLImporter kmlFileTrackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
|
||||
private List<Track.Id> parseKml(ZipInputStream zipInputStream) {
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
|
||||
|
||||
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
|
||||
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
||||
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new ImportParserException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the kml as byte array.
|
||||
*
|
||||
* @param zipInputStream the zip input stream
|
||||
*/
|
||||
private byte[] getKml(ZipInputStream zipInputStream) throws IOException {
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int count;
|
||||
while ((count = zipInputStream.read(buffer)) != -1) {
|
||||
byteArrayOutputStream.write(buffer, 0, count);
|
||||
InputStream nonClosableInputStream = new FilterInputStream(zipInputStream) {
|
||||
@Override
|
||||
public void close() {
|
||||
// SAX2 always tries close InputStreams; but that would also close our ZIP file.
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
};
|
||||
|
||||
return kmlFileTrackImporter.importFile(nonClosableInputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,6 +224,7 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
* @param trackId the track's id which image belongs to.
|
||||
* @param fileName the file name
|
||||
*/
|
||||
@Deprecated //TODO Use JDK9's inputStream.transferTo() instead of manual buffer
|
||||
private void readAndSaveImageFile(ZipInputStream zipInputStream, Track.Id trackId, String fileName) throws IOException {
|
||||
if (trackId == null || fileName.equals("")) {
|
||||
return;
|
||||
@@ -286,7 +234,7 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
File file = new File(dir, fileName);
|
||||
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
byte[] buffer = new byte[4096];
|
||||
int count;
|
||||
while ((count = zipInputStream.read(buffer)) != -1) {
|
||||
fileOutputStream.write(buffer, 0, count);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
|
||||
/**
|
||||
* Interface for a track importer.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public interface TrackImporter {
|
||||
|
||||
/**
|
||||
* Import a file.
|
||||
*
|
||||
* @param inputStream the file's input stream
|
||||
*/
|
||||
@NonNull
|
||||
List<Track.Id> importFile(InputStream inputStream) throws ImportParserException;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user