forked from upstream-mirrors/OpenTracks
Compare commits
230 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 | |||
| 0b30ecabb1 | |||
| 107cc14a2d | |||
| 6d3d8bb83c | |||
| 9c8e0335ef | |||
| 8c648c69e8 | |||
| 4422464f8d | |||
| d094531779 | |||
| a01341d235 | |||
| 83065dad39 | |||
| 68ba3e0c2a | |||
| 3f21edc44c | |||
| aefd1a6a35 | |||
| 74fa73e4a4 | |||
| 0a006d64f9 | |||
| 997ab6060e | |||
| 6d926ca7a8 | |||
| 3c1f1a9ba5 | |||
| ed5da55fba | |||
| d1e9649505 | |||
| 2431d60171 | |||
| e08f5e7273 | |||
| 7d2a6fff59 | |||
| a5158a55d9 | |||
| 15fbd90ca4 | |||
| eebc33f60c | |||
| ba2a755fb7 | |||
| 344c50a20c | |||
| 9a9a610321 | |||
| 8af427ea48 | |||
| 7b7d7c8061 | |||
| 7d6919d3f9 | |||
| 7eceb979c3 | |||
| bd48944ad5 | |||
| 503cba1315 | |||
| a313cb6244 | |||
| 7f0c6309c0 | |||
| 02c578327d | |||
| 7fec0dcff4 | |||
| 34eb7a833a | |||
| 5db3aa7ac1 | |||
| 47fd7c51a9 | |||
| 2f6f0b4e17 | |||
| 41b794f66b | |||
| e2c592367a | |||
| 7a24c56474 | |||
| 3f6bd6926a | |||
| 508af45449 | |||
| 0f89cd7875 | |||
| 29b522b8a2 | |||
| 14e63a1a54 | |||
| e559583d23 | |||
| 3b9711d82e | |||
| 114fc8255f | |||
| 26e655d58f | |||
| 815b6a3cf1 | |||
| 3dc8a724d8 | |||
| 70f571cd09 | |||
| 3f5c3a5c08 | |||
| a13465391f | |||
| 97454b5369 | |||
| f6a47c4594 | |||
| fce76230e1 | |||
| fdf4336a9b | |||
| 844c6e9609 | |||
| 784942ffd2 | |||
| edf4ccc941 | |||
| d90258294f | |||
| 01f4c13307 | |||
| 873956426d | |||
| 4128c54960 | |||
| 9978bddf21 | |||
| a9726b9a94 | |||
| 05ba52bab5 | |||
| ca95f40e77 | |||
| 5cb85c4383 | |||
| d0a477b112 | |||
| ec365a18ca | |||
| 0ff735e559 | |||
| 8a35835791 |
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
@@ -6,16 +6,18 @@ 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 }}
|
||||
arch: x86_64
|
||||
profile: Nexus 6
|
||||
script: ./gradlew connectedCheck
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -19,8 +20,16 @@ However, often only one value is provided.
|
||||
### Speed
|
||||
|
||||
* Garmin Speed Sensor 2
|
||||
* Has updatable firmware that requires an account for garmin.com
|
||||
* This sensor reports speed data as cadence.
|
||||
A workaround is in place.
|
||||
* This sensor sometimes counts backwards (unknown software version).
|
||||
A workaround is in place.
|
||||
|
||||
* 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.2'
|
||||
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 3862
|
||||
versionName "v3.15.1"
|
||||
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-alpha04'
|
||||
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,17 +2,7 @@
|
||||
interface TrackExporter {
|
||||
}
|
||||
class KmzTrackExporter implements TrackExporter
|
||||
KmzTrackExporter o-- FileTrackExporter
|
||||
class FileTrackExporter implements TrackExporter {
|
||||
/' One track only '/
|
||||
}
|
||||
FileTrackExporter o-- TrackWriter
|
||||
|
||||
interface TrackWriter {
|
||||
}
|
||||
class GpxTrackWriter implements TrackWriter
|
||||
class KmlTrackWriter implements TrackWriter
|
||||
|
||||
class KmlTrackExporter implements TrackExporter
|
||||
|
||||
interface TrackImporter {
|
||||
}
|
||||
|
||||
+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 {
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
<?xml version="1.0"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
|
||||
|
||||
<xs:element name="trackuuid"
|
||||
type="xs:string" />
|
||||
<xsd:element name="trackuuid"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
A unique track identifier stored as UUID.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
|
||||
</xs:schema>
|
||||
<xsd:element name="loss"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The lost elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="gain"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The gained elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
@@ -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,7 @@
|
||||
v3.15.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Updated translations
|
||||
|
||||
Bugfix:
|
||||
- Crash on GPX export
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.15.3: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Computation avg/max: heart rate, cadence, and power
|
||||
|
||||
Bugfix:
|
||||
- KML/KMZ do not export #start/#end placemarks anymore
|
||||
- While recording: Speed/location was sometimes falsly shown as 0.
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.15.4: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Voice annoucements interval should be in minutes (introduced in v3.15.0)
|
||||
@@ -0,0 +1,13 @@
|
||||
v3.16.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Cycling: get distance from BLE speed sensor
|
||||
- Reworked UI presented while recording
|
||||
- UI performance optimizations
|
||||
- Exported filenames: <First 32bits UUID>_<track's name>.<extension>
|
||||
- Import multiple files via Intent (e.g., from Gadgetbridge)
|
||||
- GPX: export power data
|
||||
- Updated translations
|
||||
|
||||
Bugfix:
|
||||
- Voice announcement use minutes correctly
|
||||
@@ -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,11 +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 {
|
||||
@@ -18,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.
|
||||
@@ -56,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++),
|
||||
@@ -64,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);
|
||||
@@ -103,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);
|
||||
@@ -115,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;
|
||||
}
|
||||
|
||||
@@ -148,6 +150,20 @@ 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) {
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null);
|
||||
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||
while (trackPointIterator.hasNext()) {
|
||||
trackPoints.add(trackPointIterator.next());
|
||||
}
|
||||
return trackPoints;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TestSensorDataUtil {
|
||||
|
||||
private final List<TrackPoint> trackPointList = new ArrayList<>();
|
||||
private final List<SensorData> sensorDataList = new ArrayList<>();
|
||||
|
||||
public void add(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
|
||||
sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type));
|
||||
TrackPoint tp = new TrackPoint(type);
|
||||
int i = trackPointList.size() + 1;
|
||||
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
tp.setHeartRate_bpm(hr);
|
||||
tp.setCyclingCadence_rpm(cadence);
|
||||
tp.setPower(power);
|
||||
tp.setAccuracy(1f);
|
||||
tp.setAltitude(1f);
|
||||
tp.setTime(time);
|
||||
tp.setSpeed(Speed.of(5f + (i / 10f)));
|
||||
tp.setAltitudeGain(3f);
|
||||
tp.setAltitudeLoss(3f);
|
||||
trackPointList.add(tp);
|
||||
}
|
||||
|
||||
public List<TrackPoint> getTrackPointList() {
|
||||
return this.trackPointList;
|
||||
}
|
||||
|
||||
public SensorDataStats computeStats() {
|
||||
if (sensorDataList.size() <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SensorDataStats stats = new SensorDataStats();
|
||||
long timeElapsed;
|
||||
long movingTime = 0;
|
||||
SensorData dataPrev = sensorDataList.get(0);
|
||||
stats.maxHr = dataPrev.hr;
|
||||
stats.maxCadence = dataPrev.cadence;
|
||||
SensorData dataCurrent;
|
||||
for (int i = 1; i < sensorDataList.size(); i++) {
|
||||
dataCurrent = sensorDataList.get(i);
|
||||
if (dataPrev.type != TrackPoint.Type.SEGMENT_START_MANUAL) {
|
||||
timeElapsed = dataCurrent.type != TrackPoint.Type.SEGMENT_START_MANUAL ? dataCurrent.time.getEpochSecond() - dataPrev.time.getEpochSecond() : 0;
|
||||
stats.avgHr += (dataPrev.hr * timeElapsed);
|
||||
stats.maxHr = Math.max(dataPrev.hr, stats.maxHr);
|
||||
stats.avgCadence += (dataPrev.cadence * timeElapsed);
|
||||
stats.maxCadence = Math.max(dataPrev.cadence, stats.maxCadence);
|
||||
stats.avgPower += (dataPrev.power * timeElapsed);
|
||||
|
||||
movingTime += timeElapsed;
|
||||
}
|
||||
dataPrev = dataCurrent;
|
||||
}
|
||||
|
||||
stats.avgHr /= movingTime;
|
||||
stats.avgCadence /= movingTime;
|
||||
stats.avgPower /= movingTime;
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private static class SensorData {
|
||||
final Instant time;
|
||||
final float hr;
|
||||
final float cadence;
|
||||
final float power;
|
||||
final TrackPoint.Type type;
|
||||
|
||||
public SensorData(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
|
||||
this.time = time;
|
||||
this.hr = hr == null ? 0 : hr;
|
||||
this.cadence = cadence == null ? 0 : cadence;
|
||||
this.power = power == null ? 0 : power;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorDataStats {
|
||||
public float avgHr = 0f;
|
||||
public float maxHr = 0f;
|
||||
public float avgCadence = 0f;
|
||||
public float maxCadence = 0f;
|
||||
public float avgPower = 0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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;
|
||||
|
||||
public class TrackPointTest {
|
||||
|
||||
@Test
|
||||
public void isRecent_true() {
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
tp.setTime(Instant.now());
|
||||
|
||||
assertTrue(tp.isRecent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isRecent_false() {
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
tp.setTime(Instant.now().minus(2, ChronoUnit.MINUTES));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
+366
-34
@@ -35,18 +35,24 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
@@ -90,29 +96,22 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noPoints() {
|
||||
testIterator(new Track.Id(1), 0, 1);
|
||||
testIterator(new Track.Id(1), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchAscending() {
|
||||
testIterator(new Track.Id(1), 50, 100);
|
||||
testIterator(new Track.Id(2), 50, 50);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchAscending() {
|
||||
testIterator(new Track.Id(1), 50, 11);
|
||||
testIterator(new Track.Id(2), 50, 25);
|
||||
public void testLocationIterator_noAscending() {
|
||||
testIterator(new Track.Id(1), 50);
|
||||
testIterator(new Track.Id(2), 50);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
testIterator(new Track.Id(1), 20000, 2000);
|
||||
testIterator(new Track.Id(1), 20000 / 2);
|
||||
}
|
||||
|
||||
private void testIterator(Track.Id trackId, int numPoints, int batchSize) {
|
||||
long lastPointId = initializeTrack(trackId, numPoints);
|
||||
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
||||
private void testIterator(Track.Id trackId, int numPoints) {
|
||||
TrackPoint.Id lastPointId = initializeTrack(trackId, numPoints);
|
||||
List<TrackPoint> locations = new ArrayList<>(numPoints);
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||
while (it.hasNext()) {
|
||||
@@ -120,13 +119,13 @@ public class CustomContentProviderUtilsTest {
|
||||
assertNotNull(trackPoint);
|
||||
locations.add(trackPoint);
|
||||
// Make sure the IDs are returned in the right order.
|
||||
assertEquals(lastPointId - numPoints + locations.size(), trackPoint.getId().getId());
|
||||
assertEquals(lastPointId.getId() - numPoints + locations.size(), trackPoint.getId().getId());
|
||||
}
|
||||
assertEquals(numPoints, locations.size());
|
||||
}
|
||||
}
|
||||
|
||||
private long initializeTrack(Track.Id id, int numPoints) {
|
||||
private TrackPoint.Id initializeTrack(Track.Id id, int numPoints) {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id.getId());
|
||||
@@ -146,17 +145,17 @@ public class CustomContentProviderUtilsTest {
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
|
||||
|
||||
// Load all inserted trackPoints.
|
||||
long lastPointId = -1;
|
||||
TrackPoint.Id lastPointId = null;
|
||||
int counter = 0;
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) {
|
||||
while (it.hasNext()) {
|
||||
TrackPoint trackPoint = it.next();
|
||||
lastPointId = trackPoint.getId().getId();
|
||||
lastPointId = trackPoint.getId();
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(numPoints == 0 || lastPointId > 0);
|
||||
assertTrue(numPoints == 0 || lastPointId.getId() > 0);
|
||||
assertEquals(numPoints, counter);
|
||||
|
||||
return lastPointId;
|
||||
@@ -431,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);
|
||||
@@ -567,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);
|
||||
@@ -609,7 +608,7 @@ public class CustomContentProviderUtilsTest {
|
||||
contentProviderUtils.insertMarker(marker3);
|
||||
contentProviderUtils.insertMarker(marker4);
|
||||
|
||||
assertEquals(4, contentProviderUtils.getNextMarkerNumber(trackId));
|
||||
assertEquals(Integer.valueOf(4), contentProviderUtils.getNextMarkerNumber(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -766,9 +765,9 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
// when / then
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
|
||||
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
|
||||
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId);
|
||||
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
|
||||
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -810,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());
|
||||
}
|
||||
|
||||
@@ -825,7 +824,7 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
|
||||
assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -843,7 +842,7 @@ public class CustomContentProviderUtilsTest {
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertTrue(trackPoints.get(10).hasHeartRate());
|
||||
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
|
||||
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
|
||||
@@ -875,7 +874,7 @@ public class CustomContentProviderUtilsTest {
|
||||
.map(TrackPoint.Id::new).collect(Collectors.toList());
|
||||
|
||||
// when
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8), 5);
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8));
|
||||
|
||||
// then
|
||||
assertEquals(2, cursor.getCount());
|
||||
@@ -927,4 +926,337 @@ public class CustomContentProviderUtilsTest {
|
||||
assertEquals("12", ContentProviderUtils.formatIdListForUri(new Track.Id(12)));
|
||||
assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new Track.Id(42), new Track.Id(43), new Track.Id(44)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_noSensorData() {
|
||||
// given
|
||||
List<TrackPoint> trackPointList = new ArrayList<>();
|
||||
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
|
||||
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
|
||||
trackPoint.setPower(null);
|
||||
trackPoint.setCyclingCadence_rpm(null);
|
||||
trackPoint.setHeartRate_bpm(null);
|
||||
trackPointList.add(trackPoint);
|
||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, trackPointList);
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_needAtLeastTwoTrackPointsFalse() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 90 300 0
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 90f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_needAtLeastTwoTrackPointsTrue() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 90 300 -1
|
||||
* 1 140 90 300 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 90f, 300f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), 140f, 90f, 300f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertTrue(sensorStatistics.hasHeartRate());
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertTrue(sensorStatistics.hasCadence());
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertTrue(sensorStatistics.hasPower());
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_onlyHr() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 NULL NULL -1
|
||||
* 1 140 NULL NULL 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, null, null, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), 140f, null, null, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertTrue(sensorStatistics.hasHeartRate());
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_onlyCadence() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 NULL 90 NULL -1
|
||||
* 1 NULL 90 NULL 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, null, 90f, null, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), null, 90f, null, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertTrue(sensorStatistics.hasCadence());
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertFalse(sensorStatistics.hasPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_onlyPower() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 NULL NULL 300 -1
|
||||
* 1 NULL NULL 300 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, null, null, 300f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(1, ChronoUnit.SECONDS), null, null, 300f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertFalse(sensorStatistics.hasHeartRate());
|
||||
assertFalse(sensorStatistics.hasCadence());
|
||||
assertTrue(sensorStatistics.hasPower());
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 75 250 -1
|
||||
* 2 148 80 300 0
|
||||
* 1 150 82 325 0
|
||||
* 7 160 90 275 0
|
||||
* 4 155 85 280 0
|
||||
* 1 155 84 295 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 75f, 250f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(2, ChronoUnit.SECONDS), 148f, 80f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(3, ChronoUnit.SECONDS), 150f, 82f, 325f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(10, ChronoUnit.SECONDS), 160f, 90f, 275f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(14, ChronoUnit.SECONDS), 155f, 85f, 280f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(15, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_withManualResume() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 75 250 -1
|
||||
* 2 148 80 300 0
|
||||
* 1 150 82 325 0
|
||||
* 3 174 88 400 0
|
||||
* 20 127 54 175 -2
|
||||
* 3 160 90 275 0
|
||||
* 7 155 85 280 0
|
||||
* 3 150 90 267 0
|
||||
* 3 170 90 240 0
|
||||
* 2 155 84 295 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 75f, 250f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(2, ChronoUnit.SECONDS), 148f, 80f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(3, ChronoUnit.SECONDS), 150f, 82f, 325f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(6, ChronoUnit.SECONDS), 174f, 88f, 400f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(26, ChronoUnit.SECONDS), 127f, 54f, 175f, TrackPoint.Type.SEGMENT_START_MANUAL);
|
||||
sensorDataUtil.add(start.plus(29, ChronoUnit.SECONDS), 160f, 90f, 275f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(36, ChronoUnit.SECONDS), 155f, 85f, 280f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(39, ChronoUnit.SECONDS), 150f, 90f, 267f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(42, ChronoUnit.SECONDS), 170f, 90f, 240f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(44, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_withStartAutomatic() {
|
||||
// given
|
||||
/*
|
||||
* time elapsed hr cadence power track type
|
||||
* 0 140 75 250 -1
|
||||
* 2 148 80 300 0
|
||||
* 1 150 82 325 0
|
||||
* 3 174 88 400 0
|
||||
* 20 127 54 175 -1
|
||||
* 3 160 90 275 0
|
||||
* 7 155 85 280 0
|
||||
* 3 150 90 267 0
|
||||
* 3 170 90 240 0
|
||||
* 2 155 84 295 1
|
||||
*/
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
sensorDataUtil.add(start, 140f, 75f, 250f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(2, ChronoUnit.SECONDS), 148f, 80f, 300f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(3, ChronoUnit.SECONDS), 150f, 82f, 325f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(6, ChronoUnit.SECONDS), 174f, 88f, 400f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(26, ChronoUnit.SECONDS), 127f, 54f, 175f, TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
sensorDataUtil.add(start.plus(29, ChronoUnit.SECONDS), 160f, 90f, 275f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(36, ChronoUnit.SECONDS), 155f, 85f, 280f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(39, ChronoUnit.SECONDS), 150f, 90f, 267f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(42, ChronoUnit.SECONDS), 170f, 90f, 240f, TrackPoint.Type.TRACKPOINT);
|
||||
sensorDataUtil.add(start.plus(44, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
private void testGetSensorStats_randomData(int totalPoints, boolean withStartSegments) {
|
||||
// given
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < totalPoints; i++) {
|
||||
int randomNum = withStartSegments ? random.nextInt(50) - 2 : 0;
|
||||
TrackPoint.Type type = randomNum >= 0 ? TrackPoint.Type.TRACKPOINT : TrackPoint.Type.getById(randomNum);
|
||||
float randomHr = random.nextFloat() * (200f - 90f) + 90f;
|
||||
float randomCadence = random.nextFloat() * (110f - 40f) + 40f;
|
||||
float randomPower = random.nextFloat() * (500f - 100f) + 100f;
|
||||
sensorDataUtil.add(start.plus(i, ChronoUnit.SECONDS), randomHr, randomCadence, randomPower, type);
|
||||
}
|
||||
sensorDataUtil.add(start.plus(totalPoints, ChronoUnit.SECONDS), 155f, 84f, 295f, TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
|
||||
Track.Id trackId = new Track.Id(start.toEpochMilli());
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track, sensorDataUtil.getTrackPointList());
|
||||
|
||||
// when
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
// then
|
||||
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0.01f);
|
||||
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0.01f);
|
||||
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0.01f);
|
||||
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0.01f);
|
||||
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_veryLongActivity12h() {
|
||||
testGetSensorStats_randomData(43200 / 6, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_withSeveralRandomStartSegments() {
|
||||
testGetSensorStats_randomData(5000, true);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-10
@@ -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;
|
||||
@@ -96,34 +97,36 @@ public class SensorDataCyclingTest {
|
||||
@Test
|
||||
public void compute_speed() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
|
||||
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(), 0.01);
|
||||
assertEquals(2.15, current.getValue().distance.toM(), 0.01);
|
||||
assertEquals(1.20, current.getValue().speed.toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed_rollOverCount() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2000);
|
||||
current.compute(previous, Distance.ofMM(2000));
|
||||
|
||||
// then
|
||||
assertEquals(2, current.getValue(), 0.01);
|
||||
assertEquals(2, current.getValue().distance.toM(), 0.01);
|
||||
assertEquals(2, current.getValue().speed.toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equals_speed_with_no_data() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress");
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
previous.toString();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+166
-130
@@ -2,11 +2,14 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
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,15 +17,14 @@ 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;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -30,16 +32,18 @@ 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;
|
||||
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;
|
||||
@@ -51,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();
|
||||
@@ -76,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<>();
|
||||
@@ -84,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), 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), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50), 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), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50), 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);
|
||||
@@ -109,7 +138,7 @@ public class ExportImportTest {
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
markers = contentProviderUtils.getMarkers(trackId);
|
||||
assertEquals(10, trackPoints.size());
|
||||
assertEquals(2, markers.size());
|
||||
@@ -123,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);
|
||||
|
||||
@@ -141,14 +164,13 @@ 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);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -161,10 +183,10 @@ public class ExportImportTest {
|
||||
assertEquals(track.getUuid(), importedTrack.getUuid());
|
||||
|
||||
// 2. trackpoints
|
||||
assertTrackpoints(trackPoints, false, false, false, false, false);
|
||||
assertTrackpoints(trackPoints, false, false, false, false, false, false);
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(false, false);
|
||||
assertTrackStatistics(false, false, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -172,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);
|
||||
|
||||
@@ -180,14 +204,13 @@ 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);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -199,10 +222,49 @@ public class ExportImportTest {
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. trackpoints
|
||||
assertTrackpoints(trackPoints, true, true, true, true, true);
|
||||
assertTrackpoints(trackPoints, true, true, true, true, true, true);
|
||||
|
||||
// 2. trackstatistics
|
||||
assertTrackStatistics(false, true);
|
||||
assertTrackStatistics(false, true, true);
|
||||
|
||||
// 4. markers
|
||||
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();
|
||||
@@ -210,63 +272,35 @@ public class ExportImportTest {
|
||||
|
||||
@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(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);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new KmlFileTrackImporter(context));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNull(importedTrack);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_only_track() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
public void gpx() throws TimeoutException, IOException {
|
||||
setUp(true);
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata_and_pictures() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx() {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
@@ -274,14 +308,13 @@ 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);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context, contentProviderUtils));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -301,10 +334,10 @@ public class ExportImportTest {
|
||||
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
|
||||
assertTrackpoints(trackPointsWithCoordinates, false, true, true, true, true);
|
||||
assertTrackpoints(trackPointsWithCoordinates, true, true, true, true, true, false);
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(true, true);
|
||||
assertTrackStatistics(true, true, false);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -312,22 +345,25 @@ 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(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);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter trackImporter = new XMLImporter(new GpxFileTrackImporter(context, contentProviderUtils));
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -354,9 +390,8 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss) {
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
|
||||
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());
|
||||
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
@@ -378,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)) {
|
||||
@@ -391,24 +426,27 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
if (verifyHeartrate) {
|
||||
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
assertEquals("" + trackPoint, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
}
|
||||
if (verifyCadence) {
|
||||
assertEquals(trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
assertEquals("" + trackPoint, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
}
|
||||
if (verifyPower) {
|
||||
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss) {
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyAltitudeGainAndLoss, boolean verifyDistance) {
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
|
||||
assertNotNull(importedTrack.getTrackStatistics());
|
||||
@@ -426,40 +464,38 @@ public class ExportImportTest {
|
||||
assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
|
||||
|
||||
// Distance
|
||||
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
|
||||
if (verifyDistance) {
|
||||
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, long speed, long altitude, float elevationGain, float heartRate, float cyclingCadence, float power) {
|
||||
Location location = new Location("");
|
||||
location.setTime(time);
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
location.setAccuracy(accuracy);
|
||||
location.setAltitude(altitude);
|
||||
location.setSpeed(speed);
|
||||
|
||||
TrackPoint tp = new TrackPoint(location);
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
+14
-12
@@ -11,9 +11,11 @@ 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;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -47,14 +49,14 @@ 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
|
||||
// 1. import
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -69,7 +71,7 @@ public class LegacyImportTest {
|
||||
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(6, importedTrackPoints.size());
|
||||
|
||||
// first 3 trackpoints
|
||||
@@ -89,13 +91,13 @@ 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
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,14 +105,14 @@ 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
|
||||
// 1. import
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -122,7 +124,7 @@ public class LegacyImportTest {
|
||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(6, importedTrackPoints.size());
|
||||
|
||||
// first segment
|
||||
@@ -150,7 +152,7 @@ public class LegacyImportTest {
|
||||
if (altitude == null) {
|
||||
assertFalse(trackPoint.hasAltitude());
|
||||
} else {
|
||||
assertEquals(altitude, (Double) trackPoint.getAltitude());
|
||||
assertEquals(altitude, (Double) trackPoint.getAltitude().toM());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-9
@@ -11,10 +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;
|
||||
@@ -25,7 +27,10 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private TrackPoint locationMock;
|
||||
private TrackPoint trackPointMock;
|
||||
|
||||
@Mock
|
||||
private TrackStatistics trackStatisticsMock;
|
||||
|
||||
@Mock
|
||||
private NotificationCompat.Builder notificationCompatBuilder;
|
||||
@@ -35,18 +40,20 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
|
||||
@Test
|
||||
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||
when(locationMock.hasAccuracy()).thenReturn(true);
|
||||
when(locationMock.getAccuracy()).thenReturn(999f);
|
||||
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
|
||||
when(trackPointMock.hasAccuracy()).thenReturn(true);
|
||||
when(trackPointMock.getAccuracy()).thenReturn(999f);
|
||||
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);
|
||||
subject.setMetricUnits(true);
|
||||
|
||||
// when
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 1000);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 1000);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
|
||||
// then
|
||||
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||
|
||||
+61
-69
@@ -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;
|
||||
@@ -45,6 +46,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -82,8 +84,13 @@ 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);
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
|
||||
@@ -102,7 +109,6 @@ public class TrackRecordingServiceTest {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
@@ -112,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();
|
||||
}
|
||||
@@ -145,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());
|
||||
@@ -184,13 +193,14 @@ 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();
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
|
||||
assertEquals(1, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
@@ -200,14 +210,15 @@ 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
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
@@ -218,23 +229,23 @@ 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
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
// then
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
//when
|
||||
service.resumeTrack(trackId);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(3, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
@@ -245,12 +256,13 @@ 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();
|
||||
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
@@ -258,9 +270,8 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(4, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
@@ -272,68 +283,49 @@ 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();
|
||||
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
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(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(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
|
||||
@@ -341,15 +333,15 @@ public class TrackRecordingServiceTest {
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(PreferencesUtils.isRecording(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
|
||||
@@ -363,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(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setBoolean(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);
|
||||
@@ -422,7 +415,6 @@ public class TrackRecordingServiceTest {
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
|
||||
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
|
||||
trackRecordingService.newTrackPoint(trackPoint, 50);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-8
@@ -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;
|
||||
@@ -23,6 +24,7 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -43,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
|
||||
@@ -54,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() {
|
||||
@@ -62,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
|
||||
@@ -76,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
|
||||
@@ -104,7 +113,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(8, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -138,7 +147,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(4, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -185,7 +194,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
@@ -214,7 +223,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(6, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -260,7 +269,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(8, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
@@ -292,7 +301,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(7, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
|
||||
+42
-23
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -53,6 +53,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@@ -77,7 +78,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().apply();
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
@@ -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(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(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(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(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,65 +137,77 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(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(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(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(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();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(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(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(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(context));
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
long startTime = System.currentTimeMillis();
|
||||
@@ -223,7 +243,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setTime(startTime + i * 10000);
|
||||
location.setBearing(3.0f);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
service.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
if (i % 7 == 0) {
|
||||
@@ -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());
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -25,6 +26,9 @@ public class HandlerServerTest {
|
||||
@Mock
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
@Mock
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private HandlerServer subject;
|
||||
|
||||
@Before
|
||||
@@ -41,10 +45,10 @@ public class HandlerServerTest {
|
||||
@Test
|
||||
public void onSharedPreferenceChanged() {
|
||||
// when
|
||||
subject.onSharedPreferenceChanged(null, null, null);
|
||||
subject.onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
|
||||
// then
|
||||
verify(locationHandler).onSharedPreferenceChanged(null, null, null);
|
||||
verify(locationHandler).onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+4
-4
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
|
||||
public class LocationHandlerTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
@Mock
|
||||
private HandlerServer handlerServer;
|
||||
@@ -43,11 +44,10 @@ public class LocationHandlerTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onStart(context);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class LocationHandlerTest {
|
||||
@Test
|
||||
public void testOnLocationChanged_poorAccuracy() {
|
||||
// given
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
|
||||
// when
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
|
||||
+172
-14
@@ -1,38 +1,196 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
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;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackStatisticsUpdaterTest {
|
||||
|
||||
private static final Distance GPS_DISTANCE = Distance.of(50);
|
||||
|
||||
@Test
|
||||
public void addTrackPoint() {
|
||||
public void addTrackPoint_TestingTrack() {
|
||||
// given
|
||||
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
|
||||
|
||||
// when
|
||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(it -> updater.addTrackPoint(it, 50));
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE));
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = updater.getTrackStatistics();
|
||||
assertEquals(85.35, statistics.getTotalDistance(), 0.01);
|
||||
assertEquals(Duration.ofMillis(13999), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(6), statistics.getMovingTime());
|
||||
TrackStatistics statistics = subject.getTrackStatistics();
|
||||
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
|
||||
public void addTrackPoint_distance_from_GPS_not_moving() {
|
||||
// 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));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_moving() {
|
||||
// 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.setSpeed(Speed.of(5f));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() {
|
||||
// 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));
|
||||
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(Distance.of(10f));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
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().toM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_not_moving_and_sensor_moving() {
|
||||
// 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));
|
||||
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(Distance.of(10f));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
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().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")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_not_moving() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_moving() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_not_moving_and_sensor_speed() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() {
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ import org.junit.runner.RunWith;
|
||||
import java.time.Duration;
|
||||
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.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -26,44 +27,146 @@ public class AnnouncementUtilsTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric() {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
|
||||
|
||||
public void getAnnouncement_metric_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
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, "airplane", null);
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_metric() {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
|
||||
|
||||
public void getAnnouncement_withInterval_metric_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
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, 1000);
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
String expected = "OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour Lap speed of 51.2 kilometers per hour";
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", lastInterval);
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals(expected, announcement);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertNull(sensor.getSpeed());
|
||||
assertNull(sensor.getDistanceSpeed());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,7 +60,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertNull(sensor.getCadence());
|
||||
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,7 +73,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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
|
||||
public void testIsExportFileExists_exists() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_other name name.kmz");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertTrue(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists1() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "gpx";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists2() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,14 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_ok() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
|
||||
@@ -36,12 +38,13 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_invalid() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
@@ -50,12 +53,13 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_noValue() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,18 +96,12 @@ public class StringUtilsTest {
|
||||
assertEquals("<![CDATA[hello]]]]><![CDATA[>there]]>", StringUtils.formatCData("hello]]>there"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with fractional seconds.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_fractional() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with time zone.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_timezone() {
|
||||
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
|
||||
@@ -118,9 +114,6 @@ public class StringUtilsTest {
|
||||
assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with fractional seconds and time zone.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_fractionalAndTimezone() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
@@ -173,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -46,12 +47,14 @@ public class TrackNameUtilsTest {
|
||||
|
||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
|
||||
|
||||
/**
|
||||
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_date_local() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
|
||||
@@ -60,7 +63,7 @@ public class TrackNameUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_date_iso_8601() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
|
||||
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
@@ -70,7 +73,7 @@ public class TrackNameUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_number() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
||||
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
}
|
||||
|
||||
+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" />
|
||||
@@ -73,11 +69,13 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/gpx+xml" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/gpx" />
|
||||
</intent-filter>
|
||||
@@ -135,6 +133,7 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/vnd.google-earth.kmz" />
|
||||
</intent-filter>
|
||||
@@ -192,6 +191,8 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/vnd.google-earth.kml+xml" />
|
||||
</intent-filter>
|
||||
@@ -345,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(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,11 +84,9 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
// Setup UI elements
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.marker_types, android.R.layout.simple_dropdown_item_1line);
|
||||
viewBinding.markerEditMarkerType.setAdapter(adapter);
|
||||
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> {
|
||||
viewModel.onPhotoDelete(viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString());
|
||||
});
|
||||
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> viewModel.onPhotoDelete(viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString()));
|
||||
|
||||
viewBinding.markerEditCancel.setOnClickListener(v -> {
|
||||
viewModel.onCancel();
|
||||
|
||||
@@ -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 preferences, String key) {
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(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(null, 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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -23,35 +24,22 @@ 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.
|
||||
PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
// 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);
|
||||
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
|
||||
PreferencesUtils.setString(sharedPreferences, this, R.string.stats_units_key, statsUnits);
|
||||
}
|
||||
|
||||
ActivityUtils.applyNightMode(this);
|
||||
ActivityUtils.applyNightMode(sharedPreferences, this);
|
||||
|
||||
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
|
||||
if (PreferencesUtils.isRecording(this)) {
|
||||
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
|
||||
PreferencesUtils.setLong(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.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -29,6 +30,7 @@ import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -36,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;
|
||||
@@ -46,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;
|
||||
@@ -67,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;
|
||||
|
||||
@@ -84,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() {
|
||||
@@ -103,29 +109,16 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
}
|
||||
};
|
||||
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
|
||||
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(() -> {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
loaderCallbacks.restart();
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -139,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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -177,35 +164,26 @@ 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) -> {
|
||||
Intent newIntent;
|
||||
if (trackId == recordingTrackId.getId()) {
|
||||
if (recordingStatus.isRecording() && trackId == recordingStatus.getTrackId().getId()) {
|
||||
// Is recording -> open record activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
startActivity(newIntent);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
this,
|
||||
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||
startActivity(newIntent, activityOptions.toBundle());
|
||||
}
|
||||
startActivity(newIntent);
|
||||
});
|
||||
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@@ -222,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);
|
||||
}
|
||||
@@ -257,18 +235,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
super.onStart();
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// Update UI
|
||||
trackController.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
@@ -276,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
|
||||
@@ -309,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);
|
||||
@@ -317,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);
|
||||
@@ -335,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);
|
||||
}
|
||||
@@ -368,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);
|
||||
}
|
||||
|
||||
@@ -379,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();
|
||||
}
|
||||
|
||||
@@ -409,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() {
|
||||
@@ -436,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);
|
||||
@@ -490,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]);
|
||||
@@ -502,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);
|
||||
@@ -567,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,8 @@ 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;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@@ -39,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;
|
||||
@@ -51,10 +56,13 @@ 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();
|
||||
|
||||
public static final String VIEW_TRACK_ICON = "track_icon";
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
@@ -62,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);
|
||||
@@ -84,11 +122,19 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
if (savedInstanceState != null) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -102,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
|
||||
@@ -138,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(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);
|
||||
@@ -193,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);
|
||||
}
|
||||
|
||||
@@ -245,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:
|
||||
@@ -275,4 +334,13 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,38 +88,29 @@ 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
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
|
||||
@@ -170,7 +156,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this);
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
@@ -182,7 +168,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
@@ -192,7 +178,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setFullscreenPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
@@ -206,7 +192,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
super.onStart();
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
trackDataHub.start();
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -134,11 +135,13 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO Check preference handling.
|
||||
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
String category = aggregatedStatistic.getCategory();
|
||||
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context, category);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
||||
|
||||
sportIcon.setImageResource(getIcon(aggregatedStatistic));
|
||||
typeLabel.setText(category);
|
||||
|
||||
@@ -14,8 +14,8 @@ import java.util.concurrent.Future;
|
||||
*/
|
||||
class ExecutorListViewService<T> {
|
||||
|
||||
private List<T> enqueueObjects = new ArrayList<>();
|
||||
private ExecutorService executorService;
|
||||
private final List<T> enqueueObjects = new ArrayList<>();
|
||||
private final ExecutorService executorService;
|
||||
|
||||
public ExecutorListViewService(int numThreads) {
|
||||
executorService = Executors.newFixedThreadPool(numThreads);
|
||||
@@ -23,7 +23,6 @@ class ExecutorListViewService<T> {
|
||||
|
||||
public void shutdown() {
|
||||
enqueueObjects.clear();
|
||||
enqueueObjects = null;
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
@@ -39,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,72 +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(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;
|
||||
}
|
||||
|
||||
@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(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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,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 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;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public class ScrollVisibleViews implements AbsListView.OnScrollListener {
|
||||
private int from = -1;
|
||||
private int to = -1;
|
||||
|
||||
private VisibleViewsListener visibleViewsListener;
|
||||
private final VisibleViewsListener visibleViewsListener;
|
||||
|
||||
public ScrollVisibleViews(@NonNull VisibleViewsListener visibleViewsListener) {
|
||||
this.visibleViewsListener = visibleViewsListener;
|
||||
|
||||
@@ -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,19 +75,23 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int targetNumPoints;
|
||||
|
||||
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;
|
||||
@@ -138,15 +146,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
||||
|
||||
|
||||
PreferencesUtils.register(context, this);
|
||||
onSharedPreferenceChanged(null, null);
|
||||
handler.post(() -> {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@@ -155,8 +154,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
return;
|
||||
}
|
||||
|
||||
PreferencesUtils.unregister(context, this);
|
||||
|
||||
started = false;
|
||||
|
||||
//Unregister listeners
|
||||
@@ -170,6 +167,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
handlerThread = null;
|
||||
}
|
||||
handler = null;
|
||||
trackStatisticsUpdater = null;
|
||||
}
|
||||
|
||||
public void loadTrack(final @NonNull Track.Id trackId) {
|
||||
@@ -210,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(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
}
|
||||
});
|
||||
return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId()) && recordingStatus.isPaused();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,10 +351,11 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = null;
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
||||
|
||||
while (trackPointIterator.hasNext()) {
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
trackPoint = trackPointIterator.next();
|
||||
TrackPoint.Id trackPointId = trackPoint.getId();
|
||||
|
||||
// Stop if past the last wanted point
|
||||
@@ -376,6 +363,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
break;
|
||||
}
|
||||
|
||||
correctAltitude(trackPoint);
|
||||
|
||||
if (localFirstSeenTrackPointId == null) {
|
||||
localFirstSeenTrackPointId = trackPointId;
|
||||
}
|
||||
@@ -385,34 +374,58 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
localNumLoadedTrackPoints++;
|
||||
localLastSeenTrackPointIdId = trackPointId;
|
||||
}
|
||||
}
|
||||
|
||||
if (trackPoint != null) {
|
||||
localLastSeenTrackPointIdId = trackPoint.getId();
|
||||
}
|
||||
|
||||
if (updateSamplingState) {
|
||||
numLoadedPoints = localNumLoadedTrackPoints;
|
||||
firstSeenTrackPointId = localFirstSeenTrackPointId;
|
||||
lastSeenTrackPointId = localLastSeenTrackPointIdId;
|
||||
}
|
||||
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
listener.onNewTrackPointsDone();
|
||||
if (trackPoint != null) {
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
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.
|
||||
*/
|
||||
@@ -420,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;
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
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.
|
||||
@@ -32,6 +36,7 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
//TODO Could be @NonNull
|
||||
void onTrackUpdated(Track track);
|
||||
|
||||
/**
|
||||
@@ -44,20 +49,23 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledInTrackPoint(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(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();
|
||||
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,14 +101,28 @@ 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Track track = (Track) o;
|
||||
return id.equals(track.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
public static class Id implements Parcelable {
|
||||
|
||||
private final long id;
|
||||
|
||||
@@ -20,7 +20,9 @@ import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -42,15 +44,18 @@ import java.util.Objects;
|
||||
*/
|
||||
public class TrackPoint {
|
||||
|
||||
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
||||
|
||||
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 Distance sensorDistance_m;
|
||||
|
||||
public enum Type {
|
||||
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
|
||||
@@ -86,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;
|
||||
@@ -98,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());
|
||||
@@ -110,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;
|
||||
}
|
||||
|
||||
@@ -208,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() {
|
||||
@@ -246,28 +251,43 @@ public class TrackPoint {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public boolean isRecent() {
|
||||
return Instant.now()
|
||||
.isBefore(time.plus(MAX_LOCATION_AGE));
|
||||
}
|
||||
|
||||
|
||||
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().isMoving();
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
@@ -294,8 +314,16 @@ public class TrackPoint {
|
||||
this.accuracy = horizontalAccuracy;
|
||||
}
|
||||
|
||||
public float distanceTo(@NonNull TrackPoint dest) {
|
||||
return getLocation().distanceTo(dest.getLocation());
|
||||
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
if (hasSensorDistance()) {
|
||||
return getSensorDistance();
|
||||
}
|
||||
|
||||
return Distance.of(getLocation().distanceTo(previous.getLocation()));
|
||||
}
|
||||
|
||||
public boolean fulfillsAccuracy(int poorAccuracy) {
|
||||
return hasAccuracy() && accuracy < poorAccuracy;
|
||||
}
|
||||
|
||||
public float bearingTo(@NonNull TrackPoint dest) {
|
||||
@@ -307,6 +335,18 @@ public class TrackPoint {
|
||||
}
|
||||
|
||||
// Sensor data
|
||||
public boolean hasSensorDistance() {
|
||||
return sensorDistance_m != null;
|
||||
}
|
||||
|
||||
public Distance getSensorDistance() {
|
||||
return sensorDistance_m;
|
||||
}
|
||||
|
||||
public void setSensorDistance(Distance distance_m) {
|
||||
this.sensorDistance_m = distance_m;
|
||||
}
|
||||
|
||||
public boolean hasSensorData() {
|
||||
return hasHeartRate() || hasCyclingCadence() || hasPower();
|
||||
}
|
||||
@@ -327,7 +367,7 @@ public class TrackPoint {
|
||||
return cyclingCadence_rpm != null;
|
||||
}
|
||||
|
||||
public Float getCyclingCadence_rpm() {
|
||||
public float getCyclingCadence_rpm() {
|
||||
return cyclingCadence_rpm;
|
||||
}
|
||||
|
||||
@@ -339,7 +379,7 @@ public class TrackPoint {
|
||||
return power != null;
|
||||
}
|
||||
|
||||
public Float getPower() {
|
||||
public float getPower() {
|
||||
return power;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,17 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
String BEARING = "bearing";
|
||||
String SENSOR_HEARTRATE = "sensor_heartrate";
|
||||
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";
|
||||
String ALIAS_MAX_HR = "max_hr";
|
||||
String ALIAS_AVG_CADENCE = "avg_cadence";
|
||||
String ALIAS_MAX_CADENCE = "max_cadence";
|
||||
String ALIAS_AVG_POWER = "avg_power";
|
||||
|
||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
@@ -67,9 +75,10 @@ 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"
|
||||
+ ")";
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public interface TracksColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "tracks";
|
||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_SENSOR_STATS = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/sensorstats");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
|
||||
String DEFAULT_SORT_ORDER = _ID;
|
||||
@@ -51,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 + " ("
|
||||
@@ -71,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 + ")";
|
||||
|
||||
|
||||
+6
-4
@@ -19,9 +19,10 @@ class CachedTrackPointsIndexes {
|
||||
final int bearingIndex;
|
||||
final int sensorHeartRateIndex;
|
||||
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);
|
||||
@@ -35,8 +36,9 @@ class CachedTrackPointsIndexes {
|
||||
bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
|
||||
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-129
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
@@ -38,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;
|
||||
@@ -57,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
|
||||
@@ -69,7 +72,6 @@ public class ContentProviderUtils {
|
||||
private static final String ID_SEPARATOR = ",";
|
||||
|
||||
private final ContentResolver contentResolver;
|
||||
private int defaultCursorBatchSize = 2000;
|
||||
|
||||
public ContentProviderUtils(Context context) {
|
||||
contentResolver = context.getContentResolver();
|
||||
@@ -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);
|
||||
|
||||
@@ -175,7 +179,7 @@ public class ContentProviderUtils {
|
||||
|
||||
// Delete track last since it triggers a database vacuum call
|
||||
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id->Long.toString(id.getId())).toArray(String[]::new));
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new));
|
||||
}
|
||||
|
||||
public void deleteTrack(Context context, @NonNull Track.Id trackId) {
|
||||
@@ -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));
|
||||
@@ -555,15 +554,18 @@ public class ContentProviderUtils {
|
||||
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
|
||||
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
|
||||
}
|
||||
if (!cursor.isNull(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;
|
||||
@@ -578,24 +580,6 @@ public class ContentProviderUtils {
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first location id for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
@Deprecated
|
||||
public Track.Id getFirstTrackPointId(Track.Id trackId) {
|
||||
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return new Track.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last location id for a track.
|
||||
* Returns -1L if it doesn't exist.
|
||||
@@ -621,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())};
|
||||
@@ -646,9 +631,9 @@ public class ContentProviderUtils {
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting trackPoint id. `null` to ignore
|
||||
* @param maxLocations maximum number of locations to return. `null` for no limit
|
||||
*/
|
||||
public Cursor getTrackPointCursor(Track.Id trackId, TrackPoint.Id startTrackPointId, Integer maxLocations) {
|
||||
@NonNull
|
||||
public Cursor getTrackPointCursor(@NonNull Track.Id trackId, TrackPoint.Id startTrackPointId) {
|
||||
String selection;
|
||||
String[] selectionArgs;
|
||||
if (startTrackPointId != null) {
|
||||
@@ -659,11 +644,7 @@ public class ContentProviderUtils {
|
||||
selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||
}
|
||||
|
||||
String sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER;
|
||||
if (maxLocations != null) {
|
||||
sortOrder += " LIMIT " + maxLocations;
|
||||
}
|
||||
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
|
||||
return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -707,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());
|
||||
@@ -725,15 +706,18 @@ public class ContentProviderUtils {
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
|
||||
}
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
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;
|
||||
@@ -753,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()) {
|
||||
@@ -774,39 +759,6 @@ public class ContentProviderUtils {
|
||||
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
|
||||
List<TrackPoint> trackPoints = null;
|
||||
|
||||
try (Cursor trackPointCursor = getTrackPointCursor(trackId, null, null)) {
|
||||
if (trackPointCursor != null) {
|
||||
trackPointCursor.moveToFirst();
|
||||
trackPoints = new ArrayList<>(trackPointCursor.getCount());
|
||||
for (int i = 0; i < trackPointCursor.getCount(); i++) {
|
||||
trackPoints.add(createTrackPoint(trackPointCursor));
|
||||
trackPointCursor.moveToNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trackPoints;
|
||||
}
|
||||
|
||||
int getDefaultCursorBatchSize() {
|
||||
return defaultCursorBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default cursor batch size. For testing purpose.
|
||||
*
|
||||
* @param defaultCursorBatchSize the default cursor batch size
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
|
||||
this.defaultCursorBatchSize = defaultCursorBatchSize;
|
||||
}
|
||||
|
||||
|
||||
public static String formatIdListForUri(Track.Id... trackIds) {
|
||||
long[] ids = new long[trackIds.length];
|
||||
for (int i = 0; i < trackIds.length; i++) {
|
||||
@@ -837,30 +789,19 @@ public class ContentProviderUtils {
|
||||
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
||||
}
|
||||
|
||||
public SensorStatistics getSensorStats(Track.Id trackId) {
|
||||
if (trackId == null) {
|
||||
return null;
|
||||
}
|
||||
String[] projection = {
|
||||
"MAX(" + TrackPointsColumns.SENSOR_HEARTRATE + ") max_hr",
|
||||
"AVG(" + TrackPointsColumns.SENSOR_HEARTRATE + ") avg_hr",
|
||||
"MAX(" + TrackPointsColumns.SENSOR_CADENCE + ") max_cadence",
|
||||
"AVG(" + TrackPointsColumns.SENSOR_CADENCE + ") avg_cadence",
|
||||
"AVG(" + TrackPointsColumns.SENSOR_POWER + ") avg_power"
|
||||
};
|
||||
String selection = TrackPointsColumns.TRACKID + "=?";
|
||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||
public SensorStatistics getSensorStats(@NonNull Track.Id trackId) {
|
||||
SensorStatistics sensorStatistics = null;
|
||||
try (Cursor cursor = getTrackPointCursor(projection, selection, selectionArgs, null)) {
|
||||
try (Cursor cursor = contentResolver.query(ContentUris.withAppendedId(TracksColumns.CONTENT_URI_SENSOR_STATS, trackId.getId()), null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
sensorStatistics = new SensorStatistics(
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("max_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow("max_hr")) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow("avg_hr")) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("max_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow("max_cadence")) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow("avg_cadence")) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_power")) ? cursor.getFloat(cursor.getColumnIndexOrThrow("avg_power")) : null
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("max_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_HR)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_HR)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("max_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_CADENCE)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_CADENCE)) : null,
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_power")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_POWER)) : null
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
return sensorStatistics;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
|
||||
@@ -56,6 +57,38 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
private SQLiteDatabase db;
|
||||
|
||||
/**
|
||||
* The string representing the query that compute sensor stats from trackpoints table.
|
||||
* It computes the average for heart rate, cadence, and power (duration-based average) and the maximum for heart rate and cadence.
|
||||
* Finally, it ignores manual pause (SEGMENT_START_MANUAL).
|
||||
*/
|
||||
private final String SENSOR_STATS_QUERY =
|
||||
"WITH time_select as " +
|
||||
"(SELECT t1." + TrackPointsColumns.TIME + " * (t1." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")) time_value " +
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t1 " +
|
||||
"WHERE t1." + TrackPointsColumns._ID + " > t." + TrackPointsColumns._ID + " AND t1." + TrackPointsColumns.TRACKID + " = ? ORDER BY _id LIMIT 1) " +
|
||||
|
||||
"SELECT " +
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_HEARTRATE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_HR + ", " +
|
||||
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_HEARTRATE + ") " + TrackPointsColumns.ALIAS_MAX_HR + ", " +
|
||||
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_CADENCE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_CADENCE + ", " +
|
||||
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_CADENCE + ") " + TrackPointsColumns.ALIAS_MAX_CADENCE + ", " +
|
||||
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_POWER + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_POWER + " " +
|
||||
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t " +
|
||||
"WHERE t." + TrackPointsColumns.TRACKID + " = ? " +
|
||||
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
||||
|
||||
public CustomContentProvider() {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||
@@ -63,6 +96,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.TRACKPOINTS_BY_TRACKID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_SENSOR_STATS.getPath() + "/#", UrlType.TRACKS_SENSOR_STATS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||
@@ -223,6 +257,9 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
break;
|
||||
case TRACKS_SENSOR_STATS:
|
||||
long trackId = ContentUris.parseId(url);
|
||||
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
||||
case MARKERS:
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
||||
@@ -363,6 +400,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
TRACKPOINTS_BY_TRACKID,
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
TRACKS_SENSOR_STATS,
|
||||
MARKERS,
|
||||
MARKERS_BY_ID,
|
||||
MARKERS_BY_TRACKID
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user