Compare commits

...

109 Commits

Author SHA1 Message Date
Dennis Guse 42ae582cd8 Release: v4.24.2 2025-12-19 22:14:30 +01:00
frazou 6e0ad50397 Translated using Weblate (French)
Currently translated at 100.0% (544 of 544 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/fr/
2025-12-13 12:35:09 +01:00
Dennis Guse d43efad52a Bugfix: crash on select all tracks or all markers.
Fixes #2231.
2025-12-12 21:59:45 +01:00
Dennis Guse 693904529d Release: v4.24.1 2025-12-11 22:46:23 +01:00
Dennis Guse 08143a546a Bugfix: marker.icon column is still used by OSMDashboard v6.4.1
Fixes https://codeberg.org/OpenTracksApp/OSMDashboard/issues/552
2025-12-11 22:44:44 +01:00
Dennis Guse eb49156ee7 Bugfix: crash when using number as default track name.
Fixes #2229.
2025-12-11 18:24:47 +01:00
CI/CD Bot by pstorch 51c03d8d75 Update dependency com.android.tools.build:gradle to v8.13.2 2025-12-11 05:33:57 +00:00
CI/CD Bot by pstorch 666cbbad68 Update dependency org.mockito:mockito-android to v5.21.0 2025-12-09 19:34:11 +00:00
Dennis Guse 1cbc93c303 Release: v4.24.0 2025-12-09 07:41:29 +01:00
frazou afd1232940 Fix null race-condition for heart rate sensor data
The previous implementation was technically safe because the sensor
never had null values. Now that it can, the calls to getHeartRate
must be stored instead of repeated to avoid a race condition where the
sensor data become null between the null-check and the second call
2025-12-07 19:16:42 +01:00
frazou a3407ccbea Add an optional filter for invalid (0 bpm) heart rate readings 2025-12-07 19:16:42 +01:00
yosoyalguien c259f9b8bd Translated using Weblate (Spanish (Latin America))
Currently translated at 84.0% (457 of 544 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/es_419/
2025-12-07 12:15:12 +01:00
Stzyxh b1a556bf91 Translated using Weblate (German)
Currently translated at 6.2% (11 of 175 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://translate.codeberg.org/projects/opentracks/app-store-metadata-files/de/
2025-12-07 12:15:12 +01:00
tace16 ab70874d59 Translated using Weblate (Tamil)
Currently translated at 100.0% (175 of 175 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://translate.codeberg.org/projects/opentracks/app-store-metadata-files/ta/
2025-12-07 12:15:12 +01:00
Dennis Guse e6367b1dfa Cleanup: inline SelectionData. 2025-12-06 11:46:44 +01:00
Dennis Guse 8db381c448 Cleanup. 2025-12-06 10:25:11 +01:00
Dennis Guse ef111b7959 Cleanup: removed import functionality for legacy format (coordinates 100 and 200 where special).
Deadline was EOF 2021.
2025-12-06 10:23:21 +01:00
Dennis Guse 56c6cb1f19 Cleanup: removed CachedTrackPointsIndexes.
Caching already happens in SQLite.getColumnIndexOrThrow().
2025-12-06 10:05:24 +01:00
Dennis Guse 874c5eb8ae Cleanup. 2025-12-06 09:36:04 +01:00
Dennis Guse 1a087b1117 Bugfix: icon for activity motor bike was wrong. 2025-12-06 09:34:17 +01:00
Dennis Guse 3120cd8da0 README: description for KML/KMZ vs. GPX. 2025-12-06 09:31:36 +01:00
Dennis Guse 4263c000c0 Show coordinates with always with 5 decimals. 2025-12-06 09:18:06 +01:00
Dennis Guse f4073ead63 Removed KMZ export without photos. 2025-12-06 09:03:03 +01:00
Dennis Guse 2f90784442 Cleanup. 2025-12-06 08:47:06 +01:00
Dennis Guse 2cab394d03 Cleanup. 2025-12-06 08:41:57 +01:00
Dennis Guse 55f1bcb30b PublicAPI: StartRecording does not overwrite Track defaults if no data was provided.
Fixes #2214.
2025-12-06 08:34:17 +01:00
Dennis Guse 33a17784e6 Re-enable marker photo sharing.
Don't recall why it was disabled.
2025-12-06 08:27:48 +01:00
Dennis Guse 7cb97b0e6b Cleanup: use try-with-resources. 2025-12-06 08:27:01 +01:00
Dennis Guse 1cb3db496a Cleanup. 2025-12-05 08:05:25 +01:00
Dennis Guse d49d6c9857 Cleanup: AutoCloseableIterator now handles the cursor. 2025-12-05 08:05:25 +01:00
Dennis Guse 12f0c3313f ContentProviderUtils.searchMarkers uses MarkerIterator. 2025-12-05 08:05:25 +01:00
Dennis Guse a78e059612 Cleanup: ChartView doesn't show any Markers since a while.
Drawing Markers was removed in d8eed2ccc9
2025-12-05 08:05:25 +01:00
Dennis Guse 0774ee512f Cleanup: clarify options for Marker.TypeLocalized. 2025-12-05 08:05:25 +01:00
Dennis Guse 4c55468571 TrackList now has an AutoClosableIterator. 2025-12-05 08:05:25 +01:00
Dennis Guse 201f2d1b27 Cleanup: renaming Marker.category to typeLocalized. 2025-12-05 08:05:25 +01:00
Dennis Guse 595ad7f7b5 Marker list: split category and description into two fields. 2025-12-05 08:05:25 +01:00
Dennis Guse d518507193 Cleanup: use TextUtils.isEmpty(). 2025-12-05 08:05:25 +01:00
Dennis Guse 888f59133d Track list: split activity type localized and description into two fields. 2025-12-05 08:05:25 +01:00
Dennis Guse 5bb5b3b344 Track.ActivityType and Marker.Category are shown not wrapped in [] anymore. 2025-12-05 08:05:25 +01:00
Dennis Guse b584a95c9c Bugfix: crash when opening Marker Detail.
Introduced in 5b4b54694c
2025-12-05 08:05:25 +01:00
Dennis Guse 9ba152c469 Cleanup: use Position instead of Android's location. 2025-12-05 08:05:25 +01:00
Dennis Guse 1191f2e1e2 Cleanup: remove unused imports. 2025-12-05 08:05:25 +01:00
frazou e5aed794ba Fix idle discrepancy in UI 2025-11-30 22:49:50 +01:00
Dennis Guse 7956063598 Cleanup: Marker.icon was not used anymore. 2025-11-30 16:51:02 +01:00
Dennis Guse 0f356de2ca Gradle: add managed device. 2025-11-30 16:51:02 +01:00
Dennis Guse 5b4b54694c Marker is now a record. 2025-11-30 16:51:02 +01:00
Dennis Guse 9c271972d2 Cleanup. 2025-11-30 16:51:02 +01:00
CI/CD Bot by pstorch 744f11eaa0 Update actions/checkout action to v6 2025-11-30 14:34:10 +00:00
tace16 3c16f67882 Translated using Weblate (Tamil)
Currently translated at 100.0% (544 of 544 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/ta/
2025-11-27 20:48:11 +01:00
Dennis Guse 12d1a50995 Track is now a record.
Create Track directly.
2025-11-27 20:34:51 +01:00
Dennis Guse 21404fed50 Track is now a record.
Create Track directly.
2025-11-27 20:11:53 +01:00
Dennis Guse 990c91f978 Track is now a record. 2025-11-27 19:35:05 +01:00
Dennis Guse 6a262426a5 Delete all tracks (trackpoints and markers) works via cascade. 2025-11-27 17:14:11 +01:00
Dennis Guse 1b2998f423 CustomContentProvider: limit URIs for updates. 2025-11-27 17:14:11 +01:00
Dennis Guse d24dd2b005 Cleanup. 2025-11-27 17:14:11 +01:00
Dennis Guse 341fd02f9a Cleanup. 2025-11-26 22:48:00 +01:00
Dennis Guse 6833ea7282 Use MarkerIterator for TrackDataHub. 2025-11-26 22:46:39 +01:00
Dennis Guse a1da53864e Use MarkerIterator for MarkerDetailActivity. 2025-11-26 22:10:53 +01:00
Dennis Guse 28d49de047 Cleanup: removed ContentProviderUtils.getMarkers(). 2025-11-26 21:51:07 +01:00
Dennis Guse 4ae9d24960 Implemented MarkerIterator. 2025-11-26 21:42:11 +01:00
Dennis Guse 0b0b1a0d8d Cleanup. 2025-11-26 20:07:39 +01:00
Dennis Guse 090cdbf712 Cleanup: ContentProviderUtils.getMarkerCursor() has an optional parameter. 2025-11-26 20:05:27 +01:00
Dennis Guse 8258d607c3 Cleanup: ContentProviderUtils.getMarkerCursor() had an unused parameter. 2025-11-26 19:56:32 +01:00
Dennis Guse 686a2a9b8f Cleanup: removed method that was only used for testing in ContentProviderUtils. 2025-11-25 19:34:41 +01:00
Dennis Guse 7ddb6b32bb Cleanup: TrackDataHub doesn't need to query the id of the last TrackPoint. 2025-11-25 18:46:20 +01:00
Dennis Guse 5cd639c852 Cleanup: TrackDataHub only needs in-sample. 2025-11-25 18:42:02 +01:00
Dennis Guse ae9906fb18 Cleanup. 2025-11-25 07:40:01 +01:00
Dennis Guse 96bcac397b Cleanup. 2025-11-25 07:31:49 +01:00
Dennis Guse d7b207afac Cleanup: trackStatistics were already updated. 2025-11-25 07:31:49 +01:00
Dennis Guse 1672ba99ad Cleanup: in TrackImporter create Track object when all data got loaded. 2025-11-25 07:31:49 +01:00
Dennis Guse a6d4a05839 Statistics requires startTime and stopTime.
This is now enforced for import as wells.
2025-11-25 07:31:48 +01:00
Dennis Guse 860b90da37 Cleanup ExportImportTest. 2025-11-24 19:38:33 +01:00
Dennis Guse 46d7385d25 Cleanup RecordingData. 2025-11-22 18:22:00 +01:00
Dennis Guse 25fbd9490c Cleanup RecordingData. 2025-11-22 18:06:48 +01:00
Dennis Guse 6d33adffce Move temp functionality into TrackStatisticsUpdater. 2025-11-22 17:49:35 +01:00
Dennis Guse 92cecb6f3a For TrackStatisticsUpdater there may no pre-existing Statistics. 2025-11-22 13:14:50 +01:00
Dennis Guse e6bdc9a41d Altitude extremities is now null instead of (0,0) if no altitude was received (yet). 2025-11-22 13:03:52 +01:00
Dennis Guse e234e9c493 Only execute VoiceAnnouncementUtilsTest for SDK34+. 2025-11-22 12:56:17 +01:00
Dennis Guse 73d6ca6ca6 Cleanup: SegmentStatisticUpdater is always initialized. 2025-11-21 16:30:29 +01:00
Dennis Guse 1ef7a6ecc4 Move .merge() from SegmentStatisticUpdater to Statistics.
This is a functional change as maxSpeed is now determined by Segment (rather than the joined Segment).
2025-11-21 16:15:18 +01:00
Dennis Guse 94a0b57362 Simplify construction of TrackStatisticsUpdater. 2025-11-21 13:37:14 +01:00
Dennis Guse 4ebaf366d6 TrackStatisticsUpdater only needs one SegmentStatisticUpdater. 2025-11-21 13:37:14 +01:00
Dennis Guse 911f2c525f Move isIdle to TrackStatisticsUpdater. 2025-11-21 13:37:14 +01:00
CI/CD Bot by pstorch a310f773a1 Update dependency com.android.tools.build:gradle to v8.13.1 2025-11-20 05:33:43 +01:00
Dennis Guse 8a247e5b38 Cleanup: TrackStatisticsUpdater. 2025-11-17 22:22:44 +01:00
Dennis Guse 1045ade71a Cleanup: SegmentStatisticUpdater. 2025-11-17 22:22:44 +01:00
Dennis Guse 4307dceb70 Cleanup: rename to totalDuration and movingDuration. 2025-11-17 22:22:44 +01:00
Dennis Guse 59ea50b96f Cleanup: SegmentStatisticUpdater. 2025-11-17 22:22:44 +01:00
Dennis Guse 8d015fee6c Cleanup: SegmentStatisticUpdater. 2025-11-17 22:22:44 +01:00
Dennis Guse 653352255e Cleanup: SegmentStatisticUpdater. 2025-11-17 22:22:44 +01:00
Dennis Guse ebcdd87481 Cleanup: use Duration.ZERO. 2025-11-17 22:22:44 +01:00
Stzyxh 8d1af8f2cf Translated using Weblate (German)
Currently translated at 5.1% (9 of 175 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://translate.codeberg.org/projects/opentracks/app-store-metadata-files/de/
2025-11-17 22:22:12 +01:00
CI/CD Bot by pstorch 766739c222 Update dependency gradle to v9.2.1 2025-11-17 14:35:53 +00:00
Dennis Guse 54fa6aa3e2 Add GPX.Studio to README. 2025-11-16 15:17:15 +01:00
SomeTr 1dc737410b Translated using Weblate (Ukrainian)
Currently translated at 99.8% (543 of 544 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2025-11-10 00:09:55 +00:00
ghose 4093899a82 Translated using Weblate (Galician)
Currently translated at 17.7% (31 of 175 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://translate.codeberg.org/projects/opentracks/app-store-metadata-files/gl/
2025-11-10 00:09:55 +00:00
Dennis Guse 9236eb6944 AndroidStudio: set line width (for autoformatting) to 200. 2025-11-09 22:45:50 +01:00
Dennis Guse 9f00b0606b Revert "AndroidStudio: set line width (for autoformatting) to 200."
This reverts commit 95b27f66fb.
2025-11-09 22:45:41 +01:00
SomeTr 20a18b86dc Translated using Weblate (Ukrainian)
Currently translated at 99.4% (541 of 544 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2025-11-08 23:09:40 +00:00
alecsargent 34cfc235c3 Translated using Weblate (Spanish (Latin America))
Currently translated at 83.6% (455 of 544 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://translate.codeberg.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/es_419/
2025-11-08 23:09:40 +00:00
CI/CD Bot by pstorch f733d18a60 Add renovate.json 2025-11-08 20:33:45 +00:00
Dennis Guse 221b61c7f1 Aggregate statistics are now computed in the database. 2025-11-06 21:54:30 +01:00
Dennis Guse ca64f2c749 Tracks table: remove unused columns and rename existing columns. 2025-11-06 21:54:30 +01:00
Dennis Guse 3d083928eb Cleanup. 2025-11-06 21:54:30 +01:00
Dennis Guse f47ab142f2 Upgrade to Gradlew 9.2.0 2025-11-06 21:54:30 +01:00
Dennis Guse 6e4d46a14d Distance.ZERO as constant. 2025-11-06 21:54:30 +01:00
Dennis Guse fffeb40200 Speed.ZERO as constant. 2025-11-06 21:54:30 +01:00
Dennis Guse a5e5d0ca51 AggregatedStatistics: compute without SegmentStatisticUpdater. 2025-11-06 21:54:30 +01:00
Dennis Guse 7791ff8659 Cleanup: Track simplify constructors. 2025-11-06 21:54:30 +01:00
207 changed files with 3305 additions and 4020 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 100
max_line_length = 200
tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
image: 'ubuntu:android-24.04'
steps:
- name: Checkout sources
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
@@ -27,7 +27,7 @@ jobs:
image: 'ubuntu:android-24.04'
steps:
- name: Checkout sources
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
-6
View File
@@ -47,12 +47,6 @@
<MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings>
<editorconfig>
<option name="ENABLED" value="false" />
</editorconfig>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="200" />
</codeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
+8 -6
View File
@@ -40,8 +40,7 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
</tr>
</table>
Translations are hosted on <a href="https://translate.codeberg.org/engage/opentracks/">
translate.codeberg.org/projects/opentracks/</a>.
Translations are hosted on <a href="https://translate.codeberg.org/engage/opentracks/">translate.codeberg.org/projects/opentracks/</a>.
<a href="https://translate.codeberg.org/engage/opentracks/">
<img src="https://translate.codeberg.org/widget/opentracks/opentracks-src-main-res-values-strings-xml-main/horizontal-auto.svg" alt="Translation status" />
</a>
@@ -112,8 +111,7 @@ The API is disabled by default to protect the user's privacy, but it can easily
`Classes`:
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
* Set track data: `TRACK_NAME`, `TRACK_DESCRIPTION`, `TRACK_CATEGORY`, and `TRACK_ICON` (
non-localized identifier
see [/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java#L38](/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java#L38)).
see `ActivityType.id` defined in [/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java#L38](/src/main/java/de/dennisguse/opentracks/data/model/ActivityType.java)).
NOTE: if `TRACK_ICON` is not present, `TRACK_CATEGORY` will be used to determine the icon (
localized).
* Send recorded data to another application via _Dashboard API_: `STATS_TARGET_PACKAGE` and
@@ -134,6 +132,10 @@ adb shell am start -n "de.dennisguse.opentracks.playstore/de.dennisguse.opentrac
```
## File formats compatibility with open-source software
KML/KMZ can be used to export indoor activities (no GPS present).
GPX exports only those points that have a GPS coordinate present (sensor data is aggregated though).
| | [GPX 1.1](https://www.topografix.com/GPX/1/1/) | [KML 2.3](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html) | [KMZ 2.3](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html) |
|-----------------------------------------------------------|--------------------------------------------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------|
| [OpenLayers 7.1.0](https://openlayers.org/) | ? | [no](https://github.com/openlayers/openlayers/issues/14104) | [no](https://github.com/openlayers/openlayers/issues/14104) |
@@ -143,8 +145,8 @@ adb shell am start -n "de.dennisguse.opentracks.playstore/de.dennisguse.opentrac
| [FitTrackee](https://github.com/SamR1/FitTrackee) | yes | n/a | n/a |
| [SportsTracker](https://github.com/ssaring/sportstracker) | yes, [single tracks only](https://github.com/ssaring/sportstracker/issues/260) | no | no |
| [ExifTool](https://exiftool.org) | [yes](https://exiftool.org/forum/index.php?topic=15972.0) | no | no |
| [Wanderer](https://wanderer.to/) | yes | yes | yes |
| [Wanderer](https://wanderer.to/) | yes | yes | yes |
| [GPX.Studio]https://gpx.studio/) | yes | no | no |
## Dashboard API (incl. map)
+15 -4
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.11.1'
classpath 'com.android.tools.build:gradle:8.13.2'
}
}
@@ -46,8 +46,8 @@ android {
defaultConfig {
applicationId 'de.dennisguse.opentracks'
versionCode 6407
versionName "v4.23.2"
versionCode 6516
versionName "v4.24.2"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -57,6 +57,17 @@ android {
testInstrumentationRunner 'de.dennisguse.opentracks.TestRunner'
testInstrumentationRunnerArguments clearPackageData: 'true'
}
testOptions {
managedDevices {
localDevices {
mediumPhone {
device = "Medium Phone"
apiLevel = 35
systemImageSource = "aosp"
}
}
}
}
signingConfigs {
nightly {
if (System.getProperty('nightly_store_file') != null) {
@@ -159,7 +170,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.7.0'
androidTestImplementation 'androidx.test:runner:1.7.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
androidTestImplementation 'org.mockito:mockito-android:5.20.0'
androidTestImplementation 'org.mockito:mockito-android:5.21.0'
androidTestUtil 'androidx.test:orchestrator:1.6.1'
}
@@ -0,0 +1,17 @@
v3.0.1: OpenTracks
Änderungen:
- Markierungen teilen
- Umstrukturierte Einstellungen
- Zurück-Schaltfläche in der Aktionsleiste entfernt
- Export: Option zur Einstellung der Fotogröße entfernt
- Export: KML als Format ist jetzt auswählbar
Fehlerbehebungen:
- Exportverzeichnis ist jetzt /OpenTracks/
- Querformatmodus stürzt nicht mehr ab
- KML/KMZ-Export behoben
Entwickler:
- Tests sind ausführbar (aber viele schlagen fehl)
- Kontinuierliche Integration mit TravisCI aktiviert (keine Tests)
@@ -0,0 +1,14 @@
v3.1.0: OpenTracks
Änderungen:
- Anzeige der Strecke in Karten-Apps hinzugefügt (getestet mit OsmAnd und Maps.ME)
- Während der Verfolgung werden Streckenstatistiken angezeigt, ohne das Gerät entsperren zu müssen
- Querformat für die Detailansicht der Strecke entfernt (war fehlerhaft)
- Verlauf der Suchvorschläge entfernt
Fehlerbehebung:
- Speicherberechtigung für „Alle importieren/Alle exportieren” anfordern
Entwickler:
- Upgrade auf Gradle 5.6
- Release-Builds nicht minimieren
@@ -0,0 +1,10 @@
v4.23.2: OpenTracks
Änderungen:
- Vorausschauende Rückwärtsfunktion funktioniert vollständig
Fehlerbehebung:
- Absturz im Zusammenhang mit dem Dateinamen der Strecke in den Einstellungen behoben
Entwickler:
- Datenklasse für Statistiken eingeführt
@@ -0,0 +1,15 @@
v4.24.0: OpenTracks
Changes:
- PublicAPI: start track only sets field if data is present
- Marker photos can be shared again
- Removed KMZ without photos
- Heart rate of 0.0 is treated as invalid (except for SensorStatistics)
- Removed import workaround for special coordinates (100/200)
(was used by MyTracks to export/import special trackpoints)
Developer:
- Track and Marker are records
- AggregatedStatistics are computed in database
- Split into TrackStatisticsUpdater and SegmentStatisticUpdater
- Introduced Iterator classes (instead of passing Cursor or List)
@@ -0,0 +1,6 @@
v4.24.1: OpenTracks
Bugfix:
- Restore compatibility with OSM Dashboard v6.4.1 and earlier (introduced in v4.24.0)
- Crash when using default track name by number (introduced in v4.24.0)
@@ -0,0 +1,4 @@
v4.24.2: OpenTracks
Bugfix:
- Crash when select all (introduced in v4.24.0)
@@ -0,0 +1,10 @@
v4.23.2: OpenTracks
Cambios:
- a volta predictiva funciona
Arranxo:
- do fallo que daba relacionado co nome do ficheiro nos axustes
Desenvolvemento:
- Engádese data class para as Estatísticas
@@ -1,6 +1,6 @@
v3.0.0: Opentracks
v3.0.0: திறந்தபாதைகள்
ஓபன்ட்ராக்சின் முதல் வெளியீடு (அதாவது, மைட்ராக்சிலிருந்து ஆர்ட் ஃபோர்க்)
திறந்தபாதைகளின் முதல் வெளியீடு (அதாவது, மைட்ராக்சிலிருந்து ஆர்ட் ஃபோர்க்)
மாற்றங்கள்:
- மைட்ராக்சிலிருந்து ஓபன்ட்ராக்சுக்கு மறுபெயரிடப்பட்டது
@@ -1,4 +1,4 @@
v3.8.0: Opentracks
v3.8.0: திறந்தபாதைகள்
மாற்றத்தை உடைத்தல்:
- சிபிஎக்ச் இறக்குமதி/ஏற்றுமதி விரைவு, கேடென்ச் மற்றும் ஆர்ட்ரேட் டிராக் பாயிண்ட் எக்ச்டென்சன்/வி 2 வழியாக
@@ -6,7 +6,7 @@ v3.8.0: Opentracks
மாற்றங்கள்:
- டாச்போர்டு ஏபிஐ: குறிப்பான்களுக்கான அணுகலை வழங்குதல் (ஓபன்ட்ராக்சில் காண்பிக்க திரும்பப்பெறுதல்)
- புதிய மார்க்கர் படவுரு (ஆரஞ்சு புச் முள், எச்.வி.சி)
- இறக்குமதிக்கான கோப்புகளை கோப்பு மேலாளர் வழியாக தேர்ந்தெடுக்கலாம்
- இறக்குமதிக்கான கோப்புகளைக் கோப்பு மேலாளர் வழியாகத் தேர்ந்தெடுக்கலாம்
பிழைத்திருத்தங்கள்:
- ஒருங்கிணைந்த புள்ளிவிவரங்கள் இடைமுகம் விருப்பமான விகிதத்தை மதிக்கிறது
@@ -1,4 +1,4 @@
v3.9.0: Opentracks
v3.9.0: திறந்தபாதைகள்
மாற்றங்கள்:
- தடங்களை அவற்றின் UUID உடன் ஏற்றுமதி செய்யுங்கள்
@@ -1,4 +1,4 @@
v3.12.0: ஓபன்ட்ராக்ச்
v3.12.0: திறந்தபாதைகள்
மாற்றங்கள்:
- ஊடலை சென்சார் முகவரிகள்
@@ -1,4 +1,4 @@
v3.15.0: ஓபன்ட்ராக்ச்
v3.15.0: திறந்தபாதைகள்
எச்சரிக்கை:
இந்த வெளியீடு மாற்ற முடியாத தரவுத்தள மாற்றங்களைப் பயன்படுத்துகிறது.
@@ -14,7 +14,7 @@ v3.15.0: ஓபன்ட்ராக்ச்
(தயவுசெய்து OSMDashboard 2.10.0 ஐ மேம்படுத்தவும்)
மாற்றங்கள்:
- KML/KMZ சரியாக காப்புப் பிரதி எடுக்கவும் மீட்டமைக்கவும் பயன்படுத்தலாம் (புள்ளிவிவரங்கள் உட்பட)
- KML/KMZ சரியாகக் காப்புப் பிரதி எடுக்கவும் மீட்டமைக்கவும் பயன்படுத்தலாம் (புள்ளிவிவரங்கள் உட்பட)
டெவலப்பர்:
- மறுவேலை செய்யப்பட்ட தட பிரிவு மற்றும் அது சேமிப்பு (முன்னர் இடைநிறுத்தம்/மீண்டும் மார்க்கர்)
@@ -1,4 +1,4 @@
v3.16.0: ஓபன்ட்ராக்ச்
v3.16.0: திறந்தபாதைகள்
மாற்றங்கள்:
- சைக்கிள் ஓட்டுதல்: பி.எல்.இ வேக சென்சாரிலிருந்து தூரத்தைப் பெறுங்கள்
@@ -1,4 +1,4 @@
v3.17.0: Opentracks
v3.17.0: திறந்தபாதைகள்
மாற்றங்கள்:
- கயாக்கிங்கிற்கு ஐகானைச் சேர்க்கவும்
@@ -1,4 +1,4 @@
v3.18.0: ஓபன்ட்ராக்ச்
v3.18.0: திறந்தபாதைகள்
மாற்றங்கள்:
- உயரம் EGM2008 இல் காட்டப்பட்டுள்ளது (WGS84 எனச் சேமிக்கப்படுகிறது/ஏற்றுமதி செய்யப்படுகிறது)
@@ -1,4 +1,4 @@
v3.18.3: ஓபன்ட்ராக்ச்
v3.18.3: திறந்தபாதைகள்
மாற்றங்கள்:
- KML பாகுபடுத்தலுக்கு/GX: KOURS இன் ஆர்டர் தேவையில்லை
@@ -1,4 +1,4 @@
v3.18.6: Opentracks
v3.18.6: திறந்தபாதைகள்
மாற்றங்கள்:
- "வரைபடத்தில் காட்டு"- KMZ க்கு பதிலாக KML வழியாகச் செயல்பாடு
@@ -1,4 +1,4 @@
v3.19.0: Opentracks
v3.19.0: திறந்தபாதைகள்
மாற்றங்கள்:
- இயங்கும் விரைவு மற்றும் கேடென்ச் (ஆர்.சி.எச்) சென்சார்களுக்கான உதவி செயல்படுத்தப்பட்டது
@@ -1,4 +1,4 @@
v3.20.2: ஓபன்ட்ராக்ச்
v3.20.2: திறந்தபாதைகள்
மாற்றங்கள்:
- மிதவைகளுக்குப் பதிலாக உயரத்தை ஆதாயம் மற்றும் இழப்பு என்பது எண்கள்
@@ -1,4 +1,4 @@
v3.21.0: Opentracks
v3.21.0: திறந்தபாதைகள்
மாற்றங்கள்:
- பதிவு செய்யும்போது: டிராக் பாயிண்டுகளின் செயலற்ற அல்லாத உருவாக்கத்தை அகற்று
@@ -1,4 +1,4 @@
v3.24.0: Opentracks
v3.24.0: திறந்தபாதைகள்
மாற்றங்கள்:
- சி.பி.எச் விருப்பமானது (உட்புற பயன்பாட்டிற்கும்): தூரத்தை வழங்கும் பி.எல்.இ சென்சார்களைப் பயன்படுத்தினால் பயன்படுத்தப்பட்டால்
@@ -1,4 +1,4 @@
v3.24.1: Opentracks
v3.24.1: திறந்தபாதைகள்
மாற்றங்கள்:
- பிற பயன்பாடுகளிலிருந்து பதிவுகளைத் தொடங்க பநிஇ சேர்க்கப்பட்டது (இயல்பாக முடக்கப்பட்டுள்ளது)
@@ -1,8 +1,8 @@
V3.27.0: Opentracks
V3.27.0: திறந்தபாதைகள்
மாற்றங்கள்:
- குரல் அன்னியூசென்ச்: 100 மீ, 200 மீ மற்றும் 500 மீ
- இடைவெளி: 100 மீ மற்றும் 500 மீ சேர்க்கவும்
- இடைவெளி: 100 மீ மற்றும் 500 மீ சேர்
- கடிகாரம் இப்போது விநாடிகளையும் காட்டுகிறது
- சி.பி.எக்ச், கே.எம்.எல், கே.எம்.இசட்: கிடைமட்ட துல்லியத்தை ஏற்றுமதி செய்து இறக்குமதி செய்க
- சி.பி.எக்ச்: கிளச்ட்ரச்ட் நீட்டிப்பு வழியாக ஒட்டுமொத்த தூரத்தை ஏற்றுமதி செய்யுங்கள்
@@ -1,4 +1,4 @@
v4.0.0: Opentracks
v4.0.0: திறந்தபாதைகள்
மாற்றங்கள்:
- கீழ் பயன்பாட்டு பட்டியுடன் இடைமுகம் மறுசீரமைப்பு.
@@ -1,4 +1,4 @@
V4.0.1: Opentracks
V4.0.1: திறந்தபாதைகள்
மாற்றங்கள்:
- குரல் அறிவிப்புகள் இப்போது கட்டமைக்கப்படுகின்றன
@@ -1,4 +1,4 @@
V4.9.0: Opentracks
V4.9.0: திறந்தபாதைகள்
குறிப்பு: மாற்றத்தை உடைத்தல்
@@ -1,4 +1,4 @@
V4.10.0: Opentracks
V4.10.0: திறந்தபாதைகள்
மாற்றங்கள்:
- BLE சுற்றுச்சூழல் உணர்திறன் (காற்றழுத்தமானி) வழியாக உதவி ஆதாயம்/இழப்பு
@@ -1,4 +1,4 @@
v4.13.0: ஓபன்ட்ராக்ச்
v4.13.0: திறந்தபாதைகள்
மாற்றங்கள்:
- பொது பநிஇ வழியாகக் குறிப்பான்களை உருவாக்க முடியும்
@@ -0,0 +1,4 @@
v4.22.0: OpenTracks
மாற்றங்கள்:
- https://codeberg.org/OpenTracksApp/OpenTracks க்கு இடம்பெயர்தல்
@@ -0,0 +1,4 @@
v4.22.1: OpenTracks
மாற்றங்கள்:
- கோட்பெர்க் வழியாக முதல் வெளியீடு
@@ -0,0 +1,3 @@
v4.22.2: திறந்தபாதைகள்
பிழைத்திருத்தம்:
- நேரம் மற்றும் அலகுக்கான குரல் அறிவிப்பு அமைப்பு இணைக்கப்பட்டது
@@ -0,0 +1,10 @@
v4.23.0: OpenTracks
மாற்றங்கள்:
- UI: தற்போதைய பிரிவின் சராசரி வேகத்தைச் சேர்த்தது
(தற்போது புதிய தளவமைப்புகளில் மட்டுமே கிடைக்கிறது)
- அறிவிப்பைக் சொடுக்கு செய்வதன் மூலம் உண்மையான பதிவு இடைமுகம் திறக்கும்
டெவலப்பர்:
- நிராகரிக்கப்பட்ட JobIntentService, WorkManager உடன் மாற்றப்பட்டது
- CharSequence மற்றும் Stream.toList() தவிர .isEmpty() ஐப் பயன்படுத்தவும்
@@ -0,0 +1,7 @@
v4.23.1: OpenTracks
மாற்றங்கள்:
- தற்போதைய பிரிவில் கழிந்த நேரத்தைக் காட்ட புதிய இடைமுகம் கூறு
பிழைத்திருத்தம்:
- உடற்பயிற்சிக்குப் பிந்தைய உடனடி ஏற்றுமதி செயலிழந்தது (v4.23.0 இல் அறிமுகப்படுத்தப்பட்டது)
@@ -0,0 +1,10 @@
v4.23.2: OpenTracks
மாற்றங்கள்:
- முன்கணிப்பு முற்றிலும் வேலை செய்கிறது
பிழைத்திருத்தம்:
- அமைப்புகளில் டிராக் கோப்புப் பெயருடன் தொடர்புடைய செயலிழப்பில் சரி செய்யப்பட்டது
டெவலப்பர்:
- புள்ளிவிவரங்களுக்கான தரவு வகுப்பு அறிமுகப்படுத்தப்பட்டது
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
-3
View File
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
Vendored
+93 -94
View File
@@ -1,94 +1,93 @@
@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
@rem SPDX-License-Identifier: Apache-2.0
@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=.
@rem This is normally unused
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% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 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!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
: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
@rem SPDX-License-Identifier: Apache-2.0
@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=.
@rem This is normally unused
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% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 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!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+3
View File
@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}
@@ -52,7 +52,6 @@ public class EspressoDeleteTrackTest {
onView(allOf(withParent(withId(R.id.track_list)), withParentIndex(0))).perform(longClick());
// open menu and delete selected track
//TODO openActionBarOverflowOrOptionsMenu(); doesn't work
onView(
allOf(withContentDescription("More options"),
childAtPosition(
@@ -1,96 +0,0 @@
package de.dennisguse.opentracks.chart;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.UnitSystem;
//TODO assertEquals with ChartPoint (instead of individual comparisons)
@RunWith(AndroidJUnit4.class)
public class ChartPointTest {
private static final Statistics STATISTIC_NULL = new Statistics(null, null, Duration.ZERO, null, null, null, null, null, null, null);
@Test
public void create_by_time() {
// given
Statistics statistics = new Statistics(null, null, Duration.ofSeconds(1000), null, null, null, null, null, null, null);
// when
ChartPoint point = ChartPoint.create(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), false, UnitSystem.IMPERIAL_FEET);
// then
assertEquals(1000000, (long) point.timeOrDistance());
}
@Test
public void create_by_distance() {
// given
Statistics statistics = new Statistics(null, null, null, null, Distance.of(1000), null, null, null, null, null);
// when
ChartPoint point = ChartPoint.create(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), true, UnitSystem.METRIC);
// then
assertEquals(1, (long) point.timeOrDistance());
}
@Test
public void create_get_altitude_speed_and_pace() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
.setAltitude(Altitude.EGM2008.of(50));
// when
ChartPoint point = ChartPoint.create(STATISTIC_NULL, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
// then
assertEquals(50, point.altitude(), 0.01);
assertEquals(36, point.speed(), 0.01);
assertEquals(1.66, point.pace(), 0.01);
}
@Test
public void create_sensorNotAvailable() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
.setAltitude(Altitude.EGM2008.of(50));
// when
ChartPoint point = ChartPoint.create(STATISTIC_NULL, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
// then
assertNull(point.heartRate());
assertNull(point.cadence());
assertNull(point.power());
}
@Test
public void create_sensorAvailable() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
.setAltitude(Altitude.EGM2008.of(50))
.setHeartRate(100f)
.setCadence(101f)
.setPower(102f);
// when
ChartPoint point = ChartPoint.create(STATISTIC_NULL, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
// then
assertEquals(100.0, point.heartRate(), 0.01);
assertEquals(101.0, point.cadence(), 0.01);
assertEquals(102.0, point.power(), 0.01);
}
}
@@ -1,70 +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.chart;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
/**
* Commons utilities for creating stubs of track, location.
*
* @author Youtao Liu
*/
//TODO Move to ChartPointTest
class TrackStubUtils {
public static final double INITIAL_ALTITUDE = 22;
public static final long INITIAL_TIME = 1000L;
private static final double INITIAL_LATITUDE = 22;
private static final double INITIAL_LONGITUDE = 22;
private static final Distance INITIAL_ACCURACY = Distance.of(5);
private static final Speed INITIAL_SPEED = Speed.of(10);
private static final float INITIAL_BEARING = 3.0f;
/**
* Create a MyTracks location with default values.
*
* @return a track stub.
*/
public static TrackPoint createDefaultTrackPoint() {
return createDefaultTrackPoint(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE);
}
/**
* Creates a {@link TrackPoint} stub with specified values.
*
* @return a SensorDataSetLocation stub.
*/
private static TrackPoint createDefaultTrackPoint(double latitude, double longitude, double altitude) {
return new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.ofEpochMilli(INITIAL_TIME),
latitude,
longitude,
INITIAL_ACCURACY,
Altitude.WGS84.of(altitude),
INITIAL_ACCURACY,
INITIAL_BEARING,
INITIAL_SPEED
));
}
}
@@ -7,19 +7,24 @@ import android.util.Pair;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.MarkerIterator;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
import de.dennisguse.opentracks.util.FileUtils;
public class TestDataUtil {
@@ -34,11 +39,15 @@ public class TestDataUtil {
* Create a track without any trackPoints.
*/
public static Track createTrack(Track.Id trackId) {
Track track = new Track();
track.setId(trackId);
track.setName("Test: " + trackId.id());
return track;
return new Track(
trackId,
UUID.randomUUID(),
"Test: " + trackId.id(),
"",
"",
ActivityType.UNKNOWN,
ZoneOffset.UTC,
Statistics.DEFAULT);
}
/**
@@ -83,9 +92,9 @@ public class TestDataUtil {
);
List<Marker> markers = List.of(
new Marker(trackId, trackPoints.get(1), "Marker 1", "Marker description 1", "Marker category 3", "", null),
new Marker(trackId, trackPoints.get(4), "Marker 2", "Marker description 2", "Marker category 3", "", null),
new Marker(trackId, trackPoints.get(5), "Marker 3", "Marker description 3", "Marker category 3", "", null)
new Marker(null, trackId, "Marker 1", "Marker description 1", "Marker typeLocalized 3", trackPoints.get(1).getPosition(), null),
new Marker(null, trackId, "Marker 2", "Marker description 2", "Marker typeLocalized 3", trackPoints.get(4).getPosition(), null),
new Marker(null, trackId, "Marker 3", "Marker description 3", "Marker typeLocalized 3", trackPoints.get(5).getPosition(), null)
);
return new TrackData(track, trackPoints, markers);
@@ -137,24 +146,41 @@ public class TestDataUtil {
*/
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, List<TrackPoint> trackPoints) {
contentProviderUtils.insertTrack(track);
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.id());
}
public static Marker createMarkerWithPhoto(Context context, Track.Id trackId, TrackPoint trackPoint) throws IOException {
return createMarkerWithPhoto(context, trackId, trackPoint, "Marker description");
}
public static Marker createMarkerWithPhoto(Context context, Track.Id trackId, TrackPoint trackPoint, String description) throws IOException {
File dstFile = new File(MarkerUtils.getImageUrl(context, trackId));
dstFile.createNewFile();
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
return new Marker(trackId, trackPoint, "Marker name", "Marker description", "Marker category", "", photoUri);
return new Marker(
null,
trackId,
"Marker name",
description,
"Marker typeLocalized",
trackPoint.getPosition(),
photoUri);
}
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointIterator(trackId, null)) {
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
while (trackPointIterator.hasNext()) {
trackPoints.add(trackPointIterator.next());
}
trackPointIterator.forEachRemaining(trackPoints::add);
return trackPoints;
}
}
public static List<Marker> getMarkers(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
try (MarkerIterator trackPointIterator = contentProviderUtils.getMarkerIterator(trackId)) {
ArrayList<Marker> markers = new ArrayList<>();
trackPointIterator.forEachRemaining(markers::add);
return markers;
}
}
}
@@ -1,73 +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.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.ContentUris;
import android.content.Context;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Test;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns;
/**
* Tests {@link CustomContentProvider}.
*
* @author Youtao Liu
*/
public class CustomContentProviderTest {
private CustomContentProvider customContentProvider;
private final Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
customContentProvider = new CustomContentProvider() {
};
}
/**
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
*/
@Test
public void testOnCreate() {
assertTrue(customContentProvider.onCreate(context));
}
/**
* Tests {@link CustomContentProvider#getType(Uri)}.
*/
@Test
public void testGetType() {
assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build()));
assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
assertEquals(MarkerColumns.CONTENT_TYPE, customContentProvider.getType(MarkerColumns.CONTENT_URI));
assertEquals(MarkerColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(MarkerColumns.CONTENT_URI.buildUpon(), 1).build()));
}
}
@@ -27,7 +27,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
@@ -42,6 +41,7 @@ import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
@@ -50,22 +50,23 @@ import java.util.UUID;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.TestSensorDataUtil;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.util.FileUtils;
/**
@@ -117,7 +118,7 @@ public class CustomContentProviderUtilsTest {
private void testIterator(Track.Id trackId, int numPoints) {
TrackPoint.Id lastPointId = initializeTrack(trackId, numPoints);
List<TrackPoint> locations = new ArrayList<>(numPoints);
try (AutoCloseableIterator<TrackPoint> it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
try (TrackPointIterator it = contentProviderUtils.getTrackPointIterator(trackId, null)) {
while (it.hasNext()) {
TrackPoint trackPoint = it.next();
assertNotNull(trackPoint);
@@ -130,11 +131,19 @@ public class CustomContentProviderUtilsTest {
}
private TrackPoint.Id initializeTrack(Track.Id id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id.id());
contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id);
Track createdTrack = new Track(
id,
UUID.randomUUID(),
"Test: " + id,
"",
"",
ActivityType.UNKNOWN,
ZoneOffset.UTC,
Statistics.DEFAULT);
contentProviderUtils.insertTrack(createdTrack);
Track track = contentProviderUtils.getTrack(id);
assertNotNull(track);
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
@@ -155,7 +164,7 @@ public class CustomContentProviderUtilsTest {
// Load all inserted trackPoints.
TrackPoint.Id lastPointId = null;
int counter = 0;
try (AutoCloseableIterator<TrackPoint> it = contentProviderUtils.getTrackPointLocationIterator(id, null)) {
try (TrackPointIterator it = contentProviderUtils.getTrackPointIterator(id, null)) {
while (it.hasNext()) {
TrackPoint trackPoint = it.next();
lastPointId = trackPoint.getId();
@@ -196,8 +205,8 @@ public class CustomContentProviderUtilsTest {
when(cursorMock.getString(columnIndex)).thenReturn(name);
Track track = ContentProviderUtils.createTrack(cursorMock);
assertEquals(trackId, track.getId());
assertEquals(name, track.getName());
assertEquals(trackId, track.id());
assertEquals(name, track.name());
}
private void assertCount(int trackCount, int trackPointCount, int markerCount) {
@@ -222,8 +231,15 @@ public class CustomContentProviderUtilsTest {
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Marker marker = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
contentProviderUtils.insertMarker(marker);
contentProviderUtils.insertMarker(new Marker(
null,
trackId,
"",
"",
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
assertCount(1, 10, 1);
@@ -241,14 +257,14 @@ public class CustomContentProviderUtilsTest {
public void testDeleteAllTracks_withMarkerAndPhoto() throws IOException {
// Insert track, points and marker with photo at first.
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
TrackPoint trackPoint = getLastValidTrackPoint(trackId);
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint);
contentProviderUtils.insertMarker(marker);
assertCount(1, 10, 1);
assertTrue(marker.hasPhoto());
assertNotNull(marker.photoUrl());
File dir = FileUtils.getPhotoDir(context, trackId);
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
@@ -278,8 +294,15 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
Marker marker = new Marker(trackId1, contentProviderUtils.getLastValidTrackPoint(trackId2));
contentProviderUtils.insertMarker(marker);
contentProviderUtils.insertMarker(new Marker(
null,
trackId1,
"",
"",
"",
getLastValidTrackPoint(trackId1).getPosition(),
null
));
assertCount(3, 30, 1);
@@ -306,19 +329,19 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
// Insert a marker in tracks trackId and trackId + 1.
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId1);
TrackPoint trackPoint1 = getLastValidTrackPoint(trackId1);
Marker marker1 = TestDataUtil.createMarkerWithPhoto(context, trackId1, trackPoint1);
contentProviderUtils.insertMarker(marker1);
File dir1 = FileUtils.getPhotoDir(context, trackId1);
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId2);
TrackPoint trackPoint2 = getLastValidTrackPoint(trackId2);
Marker marker2 = TestDataUtil.createMarkerWithPhoto(context, trackId2, trackPoint2);
contentProviderUtils.insertMarker(marker2);
File dir2 = FileUtils.getPhotoDir(context, trackId2);
// Check.
assertCount(3, 30, 2);
assertTrue(marker1.hasPhoto());
assertNotNull(marker1.photoUrl());
assertTrue(dir1.isDirectory());
assertEquals(1, dir1.list().length);
assertTrue(dir1.exists());
@@ -350,7 +373,7 @@ public class CustomContentProviderUtilsTest {
// then
assertEquals(initialTrackNumber + 1, allTracks.size());
assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
assertEquals(trackId, allTracks.get(allTracks.size() - 1).id());
}
/**
@@ -377,39 +400,35 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertTrack(track);
// when / then
assertNotNull(contentProviderUtils.getTrack(track.getUuid()));
assertNotNull(contentProviderUtils.getTrack(track.uuid()));
}
/**
* Tests the method {@link ContentProviderUtils#updateTrack(Track)}
*/
@Test
public void testUpdateTrack() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId);
TrackBuilder track = new TrackBuilder(TestDataUtil.createTrack(trackId));
String nameOld = "name1";
String nameNew = "name2";
track.setName(nameOld);
// when / then
contentProviderUtils.insertTrack(track);
assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
contentProviderUtils.insertTrack(track.getTrack());
assertEquals(nameOld, contentProviderUtils.getTrack(trackId).name());
track.setName(nameNew);
contentProviderUtils.updateTrack(track);
assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
contentProviderUtils.updateTrack(track.getTrack());
assertEquals(nameNew, contentProviderUtils.getTrack(trackId).name());
}
/**
* Tests the method {@link ContentProviderUtils#createContentValues(Marker)}.
*/
@Test
public void testCreateContentValues_marker() {
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
TrackBuilder trackBuilder = new TrackBuilder(track.first);
// AverageSpeed
track.first.setStatistics(new Statistics(
trackBuilder.setStatistics(new Statistics(
Instant.ofEpochMilli(1000),
Instant.ofEpochMilli(2500),
Duration.ofMillis(1500),
@@ -421,17 +440,17 @@ public class CustomContentProviderUtilsTest {
null,
null
));
contentProviderUtils.insertTrack(track.first);
contentProviderUtils.insertTrack(trackBuilder.getTrack());
Marker marker = new Marker(trackId, track.second.get(0));
MarkerBuilder marker = new MarkerBuilder(trackId, track.second.get(0));
marker.setDescription(TEST_DESC);
contentProviderUtils.insertMarker(marker);
contentProviderUtils.insertMarker(marker.getMarker());
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
Marker.Id markerId = new Marker.Id(System.currentTimeMillis());
marker.setId(markerId);
ContentValues contentValues = contentProviderUtils.createContentValues(marker);
ContentValues contentValues = contentProviderUtils.createContentValues(marker.getMarker(), trackId);
assertEquals(markerId.id(), contentValues.get(MarkerColumns._ID));
assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(MarkerColumns.LONGITUDE));
assertEquals(TEST_DESC, contentValues.get(MarkerColumns.DESCRIPTION));
@@ -448,15 +467,18 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
Marker marker1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
marker1.setDescription(TEST_DESC);
contentProviderUtils.insertMarker(marker1);
Marker.Id marker1Id = contentProviderUtils.insertMarker(new Marker(
null,
trackId,
"",
TEST_DESC,
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
// Check insert was done.
assertEquals(1, contentProviderUtils.getMarkers(trackId).size());
// Get marker id that needs to delete.
Marker.Id marker1Id = contentProviderUtils.insertMarker(marker1);
assertEquals(1, TestDataUtil.getMarkers(contentProviderUtils, trackId).size());
// Delete
contentProviderUtils.deleteMarker(context, marker1Id);
@@ -475,18 +497,18 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
TrackPoint trackPoint = getLastValidTrackPoint(trackId);
Marker marker1 = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint);
contentProviderUtils.insertMarker(marker1);
// Check insert was done.
assertEquals(1, contentProviderUtils.getMarkers(trackId).size());
assertEquals(1, TestDataUtil.getMarkers(contentProviderUtils, trackId).size());
// Get marker id that needs to delete.
Marker.Id marker1Id = contentProviderUtils.insertMarker(marker1);
// Check marker has photo and it's in the external storage.
assertTrue(marker1.hasPhoto());
assertNotNull(marker1.photoUrl());
File dir = FileUtils.getPhotoDir(context, trackId);
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
@@ -525,20 +547,31 @@ public class CustomContentProviderUtilsTest {
// TestDataUtil.insertTrackWithLocations(contentProviderUtils, track);
// Insert at first.
Marker marker1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
marker1.setDescription(MOCK_DESC);
Marker.Id marker1Id = contentProviderUtils.insertMarker(marker1);
Marker marker2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
marker2.setDescription(MOCK_DESC);
Marker.Id marker2Id = contentProviderUtils.insertMarker(marker2);
Marker.Id marker1Id = contentProviderUtils.insertMarker(new Marker(
null,
trackId,
"",
MOCK_DESC,
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
Marker.Id marker2Id = contentProviderUtils.insertMarker(new Marker(
null,
trackId,
"",
MOCK_DESC,
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
// Delete
assertNotNull(contentProviderUtils.getMarker(marker1Id));
contentProviderUtils.deleteMarker(context, marker1Id);
assertNull(contentProviderUtils.getMarker(marker1Id));
assertEquals(MOCK_DESC, contentProviderUtils.getMarker(marker2Id).getDescription());
assertEquals(MOCK_DESC, contentProviderUtils.getMarker(marker2Id).description());
}
/**
@@ -549,14 +582,17 @@ public class CustomContentProviderUtilsTest {
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Marker marker1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
Marker marker2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
Marker marker3 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
Marker marker4 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
contentProviderUtils.insertMarker(marker1);
contentProviderUtils.insertMarker(marker2);
contentProviderUtils.insertMarker(marker3);
contentProviderUtils.insertMarker(marker4);
for (int i = 0; i < 4; i++) {
contentProviderUtils.insertMarker(new Marker(
null,
trackId,
"",
MOCK_DESC,
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
}
assertEquals(Integer.valueOf(4), contentProviderUtils.getNextMarkerNumber(trackId));
}
@@ -570,11 +606,18 @@ public class CustomContentProviderUtilsTest {
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Marker marker = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
marker.setDescription(TEST_DESC);
Marker.Id markerId = contentProviderUtils.insertMarker(marker);
Marker.Id markerId = contentProviderUtils.insertMarker(
new Marker(
null,
trackId,
"",
TEST_DESC,
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
assertEquals(TEST_DESC, contentProviderUtils.getMarker(markerId).getDescription());
assertEquals(TEST_DESC, contentProviderUtils.getMarker(markerId).description());
}
/**
@@ -586,16 +629,29 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
Marker marker = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
marker.setDescription(TEST_DESC);
Marker.Id markerId = contentProviderUtils.insertMarker(marker);
Marker.Id markerId = contentProviderUtils.insertMarker(
new Marker(
null,
trackId,
"",
TEST_DESC,
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
// Update
marker = contentProviderUtils.getMarker(markerId);
marker.setDescription(TEST_DESC_NEW);
contentProviderUtils.updateMarker(context, marker);
contentProviderUtils.updateMarker(context, new Marker(
markerId,
trackId,
"",
TEST_DESC_NEW,
"",
getLastValidTrackPoint(trackId).getPosition(),
null
));
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).getDescription());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).description());
}
/**
@@ -609,10 +665,8 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint);
marker.setDescription(TEST_DESC);
Marker.Id markerId = contentProviderUtils.insertMarker(marker);
TrackPoint trackPoint = getLastValidTrackPoint(trackId);
Marker.Id markerId = contentProviderUtils.insertMarker(TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint, TEST_DESC));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.id());
assertTrue(dir.exists());
@@ -620,14 +674,13 @@ public class CustomContentProviderUtilsTest {
assertEquals(1, dir.list().length);
// Update
marker = contentProviderUtils.getMarker(markerId);
MarkerBuilder marker = new MarkerBuilder(contentProviderUtils.getMarker(markerId));
marker.setName(TEST_NAME_NEW);
marker.setDescription(TEST_DESC_NEW);
contentProviderUtils.updateMarker(context, marker);
contentProviderUtils.updateMarker(context, marker.getMarker());
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(markerId).getName());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).getDescription());
assertTrue(marker.hasPhoto());
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(markerId).name());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).description());
assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
@@ -644,9 +697,8 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint);
marker.setDescription(TEST_DESC);
TrackPoint trackPoint = getLastValidTrackPoint(trackId);
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint, TEST_DESC);
Marker.Id markerId = contentProviderUtils.insertMarker(marker);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.id());
@@ -655,15 +707,14 @@ public class CustomContentProviderUtilsTest {
assertEquals(1, dir.list().length);
// Update
marker = contentProviderUtils.getMarker(markerId);
marker.setName(TEST_NAME_NEW);
marker.setDescription(TEST_DESC_NEW);
marker.setPhotoUrl(null);
contentProviderUtils.updateMarker(context, marker);
MarkerBuilder markerBuilder = new MarkerBuilder(contentProviderUtils.getMarker(markerId));
markerBuilder.setName(TEST_NAME_NEW);
markerBuilder.setDescription(TEST_DESC_NEW);
markerBuilder.setPhotoUrl(null);
contentProviderUtils.updateMarker(context, markerBuilder.getMarker());
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(markerId).getName());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).getDescription());
assertFalse(marker.hasPhoto());
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(markerId).name());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).description());
assertFalse(dir.exists());
}
@@ -678,11 +729,9 @@ public class CustomContentProviderUtilsTest {
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert two markers with photos.
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint);
marker.setDescription(TEST_DESC);
Marker otherMarker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint);
otherMarker.setDescription(TEST_DESC);
TrackPoint trackPoint = getLastValidTrackPoint(trackId);
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint, TEST_DESC);
Marker otherMarker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint, TEST_DESC);
Marker.Id markerId = contentProviderUtils.insertMarker(marker);
contentProviderUtils.insertMarker(otherMarker);
@@ -692,12 +741,14 @@ public class CustomContentProviderUtilsTest {
assertEquals(2, dir.list().length);
// Update one marker deleting photo.
marker = contentProviderUtils.getMarker(markerId);
marker.setPhotoUrl(null);
contentProviderUtils.updateMarker(context, marker);
MarkerBuilder markerBuilder = new MarkerBuilder(contentProviderUtils.getMarker(markerId));
markerBuilder.setPhotoUrl(null);
contentProviderUtils.updateMarker(context, markerBuilder.getMarker());
assertEquals(TEST_DESC, contentProviderUtils.getMarker(markerId).getDescription());
assertFalse(marker.hasPhoto());
// then
marker = contentProviderUtils.getMarker(markerId);
assertEquals(TEST_DESC, contentProviderUtils.getMarker(markerId).description());
assertNull(marker.photoUrl());
assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
@@ -728,50 +779,6 @@ public class CustomContentProviderUtilsTest {
}
}
/**
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
*/
//TODO incomplete
@Test
public void testCreateTrackPoint() {
// given
when(cursorMock.getColumnIndex(TrackPointsColumns._ID)).thenReturn(1);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).thenReturn(2);
when(cursorMock.isNull(2)).thenReturn(false);
int longitude = 11;
when(cursorMock.getInt(2)).thenReturn(longitude * 1000000);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).thenReturn(3);
when(cursorMock.isNull(3)).thenReturn(false);
int latitude = 22;
when(cursorMock.getInt(3)).thenReturn(latitude * 1000000);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME)).thenReturn(4);
when(cursorMock.isNull(4)).thenReturn(false);
long time = System.currentTimeMillis();
when(cursorMock.getLong(4)).thenReturn(time);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).thenReturn(5);
when(cursorMock.isNull(5)).thenReturn(false);
float speed = 2.2f;
when(cursorMock.getFloat(5)).thenReturn(speed);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).thenReturn(6);
when(cursorMock.isNull(6)).thenReturn(false);
when(cursorMock.getFloat(6)).thenReturn(75f);
// when
TrackPoint trackPoint = contentProviderUtils.createTrackPoint(cursorMock);
// then
assertEquals(latitude, trackPoint.getPosition().latitude(), 0.01);
assertEquals(longitude, trackPoint.getPosition().longitude(), 0.01);
assertEquals(time, trackPoint.getTime().toEpochMilli());
assertEquals(speed, trackPoint.getSpeed().toMPS(), 0.01);
assertEquals(HeartRate.of(75f), trackPoint.getHeartRate());
}
/**
* Tests the method
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, Track.Id)}.
@@ -780,7 +787,7 @@ public class CustomContentProviderUtilsTest {
public void testInsertTrackPoint() {
// Insert track, point at first.
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
// when
@@ -794,7 +801,7 @@ public class CustomContentProviderUtilsTest {
public void testInsertAndLoadTrackPoint() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
trackPoint.setHeartRate(1F);
@@ -812,19 +819,6 @@ public class CustomContentProviderUtilsTest {
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower());
}
/**
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(Track.Id)}.
*/
@Test
public void testGetLastValidTrackPoint() {
// Insert track, points at first.
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
checkLocation(9, lastTrackPoint.getLocation());
}
@Test
public void testGetTrackPointCursor_asc() {
// given
@@ -833,7 +827,7 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertTrack(track.first);
List<TrackPoint.Id> trackpointIds = track.second.stream()
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId())))
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.id())))
.map(TrackPoint.Id::new).toList();
// when
@@ -844,20 +838,20 @@ public class CustomContentProviderUtilsTest {
}
@Test
public void testGetTrackPointLocationIterator_asc() {
public void testGetTrackPointIterator_asc() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
List<TrackPoint.Id> trackpointIds = track.second.stream()
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId())))
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.id())))
.map(TrackPoint.Id::new).toList();
TrackPoint.Id startTrackPointId = trackpointIds.get(0);
// when
AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, trackpointIds.get(0));
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointIterator(trackId, trackpointIds.get(0));
// then
for (int i = 0; i < trackpointIds.size(); i++) {
@@ -865,24 +859,15 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = trackPointIterator.next();
assertEquals(startTrackPointId.id() + i, trackPoint.getId().id());
checkLocation(i, trackPoint.getLocation());
Position position = trackPoint.getPosition();
assertEquals(TestDataUtil.INITIAL_LATITUDE + i / 10000.0, position.latitude(), 0.01);
assertEquals(TestDataUtil.INITIAL_LONGITUDE - i / 10000.0, position.longitude(), 0.01);
assertEquals(i / 100.0, position.horizontalAccuracy().distance_m(), 0.01);
assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, position.altitude().toM(), 0.01);
}
assertFalse(trackPointIterator.hasNext());
}
/**
* Checks the value of a location.
*
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(Track.Id, int)}
* @param location the location to be checked
*/
private void checkLocation(int i, Location location) {
assertEquals(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
assertEquals(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
}
@Test
public void testFormatIdListForUri() {
assertEquals("", ContentProviderUtils.formatIdListForUri());
@@ -1222,4 +1207,19 @@ public class CustomContentProviderUtilsTest {
public void testGetSensorStats_withSeveralRandomStartSegments() {
testGetSensorStats_randomData(5000, true);
}
private TrackPoint getLastValidTrackPoint(Track.Id trackId) {
try (TrackPointIterator trackPointsCursor = contentProviderUtils.getTrackPointIterator(trackId, null)) {
List<TrackPoint> trackpoints = new ArrayList<>();
while (trackPointsCursor.hasNext())
trackpoints.add(trackPointsCursor.next());
trackpoints = trackpoints.reversed();
return trackpoints.reversed()
.stream()
.filter(it -> List.of(TrackPoint.Type.TRACKPOINT, TrackPoint.Type.SEGMENT_START_AUTOMATIC).contains(it.getType()))
.findFirst().orElseThrow();
}
}
}
@@ -100,18 +100,18 @@ public class CustomSQLiteOpenHelperTest {
// Open database with SQL upgrade
Map<String, String> tableByUpgrade;
Map<String, String> indicesByUpgrade;
try (SQLiteDatabase dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tableByUpgrade = getSQL(dbUpgraded, "table");
indicesByUpgrade = getSQL(dbUpgraded, "index");
try (CustomSQLiteOpenHelper dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME)) {
tableByUpgrade = getSQL(dbUpgraded.getReadableDatabase(), "table");
indicesByUpgrade = getSQL(dbUpgraded.getReadableDatabase(), "index");
}
context.deleteDatabase(DATABASE_NAME);
// Open database via creation script
Map<String, String> tablesByCreate;
Map<String, String> indicesByCreate;
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tablesByCreate = getSQL(dbCreated, "table");
indicesByCreate = getSQL(dbCreated, "index");
try (CustomSQLiteOpenHelper dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME)) {
tablesByCreate = getSQL(dbCreated.getReadableDatabase(), "table");
indicesByCreate = getSQL(dbCreated.getReadableDatabase(), "index");
}
@@ -134,14 +134,16 @@ public class CustomSQLiteOpenHelperTest {
@Test
public void onDowngrade_ToVersion23() {
// Create most recent database schema
new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase().close();
try(CustomSQLiteOpenHelper db = new CustomSQLiteOpenHelper(context, DATABASE_NAME)) {
db.getReadableDatabase();
}
// Downgrade schema to version 23 (base version)
Map<String, String> tablesByDowngrade;
Map<String, String> indicesByDowngrade;
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23).getReadableDatabase()) {
tablesByDowngrade = getSQL(db, "table");
indicesByDowngrade = getSQL(db, "index");
try (CustomSQLiteOpenHelper db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23)) {
tablesByDowngrade = getSQL(db.getReadableDatabase(), "table");
indicesByDowngrade = getSQL(db.getReadableDatabase(), "index");
}
// then - verify table structure
@@ -155,9 +157,9 @@ public class CustomSQLiteOpenHelperTest {
@Test
public void track_uuid_unique() {
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
db.execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
db.execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
try (CustomSQLiteOpenHelper db = new CustomSQLiteOpenHelper(context, DATABASE_NAME)) {
db.getWritableDatabase().execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
db.getWritableDatabase().execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
fail("unique constraint not enforced");
} catch (SQLiteConstraintException e) {
assertTrue(e.getMessage().contains("UNIQUE constraint failed: tracks.uuid"));
@@ -168,7 +170,8 @@ public class CustomSQLiteOpenHelperTest {
public void upgrade_data_to_30() {
// given: a track in version 29
createVersion23();
try (SQLiteDatabase db29 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 29).getWritableDatabase()) {
try (CustomSQLiteOpenHelper db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 29)) {
SQLiteDatabase db29 = db.getWritableDatabase();
db29.beginTransaction();
db29.execSQL("INSERT INTO tracks (_id) VALUES (1)");
@@ -201,7 +204,8 @@ public class CustomSQLiteOpenHelperTest {
}
// when / then
try (SQLiteDatabase db30 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 30).getWritableDatabase()) {
try (CustomSQLiteOpenHelper db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 30)) {
SQLiteDatabase db30 = db.getWritableDatabase();
{
// Track 1
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
@@ -261,7 +265,7 @@ public class CustomSQLiteOpenHelperTest {
private void createVersion23() {
// Manually create database schema with version 23 (base version)
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
try(SQLiteOpenHelper dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
@Override
public void onCreate(SQLiteDatabase db) {
}
@@ -269,12 +273,10 @@ public class CustomSQLiteOpenHelperTest {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}.getWritableDatabase();
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
dbBase.close();
}) {
dbBase.getWritableDatabase().execSQL(TRACKS_CREATE_TABLE_V23);
dbBase.getWritableDatabase().execSQL(TRACKPOINTS_CREATE_TABLE_V23);
dbBase.getWritableDatabase().execSQL(WAYPOINTS_CREATE_TABLE_V23);
}
}
}
@@ -9,6 +9,7 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import de.dennisguse.opentracks.data.models.Track;
@@ -20,7 +21,7 @@ public class TrackSelectionTest extends TestCase {
TrackSelection filter = new TrackSelection();
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertNull(selection.selection());
@@ -34,12 +35,11 @@ public class TrackSelectionTest extends TestCase {
TrackSelection filter = new TrackSelection().addTrackId(trackId);
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertEquals("_id IN (?)", selection.selection());
assertEquals(1, selection.selectionArgs().length);
assertEquals("1", selection.selectionArgs()[0]);
assertEquals(List.of("1"), selection.selectionArgs());
}
@Test
@@ -54,28 +54,24 @@ public class TrackSelectionTest extends TestCase {
.addTrackId(trackId3);
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertEquals("_id IN (?,?,?)", selection.selection());
assertEquals(3, selection.selectionArgs().length);
assertEquals("1", selection.selectionArgs()[0]);
assertEquals("2", selection.selectionArgs()[1]);
assertEquals("3", selection.selectionArgs()[2]);
assertEquals(List.of("1", "2", "3"), selection.selectionArgs());
}
@Test
public void testFilterBuildSelection_onlyOneCategory() {
public void testFilterBuildSelection_onlyOneactivity_type_localized() {
// given
TrackSelection filter = new TrackSelection().addActivityType("running");
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertEquals("category IN (?)", selection.selection());
assertEquals(1, selection.selectionArgs().length);
assertEquals("running", selection.selectionArgs()[0]);
assertEquals("activity_type_localized IN (?)", selection.selection());
assertEquals(List.of("running"), selection.selectionArgs());
}
@Test
@@ -88,15 +84,11 @@ public class TrackSelectionTest extends TestCase {
.addActivityType("trail walking");
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertEquals("category IN (?,?,?,?)", selection.selection());
assertEquals(4, selection.selectionArgs().length);
assertEquals("running", selection.selectionArgs()[0]);
assertEquals("road biking", selection.selectionArgs()[1]);
assertEquals("mountain biking", selection.selectionArgs()[2]);
assertEquals("trail walking", selection.selectionArgs()[3]);
assertEquals("activity_type_localized IN (?,?,?,?)", selection.selection());
assertEquals(List.of("running", "road biking", "mountain biking", "trail walking"), selection.selectionArgs());
}
@Test
@@ -109,9 +101,9 @@ public class TrackSelectionTest extends TestCase {
TrackSelection filterOk = new TrackSelection().addDateRange(instant, instant.plus(Duration.ofDays(1)));
// when
SelectionData selectionWrong1 = filterWrong1.buildSelection();
SelectionData selectionWrong2 = filterWrong2.buildSelection();
SelectionData selectionOk = filterOk.buildSelection();
ContentProviderUtils.SelectionData selectionWrong1 = filterWrong1.buildSelection();
ContentProviderUtils.SelectionData selectionWrong2 = filterWrong2.buildSelection();
ContentProviderUtils.SelectionData selectionOk = filterOk.buildSelection();
// Then
assertNull(selectionWrong1.selection());
@@ -120,10 +112,8 @@ public class TrackSelectionTest extends TestCase {
assertNull(selectionWrong2.selection());
assertNull(selectionWrong2.selectionArgs());
assertEquals("starttime BETWEEN ? AND ?", selectionOk.selection());
assertEquals(2, selectionOk.selectionArgs().length);
assertEquals(selectionOk.selectionArgs()[0], Long.toString(instant.toEpochMilli()));
assertEquals(selectionOk.selectionArgs()[1], Long.toString(instant.toEpochMilli() + oneDay));
assertEquals("time_start BETWEEN ? AND ?", selectionOk.selection());
assertEquals(List.of(Long.toString(instant.toEpochMilli()), Long.toString(instant.toEpochMilli() + oneDay)) , selectionOk.selectionArgs());
}
@Test
@@ -140,16 +130,11 @@ public class TrackSelectionTest extends TestCase {
.addActivityType("road biking");
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertEquals("_id IN (?,?,?) AND category IN (?,?)", selection.selection());
assertEquals(5, selection.selectionArgs().length);
assertEquals("1", selection.selectionArgs()[0]);
assertEquals("2", selection.selectionArgs()[1]);
assertEquals("3", selection.selectionArgs()[2]);
assertEquals("running", selection.selectionArgs()[3]);
assertEquals("road biking", selection.selectionArgs()[4]);
assertEquals("_id IN (?,?,?) AND activity_type_localized IN (?,?)", selection.selection());
assertEquals(List.of("1", "2", "3", "running", "road biking"), selection.selectionArgs());
}
@Test
@@ -169,16 +154,11 @@ public class TrackSelectionTest extends TestCase {
.addDateRange(instant, instant.plusMillis(oneDay));
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertEquals("_id IN (?,?,?) AND starttime BETWEEN ? AND ?", selection.selection());
assertEquals(5, selection.selectionArgs().length);
assertEquals("1", selection.selectionArgs()[0]);
assertEquals("2", selection.selectionArgs()[1]);
assertEquals("3", selection.selectionArgs()[2]);
assertEquals(selection.selectionArgs()[3], Long.toString(instant.toEpochMilli()));
assertEquals(selection.selectionArgs()[4], Long.toString(instant.toEpochMilli() + oneDay));
assertEquals("_id IN (?,?,?) AND time_start BETWEEN ? AND ?", selection.selection());
assertEquals(List.of("1", "2", "3", Long.toString(instant.toEpochMilli()), Long.toString(instant.toEpochMilli() + oneDay)), selection.selectionArgs());
}
@Test
@@ -193,14 +173,10 @@ public class TrackSelectionTest extends TestCase {
.addDateRange(instant, instant.plusMillis(oneDay));
// when
SelectionData selection = filter.buildSelection();
ContentProviderUtils.SelectionData selection = filter.buildSelection();
// Then
assertEquals("category IN (?,?) AND starttime BETWEEN ? AND ?", selection.selection());
assertEquals(4, selection.selectionArgs().length);
assertEquals("running", selection.selectionArgs()[0]);
assertEquals("road biking", selection.selectionArgs()[1]);
assertEquals(selection.selectionArgs()[2], Long.toString(instant.toEpochMilli()));
assertEquals(selection.selectionArgs()[3], Long.toString(instant.toEpochMilli() + oneDay));
assertEquals("activity_type_localized IN (?,?) AND time_start BETWEEN ? AND ?", selection.selection());
assertEquals(List.of("running", "road biking", Long.toString(instant.toEpochMilli()), Long.toString(instant.toEpochMilli() + oneDay)), selection.selectionArgs());
}
}
@@ -28,7 +28,7 @@ public class SpeedFormatterTest {
assertEquals("4:59", formatter.getSpeedParts(Speed.of(3.34)).first);
assertEquals("5:00", formatter.getSpeedParts(Speed.of(3.33)).first);
assertEquals("min/km", formatter.getSpeedParts(Speed.zero()).second);
assertEquals("min/km", formatter.getSpeedParts(Speed.ZERO).second);
}
@Test
@@ -39,7 +39,7 @@ public class SpeedFormatterTest {
.build(context);
assertEquals("8:02", formatter.getSpeedParts(Speed.of(3.34)).first);
assertEquals("min/mi", formatter.getSpeedParts(Speed.zero()).second);
assertEquals("min/mi", formatter.getSpeedParts(Speed.ZERO).second);
}
@Test
@@ -50,7 +50,7 @@ public class SpeedFormatterTest {
.build(context);
assertEquals("9:14", formatter.getSpeedParts(Speed.of(3.34)).first);
assertEquals("min/NM", formatter.getSpeedParts(Speed.zero()).second);
assertEquals("min/NM", formatter.getSpeedParts(Speed.ZERO).second);
}
@Test
@@ -61,7 +61,7 @@ public class SpeedFormatterTest {
.build(context);
assertEquals("11.9", formatter.getSpeedParts(Speed.of(3.31)).first);
assertEquals("km/h", formatter.getSpeedParts(Speed.zero()).second);
assertEquals("km/h", formatter.getSpeedParts(Speed.ZERO).second);
}
@Test
@@ -72,7 +72,7 @@ public class SpeedFormatterTest {
.build(context);
assertEquals("7.5", formatter.getSpeedParts(Speed.of(3.34)).first);
assertEquals("mph", formatter.getSpeedParts(Speed.zero()).second);
assertEquals("mph", formatter.getSpeedParts(Speed.ZERO).second);
}
@Test
@@ -83,7 +83,7 @@ public class SpeedFormatterTest {
.build(context);
assertEquals("6.5", formatter.getSpeedParts(Speed.of(3.34)).first);
assertEquals("kn", formatter.getSpeedParts(Speed.zero()).second);
assertEquals("kn", formatter.getSpeedParts(Speed.ZERO).second);
}
@Test
@@ -0,0 +1,65 @@
package de.dennisguse.opentracks.data.models;
import static org.junit.Assert.assertEquals;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
@RunWith(AndroidJUnit4.class)
public class StatisticsTest {
@Test
public void testMerge() {
// given
Statistics subject = new Statistics(
Instant.ofEpochMilli(1000),
Instant.ofEpochMilli(2500),
Duration.ofMillis(1500),
Duration.ofMillis(700),
Distance.of(750.0),
Speed.of(60.0),
new AltitudeExtremities(1200, 1250),
new AltitudeGainLoss(50, 0),
HeartRate.of(100),
null
);
Statistics append = new Statistics(
Instant.ofEpochMilli(3000),
Instant.ofEpochMilli(4000),
Duration.ofMillis(1000),
Duration.ofMillis(600),
Distance.of(350.0),
Speed.of(30.0),
new AltitudeExtremities(2800.0, 3575.0),
new AltitudeGainLoss(850, 0),
HeartRate.of(200),
null
);
// when
Statistics result = subject.merge(append);
// then
assertEquals(
new Statistics(
Instant.ofEpochMilli(1000),
Instant.ofEpochMilli(4000),
Duration.ofMillis(2500),
Duration.ofMillis(1300),
Distance.of(1100),
Speed.of(60),
new AltitudeExtremities(1200, 3575),
new AltitudeGainLoss(900, 0),
HeartRate.of(150),
null
)
, result
);
}
}
@@ -4,11 +4,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
@@ -34,10 +35,16 @@ public class TrackFilenameGenerator2Test {
@Test(expected = TrackFilenameGenerator.TemplateInvalidException.class)
public void testFilenameTemplate() {
// given
Track track = new Track();
track.setName("Best Track");
track.setUuid(UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"));
track.setStatistics(new Statistics(Instant.parse("2020-02-02T02:02:02Z"), null, null, null, null, null, null, null, null, null));
Track track = new Track(
null,
UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"),
"Berlin",
"",
"",
ActivityType.CYCLING,
ZoneOffset.UTC,
Statistics.DEFAULT
);
// when
subject.format(track, TrackFileFormat.GPX);
@@ -10,6 +10,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Collection;
import java.util.TimeZone;
@@ -48,10 +49,16 @@ public class TrackFilenameGeneratorTest {
@Test
public void testFilenameTemplate() {
// given
Track track = new Track();
track.setName("Best Track");
track.setUuid(UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"));
track.setStatistics(new Statistics(Instant.parse("2020-02-02T02:02:02Z"), null, null, null, null, null, null, null, null, null));
Track track = new Track(
null,
UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"),
"Best Track",
"",
"",
null,
ZoneOffset.UTC,
new Statistics(Instant.parse("2020-02-02T02:02:02Z"), Instant.parse("2020-02-02T02:02:02Z"), null, null, null, null, null, null, null, null)
);
// when
String filename = subject.format(track, TrackFileFormat.GPX);
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.io.file.importer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -52,6 +51,7 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
@@ -62,6 +62,7 @@ import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
@@ -150,7 +151,16 @@ public class ExportImportTest {
Distance sensorDistance = Distance.of(10); // recording distance interval
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 1f);
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 1", "Marker 1 desc", "Marker 1 category", null, null));
contentProviderUtils.insertMarker(
new Marker(
null,
trackId,
"Marker 1",
"Marker 1 desc",
"Marker 1 typeLocalized",
service.getLastStoredTrackPointWithLocation().getPosition(),
null
));
// A sensor-only TrackPoint
trackPointCreator.setClock("2020-02-02T02:02:04Z");
@@ -164,7 +174,16 @@ public class ExportImportTest {
mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null); // Distance will be added to next TrackPoint
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 0f);
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 2", "Marker 2 desc", "Marker 2 category", null, null));
contentProviderUtils.insertMarker(
new Marker(
null,
trackId,
"Marker 2",
"Marker 2 desc",
"Marker 2 typeLocalized",
service.getLastStoredTrackPointWithLocation().getPosition(),
null
));
trackPointCreator.setClock("2020-02-02T02:02:18Z");
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet(trackPointCreator);
@@ -186,15 +205,14 @@ public class ExportImportTest {
trackPointCreator.setClock("2020-02-02T02:04:00Z");
service.endCurrentTrack();
Track track = contentProviderUtils.getTrack(trackId);
track.setActivityType(TRACK_ACTIVITY_TYPE);
track.setActivityTypeLocalized(TRACK_ACTIVITY_TYPE_LOCALIZED);
track.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.updateTrack(track);
TrackBuilder trackBuilder = new TrackBuilder(contentProviderUtils.getTrack(trackId));
trackBuilder.setActivityType(TRACK_ACTIVITY_TYPE);
trackBuilder.setActivityTypeLocalized(TRACK_ACTIVITY_TYPE_LOCALIZED);
trackBuilder.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.updateTrack(trackBuilder.getTrack());
track = contentProviderUtils.getTrack(trackId);
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
markers = contentProviderUtils.getMarkers(trackId);
markers = TestDataUtil.getMarkers(contentProviderUtils, trackId);
}
@LargeTest
@@ -203,29 +221,23 @@ public class ExportImportTest {
setUp();
Track track = contentProviderUtils.getTrack(trackId);
Statistics trackStatistics = track.getStatistics();
Statistics trackStatistics = track.statistics();
assertEquals(ZoneOffset.of("+01:00"), track.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), trackStatistics.startTime());
assertEquals(Instant.parse("2020-02-02T02:04:00Z"), trackStatistics.stopTime());
assertEquals(ZoneOffset.of("+01:00"), track.zoneOffset());
assertEquals(Duration.ofSeconds(56), trackStatistics.totalTime());
assertEquals(Duration.ofSeconds(26), trackStatistics.movingTime()); //TODO Likely too low
// Distance
assertEquals(222049.34375, trackStatistics.totalDistance().toM(), 0.01); //TODO Too low
// Speed
assertEquals(8540.359, trackStatistics.maxSpeed().toMPS(), 0.01);
assertEquals(3965.166, trackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8540.359, trackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(999.122, trackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.25, trackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, trackStatistics.altitudeGainLoss().gain_m(), 0.01);
assertEquals(2, trackStatistics.altitudeGainLoss().loss_m(), 0.01);
assertEquals(new Statistics(
Instant.parse("2020-02-02T02:02:02Z"),
Instant.parse("2020-02-02T02:04:00Z"),
Duration.ofSeconds(56),
Duration.ofSeconds(26), //TODO Likely too low
Distance.of(222049.34375), //TODO Too low
Speed.of(22203.734375),
new AltitudeExtremities(999.1229858398438, 1020.25),
new AltitudeGainLoss(2, 2),
null,
null
),
trackStatistics);
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
new TrackPointAssert().assertEquals(List.of(
@@ -315,41 +327,32 @@ public class ExportImportTest {
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals(track.getActivityType(), importedTrack.getActivityType());
assertEquals(track.getActivityTypeLocalized(), importedTrack.getActivityTypeLocalized());
assertEquals(track.getDescription(), importedTrack.getDescription());
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.activityType(), importedTrack.activityType());
assertEquals(track.activityTypeLocalized(), importedTrack.activityTypeLocalized());
assertEquals(track.description(), importedTrack.description());
assertEquals(track.name(), importedTrack.name());
// 2. trackpoints
TrackPointAssert a = new TrackPointAssert();
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
a.assertEquals(trackPoints, actual);
new TrackPointAssert().assertEquals(trackPoints, actual);
// 3. trackstatistics
Statistics importedTrackStatistics = importedTrack.getStatistics();
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.startTime());
assertEquals(Instant.parse("2020-02-02T02:04:00Z"), importedTrackStatistics.stopTime());
assertEquals(Duration.ofSeconds(56), importedTrackStatistics.totalTime());
assertEquals(Duration.ofSeconds(26), importedTrackStatistics.movingTime());
// Distance
assertEquals(222049.421, importedTrackStatistics.totalDistance().toM(), 0.01);
// Speed
assertEquals(8540.362, importedTrackStatistics.maxSpeed().toMPS(), 0.01);
assertEquals(3965.168, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8540.362, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(999.122, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.25, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, importedTrackStatistics.altitudeGainLoss().gain_m(), 0.01);
assertEquals(2, importedTrackStatistics.altitudeGainLoss().loss_m(), 0.01);
Statistics importedTrackStatistics = importedTrack.statistics();
assertEquals(track.zoneOffset(), importedTrack.zoneOffset());
assertEquals(new Statistics(
Instant.parse("2020-02-02T02:02:02Z"),
Instant.parse("2020-02-02T02:04:00Z"),
Duration.ofSeconds(56),
Duration.ofSeconds(26), //TODO Likely too low
Distance.of(222049.421875), //TODO Too low
Speed.of(22203.7421875),
new AltitudeExtremities(999.1229858398438, 1020.25),
new AltitudeGainLoss(2, 2),
null,
null
),
importedTrackStatistics);
// 4. markers
assertMarkers();
@@ -405,10 +408,10 @@ public class ExportImportTest {
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals(track.getActivityType(), importedTrack.getActivityType());
assertEquals(track.getActivityTypeLocalized(), importedTrack.getActivityTypeLocalized());
assertEquals(track.getDescription(), importedTrack.getDescription());
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.activityType(), importedTrack.activityType());
assertEquals(track.activityTypeLocalized(), importedTrack.activityTypeLocalized());
assertEquals(track.description(), importedTrack.description());
assertEquals(track.name(), importedTrack.name());
// 2. trackpoints
// The GPX exporter does not support exporting TrackPoints without lat/lng.
@@ -466,29 +469,22 @@ public class ExportImportTest {
), actual);
// 3. trackstatistics
Statistics importedTrackStatistics = importedTrack.getStatistics();
assertEquals(track.zoneOffset(), importedTrack.zoneOffset());
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.startTime());
assertEquals(Instant.parse("2020-02-02T02:03:50Z"), importedTrackStatistics.stopTime());
assertEquals(Duration.ofSeconds(107), importedTrackStatistics.totalTime());
assertEquals(Duration.ofSeconds(107), importedTrackStatistics.movingTime());
// Distance
assertEquals(222271.734, importedTrackStatistics.totalDistance().toM(), 0.01);
// Speed
assertEquals(2077.305, importedTrackStatistics.maxSpeed().toMPS(), 0.01);
assertEquals(2077.305, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(2077.305, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(999.099, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.2, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, importedTrackStatistics.altitudeGainLoss().gain_m(), 0.01);
assertEquals(2, importedTrackStatistics.altitudeGainLoss().loss_m(), 0.01);
Statistics importedTrackStatistics = importedTrack.statistics();
assertEquals(new Statistics(
Instant.parse("2020-02-02T02:02:03Z"),
Instant.parse("2020-02-02T02:03:50Z"),
Duration.ofSeconds(107),
Duration.ofSeconds(107),
Distance.of(222271.734375),
Speed.of(2077.305908203125),
new AltitudeExtremities(999.0999755859375, 1020.2000122070312),
new AltitudeGainLoss(2, 2),
null,
null
),
importedTrackStatistics);
// 4. markers
assertMarkers();
@@ -553,21 +549,20 @@ public class ExportImportTest {
}
private void assertMarkers() {
assertEquals(markers.size(), contentProviderUtils.getMarkers(importTrackId).size());
List<Marker> importedMarkers = TestDataUtil.getMarkers(contentProviderUtils, importTrackId);
assertEquals(markers.size(), importedMarkers.size());
List<Marker> importedMarkers = contentProviderUtils.getMarkers(importTrackId);
for (int i = 0; i < markers.size(); i++) {
Marker marker = markers.get(i);
Marker importMarker = importedMarkers.get(i);
assertEquals(marker.getCategory(), importMarker.getCategory());
assertEquals(marker.getDescription(), importMarker.getDescription());
// assertEquals(marker.getIcon(), importMarker.getIcon()); // TODO for KML
assertEquals(marker.getName(), importMarker.getName());
assertFalse(importMarker.hasPhoto());
assertEquals(marker.typeLocalized(), importMarker.typeLocalized());
assertEquals(marker.description(), importMarker.description());
assertEquals(marker.name(), importMarker.name());
assertNull(importMarker.photoUrl());
assertEquals(marker.getPosition().latitude(), importMarker.getPosition().latitude(), 0.001);
assertEquals(marker.getPosition().longitude(), importMarker.getPosition().longitude(), 0.001);
assertEquals(marker.getPosition().altitude().toM(), importMarker.getPosition().altitude().toM(), 0.1);
assertEquals(marker.position().latitude(), importMarker.position().latitude(), 0.001);
assertEquals(marker.position().longitude(), importMarker.position().longitude(), 0.001);
assertEquals(marker.position().altitude().toM(), importMarker.position().altitude().toM(), 0.1);
}
}
@@ -61,73 +61,6 @@ public class GPXTrackImporterTest {
}
}
/**
* Check that data that contains pause (lat=100, lng=0) and resume (lat=200, lng=0) locations are restored to a segment break.
*/
@LargeTest
@Test
public void gpx_with_pause_resume() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_gpx_pause_resume);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
assertEquals("the category", importedTrack.getActivityTypeLocalized());
assertEquals("the description", importedTrack.getDescription());
assertEquals("2021-01-07 22:51", importedTrack.getName());
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
//TODO Check trackstatistics
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
// first segment
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
Instant.parse("2021-01-07T21:51:59.179Z"),
3d, 14d, null,
Altitude.WGS84.of(10), null,
null,
null)),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2021-01-07T21:52:00.653Z"),
3d, 14.001, null,
Altitude.WGS84.of(10), null,
null,
Speed.of(75.4192))),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2021-01-07T21:52:01.010Z"),
3d, 14.002, null,
Altitude.WGS84.of(10), null,
null, Speed.of(311.3948))),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2021-01-07T21:52:02.658Z")),
// created resume trackpoint with time of next valid trackpoint
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2021-01-07T21:52:03.873Z")),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2021-01-07T21:52:04.103Z"),
3d, 14.003, null,
Altitude.WGS84.of(10), null,
null,
null))
), importedTrackPoints);
}
@LargeTest
@Test
public void gpx_without_speed() throws IOException {
@@ -143,15 +76,15 @@ public class GPXTrackImporterTest {
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getActivityTypeLocalized());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
assertEquals("", importedTrack.activityTypeLocalized());
assertEquals("", importedTrack.description());
assertEquals("20210907_213924.gpx", importedTrack.name());
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
// 3. trackstatistics
Statistics trackStatistics = importedTrack.getStatistics();
Statistics trackStatistics = importedTrack.statistics();
assertEquals(0.75, trackStatistics.maxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(101), trackStatistics.movingTime());
assertEquals(Duration.ofSeconds(101), trackStatistics.movingDuration());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
@@ -198,15 +131,15 @@ public class GPXTrackImporterTest {
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getActivityTypeLocalized());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
assertEquals("", importedTrack.activityTypeLocalized());
assertEquals("", importedTrack.description());
assertEquals("20210907_213924.gpx", importedTrack.name());
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
// 3. trackstatistics
Statistics trackStatistics = importedTrack.getStatistics();
Statistics trackStatistics = importedTrack.statistics();
assertEquals(5.0, trackStatistics.maxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(101), trackStatistics.movingTime());
assertEquals(Duration.ofSeconds(101), trackStatistics.movingDuration());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
@@ -314,19 +247,19 @@ public class GPXTrackImporterTest {
// given
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
try(InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
trackExporter.writeTrack(List.of(importedTrack), outputStream);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
trackExporter.writeTrack(List.of(importedTrack), outputStream);
// then
assertEquals(new String(inputStreamExpected.readAllBytes(), StandardCharsets.UTF_8), outputStream.toString());
// then
assertEquals(new String(inputStreamExpected.readAllBytes(), StandardCharsets.UTF_8), outputStream.toString());
}
}
}
@@ -73,13 +73,13 @@ public class KMLTrackImporterTest {
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getActivityTypeLocalized());
assertEquals("", importedTrack.getDescription());
assertEquals("", importedTrack.getName());
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
assertEquals("", importedTrack.activityTypeLocalized());
assertEquals("", importedTrack.description());
assertEquals("", importedTrack.name());
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
// 2. markers
assertEquals(0, contentProviderUtils.getMarkers(importTrackId).size());
assertEquals(0, TestDataUtil.getMarkers(contentProviderUtils, importTrackId).size());
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
@@ -129,13 +129,13 @@ public class KMLTrackImporterTest {
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getActivityTypeLocalized());
assertEquals("", importedTrack.getDescription());
assertEquals("", importedTrack.getName());
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
assertEquals("", importedTrack.activityTypeLocalized());
assertEquals("", importedTrack.description());
assertEquals("", importedTrack.name());
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
// 2. markers
assertEquals(0, contentProviderUtils.getMarkers(importTrackId).size());
assertEquals(0, TestDataUtil.getMarkers(contentProviderUtils, importTrackId).size());
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
@@ -170,13 +170,14 @@ public class KMLTrackImporterTest {
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("unknown", importedTrack.getActivityTypeLocalized());
assertEquals("Test Track", importedTrack.getDescription());
assertEquals("2020-11-28 18:06", importedTrack.getName());
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
assertEquals("unknown", importedTrack.activityTypeLocalized());
assertEquals("Test Track", importedTrack.description());
assertEquals("2020-11-28 18:06", importedTrack.name());
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
// 2. markers
assertEquals(1, contentProviderUtils.getMarkers(importTrackId).size());
//TODO Check actual content (Marker should be record for this)
assertEquals(1, TestDataUtil.getMarkers(contentProviderUtils, importTrackId).size());
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
@@ -203,7 +204,7 @@ public class KMLTrackImporterTest {
12.340057, 1.23405, null,
Altitude.WGS84.of(421.8070983886719), null,
null,
Speed.of(0))),
Speed.ZERO)),
// created resume trackpoint with time of next valid trackpoint
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
@@ -211,21 +212,21 @@ public class KMLTrackImporterTest {
12.340057, 1.23405, null,
Altitude.WGS84.of(419.93902587890625), null,
null,
Speed.of(0))),
Speed.ZERO)),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-11-28T17:06:56.905Z"),
12.340057, 1.23405, null,
Altitude.WGS84.of(419.9036560058594), null,
null,
Speed.of(0))),
Speed.ZERO)),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-11-28T17:07:20.870Z"),
12.340082, 1.234046, null,
Altitude.WGS84.of(417.99432373046875), null,
null,
Speed.of(0)))
Speed.ZERO))
), importedTrackPoints);
}
@@ -49,9 +49,9 @@ public class PublicApiTest {
List<Track> tracks = new ContentProviderUtils(context).getTracks();
Assert.assertEquals(1, tracks.size());
Track track = tracks.get(0);
Assert.assertEquals("trackName", track.getName());
Assert.assertEquals("activityTypeLocalized", track.getActivityTypeLocalized());
Assert.assertEquals("airplane", track.getActivityType().getId());
Assert.assertEquals("trackName", track.name());
Assert.assertEquals("activityTypeLocalized", track.activityTypeLocalized());
Assert.assertEquals("airplane", track.activityType().getId());
}
@Test
@@ -125,6 +125,6 @@ public class SensorDataCyclingTest {
// TODO See #953
// assertEquals(2, current.getValue().getDistance().toM(), 0.01);
// assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
assertEquals(new AggregatorCyclingDistanceSpeed.Data(Distance.of(0), Distance.of(0), Speed.of(0)), current.getAggregatedValue(Instant.MIN));
assertEquals(new AggregatorCyclingDistanceSpeed.Data(Distance.ZERO, Distance.ZERO, Speed.ZERO), current.getAggregatedValue(Instant.MIN));
}
}
@@ -16,6 +16,10 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.ZoneOffset;
import java.util.UUID;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Position;
@@ -44,7 +48,7 @@ public class TrackRecordingServiceNotificationManagerTest {
null, null, Distance.of(999f),
Altitude.WGS84.of(10), null,
null,
Speed.of(0))
Speed.ZERO)
);
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean()))
@@ -53,18 +57,17 @@ public class TrackRecordingServiceNotificationManagerTest {
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
subject.setUnitSystem(UnitSystem.METRIC);
Track track = new Track(new Statistics(
Track track = new Track(
null,
null,
null,
null,
Distance.of(0),
null,
null,
null,
null,
null
));
UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"),
"Berlin",
"",
"",
ActivityType.CYCLING,
ZoneOffset.UTC,
Statistics.DEFAULT
);
RecordingData data = new RecordingData(track, trackPointMock, null, null);
// when
@@ -9,6 +9,7 @@ import android.location.Location;
import android.location.LocationManager;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.core.location.LocationManagerCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -33,7 +34,6 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
@@ -52,9 +52,6 @@ import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Tests insert location.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceRecordingTest {
@@ -110,8 +107,8 @@ public class TrackRecordingServiceRecordingTest {
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(startTime), Duration.ZERO, Duration.ZERO, Distance.of(0), Speed.zero(), new AltitudeExtremities(0.0, 0.0), null, null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(startTime), Duration.ZERO, Duration.ZERO, Distance.ZERO, Speed.ZERO, null, null, null, null)
, contentProviderUtils.getTrack(trackId).statistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime))
@@ -124,8 +121,8 @@ public class TrackRecordingServiceRecordingTest {
service.endCurrentTrack();
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.of(0), Speed.zero(), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.ZERO, Speed.ZERO, null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
@@ -199,8 +196,8 @@ public class TrackRecordingServiceRecordingTest {
service.endCurrentTrack();
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(pauseTime), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.of(0), Speed.zero(), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(pauseTime), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.ZERO, Speed.ZERO, null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
@@ -214,8 +211,8 @@ public class TrackRecordingServiceRecordingTest {
service.resumeTrack(trackId);
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(resumeTime), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.of(0), Speed.zero(), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(resumeTime), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.ZERO, Speed.ZERO, null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
@@ -342,24 +339,24 @@ public class TrackRecordingServiceRecordingTest {
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps1), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.of(0), Speed.of(15), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps1), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.ZERO, Speed.of(15), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
sendGPSLocation(trackPointCreator, gps2, 45.0001, 35.0, 1, 15);
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps2), Duration.ofSeconds(4), Duration.ofSeconds(4), Distance.of(11.113178253173828), Speed.of(15), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps2), Duration.ofSeconds(4), Duration.ofSeconds(4), Distance.of(11.113178253173828), Speed.of(15), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
// when
String gps3 = "2020-02-02T02:02:08Z";
sendGPSLocation(trackPointCreator, gps3, 45.0002, 35.0, 1, 15);
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps3), Duration.ofSeconds(6), Duration.ofSeconds(6), Distance.of(22.226356506347656), Speed.of(15), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps3), Duration.ofSeconds(6), Duration.ofSeconds(6), Distance.of(22.226356506347656), Speed.of(15), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
@@ -367,8 +364,8 @@ public class TrackRecordingServiceRecordingTest {
service.endCurrentTrack();
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.of(22.226356506347656), Speed.of(15), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.of(22.226356506347656), Speed.of(15), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
@@ -416,22 +413,22 @@ public class TrackRecordingServiceRecordingTest {
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// then
Statistics gps1Statistics = new Statistics(Instant.parse(startTime), Instant.parse(gps1), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.of(0), Speed.of(15), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null);
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getStatistics());
Statistics gps1Statistics = new Statistics(Instant.parse(startTime), Instant.parse(gps1), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.ZERO, Speed.of(15), null, new AltitudeGainLoss(0, 0), null, null);
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).statistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
sendGPSLocation(trackPointCreator, gps2, 45.00001, 35.0, 1, 15);
// then
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).statistics());
// when
String gps3 = "2020-02-02T02:02:08Z";
sendGPSLocation(trackPointCreator, gps3, 45.00002, 35.0, 1, 15);
// then
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).statistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
@@ -439,8 +436,8 @@ public class TrackRecordingServiceRecordingTest {
service.endCurrentTrack();
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.of(2.222635507583618), Speed.of(15), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.of(2.222635507583618), Speed.of(15), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
@@ -538,8 +535,8 @@ public class TrackRecordingServiceRecordingTest {
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 100, 15);
// then
Statistics startStatistics = new Statistics(Instant.parse(startTime), Instant.parse(startTime), Duration.ofSeconds(0), Duration.ofSeconds(0), Distance.of(0), Speed.of(0), new AltitudeExtremities(0.0, 0.0), null, null, null);
assertEquals(startStatistics, contentProviderUtils.getTrack(trackId).getStatistics());
Statistics startStatistics = new Statistics(Instant.parse(startTime), Instant.parse(startTime), Duration.ZERO, Duration.ZERO, Distance.ZERO, Speed.ZERO, null, null, null, null);
assertEquals(startStatistics, contentProviderUtils.getTrack(trackId).statistics());
// when
@@ -547,7 +544,7 @@ public class TrackRecordingServiceRecordingTest {
sendGPSLocation(trackPointCreator, gps2, 45.1, 35.0, 100, 15);
// then
assertEquals(startStatistics, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(startStatistics, contentProviderUtils.getTrack(trackId).statistics());
// when
@@ -556,8 +553,8 @@ public class TrackRecordingServiceRecordingTest {
service.endCurrentTrack();
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.of(0), Speed.of(0), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.ZERO, Speed.ZERO, null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
// then
new TrackPointAssert().assertEquals(List.of(
@@ -582,16 +579,16 @@ public class TrackRecordingServiceRecordingTest {
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps1), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.of(0), Speed.of(15), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps1), Duration.ofSeconds(1), Duration.ofSeconds(1), Distance.ZERO, Speed.of(15), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
sendGPSLocation(trackPointCreator, gps2, 45.1, 35.0, 1, 15);
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps2), Duration.ofSeconds(4), Duration.ofSeconds(4), Distance.of(11113.275390625), Speed.of(2778.31884765625f), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(gps2), Duration.ofSeconds(4), Duration.ofSeconds(4), Distance.of(11113.275390625), Speed.of(2778.31884765625f), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
@@ -599,8 +596,8 @@ public class TrackRecordingServiceRecordingTest {
service.endCurrentTrack();
// then
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.of(11113.275390625), Speed.of(1111.3275146484375), new AltitudeExtremities(0.0, 0.0), new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).getStatistics());
assertEquals(new Statistics(Instant.parse(startTime), Instant.parse(stopTime), Duration.ofSeconds(10), Duration.ofSeconds(10), Distance.of(11113.275390625), Speed.of(1111.3275146484375), null, new AltitudeGainLoss(0, 0), null, null)
, contentProviderUtils.getTrack(trackId).statistics());
// then
@@ -647,7 +644,7 @@ public class TrackRecordingServiceRecordingTest {
// when
String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1);
sensorManager.onChanged(new Raw<>(sensor1, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(0)))); //Should be ignored
sensorManager.onChanged(new Raw<>(sensor1, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.ZERO))); //Should be ignored
// when
String sensor2 = "2020-02-02T02:02:04Z";
@@ -704,7 +701,7 @@ public class TrackRecordingServiceRecordingTest {
null, null,
null,
Speed.of(5)))
.setSensorDistance(Distance.of(0)),
.setSensorDistance(Distance.ZERO),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(sensor3))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(10)),
@@ -723,15 +720,15 @@ public class TrackRecordingServiceRecordingTest {
null, null,
null,
Speed.of(5)))
.setSensorDistance(Distance.of(0)),
.setSensorDistance(Distance.ZERO),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL,
new Position(
Instant.parse(stopTime),
45.001, 35d, Distance.of(1),
null, null,
null,
Speed.of(0))) //Sensor data is now outdated, but we do not fall back to GPS.
.setSensorDistance(Distance.of(0))
Speed.ZERO)) //Sensor data is now outdated, but we do not fall back to GPS.
.setSensorDistance(Distance.ZERO)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@@ -742,6 +739,7 @@ public class TrackRecordingServiceRecordingTest {
return true;
}
@NonNull
@Override
public AltitudeGainLoss getAggregatedValue(Instant now) {
return new AltitudeGainLoss(altitudeGain, altitudeGain);
@@ -111,7 +111,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertFalse(service.isRecording());
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
}
@@ -127,7 +127,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
// when
@@ -136,7 +136,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
// assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue()); TODO BUG: GPS can only be stopped if a service is recording.
}
@@ -153,7 +153,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertTrue(service.isRecording());
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
service.endCurrentTrack();
@@ -165,7 +165,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertTrue(service.isRecording());
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
@@ -176,7 +176,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertFalse(service.isRecording());
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
@@ -188,7 +188,7 @@ public class TrackRecordingServiceStateMachineTest {
assertTrue(service.isRecording());
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
}
@@ -198,7 +198,7 @@ public class TrackRecordingServiceStateMachineTest {
// given
assertFalse(service.isRecording());
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// when
@@ -207,7 +207,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertFalse(service.isRecording());
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
}
@@ -217,7 +217,7 @@ public class TrackRecordingServiceStateMachineTest {
// given
assertFalse(service.isRecording());
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// when
@@ -226,7 +226,7 @@ public class TrackRecordingServiceStateMachineTest {
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
}
@@ -246,7 +246,7 @@ public class TrackRecordingServiceStateMachineTest {
assertTrue(service.isRecording());
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
}
@@ -21,8 +21,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
@RunWith(AndroidJUnit4.class)
public class VoiceAnnouncementManagerTest {
@@ -54,13 +54,32 @@ public class VoiceAnnouncementManagerTest {
voiceAnnouncementManager.setFrequency(Distance.ofKilometer(5));
// when
SegmentStatisticUpdater statistics = new SegmentStatisticUpdater();
statistics.setTotalDistance(Distance.ofKilometer(13));
voiceAnnouncementManager.start(statistics.getStatistics());
voiceAnnouncementManager.start(new Statistics(
null,
null,
Duration.ofSeconds(91),
Duration.ZERO,
Distance.ofKilometer(13),
null,
null,
null,
null,
null
));
assertEquals(Distance.of(15000), voiceAnnouncementManager.getNextTotalDistance());
statistics.setTotalDistance(Distance.of(15100));
voiceAnnouncementManager.start(statistics.getStatistics());
voiceAnnouncementManager.start(new Statistics(
null,
null,
Duration.ofSeconds(91),
Duration.ZERO,
Distance.of(15000),
null,
null,
null,
null,
null
));
assertEquals(Distance.of(20000), voiceAnnouncementManager.getNextTotalDistance());
}
@@ -73,14 +92,37 @@ public class VoiceAnnouncementManagerTest {
VoiceAnnouncementManager voiceAnnouncementManager = new VoiceAnnouncementManager(service);
voiceAnnouncementManager.setFrequency(Duration.ofSeconds(5));
Statistics first = new Statistics(
null,
null,
Duration.ofSeconds(91),
Duration.ZERO,
Distance.ZERO,
null,
null,
null,
null,
null
);
Statistics second = new Statistics(
null,
null,
Duration.ofSeconds(95),
Duration.ZERO,
Distance.ZERO,
null,
null,
null,
null,
null
);
// when
SegmentStatisticUpdater statistics = new SegmentStatisticUpdater();
statistics.setTotalTime(Duration.ofSeconds(91));
voiceAnnouncementManager.start(statistics.getStatistics());
voiceAnnouncementManager.start(first);
assertEquals(Duration.ofSeconds(95), voiceAnnouncementManager.getNextTotalTime());
statistics.setTotalTime(Duration.ofSeconds(95));
voiceAnnouncementManager.start(statistics.getStatistics());
voiceAnnouncementManager.start(second);
assertEquals(Duration.ofSeconds(100), voiceAnnouncementManager.getNextTotalTime());
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.services.announcement;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import android.content.Context;
import android.icu.text.MessageFormat;
@@ -10,6 +9,7 @@ import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Rule;
@@ -19,12 +19,14 @@ import org.mockito.Mockito;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import de.dennisguse.opentracks.LocaleRule;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
@@ -35,9 +37,11 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatisticsUpdater;
//Due to DateTimeFormatter using NBSP, these tests only work in SDK34+
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@RunWith(AndroidJUnit4.class)
public class VoiceAnnouncementUtilsTest {
@@ -57,25 +61,28 @@ public class VoiceAnnouncementUtilsTest {
PreferencesUtils.setVoiceAnnounceLapSpeedPace(true);
}
@Test
public void sdkRequirement() {
assertFalse("Due to DateTimeFormatter using NBSP, these tests only work in SDK34+", Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
}
@Test
public void getAnnouncement_metric_speed() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
Track track = new Track(
null,
null,
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
null,
null,
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -89,18 +96,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_metric_speed_rounding_check() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusSeconds(1),
Distance.of(20000),
Speed.of(100),
Track track = new Track(
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusSeconds(1),
Distance.of(20000),
Speed.of(100),
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -114,18 +129,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_metric_distance_rounding_check() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.of(19999),
Speed.of(100),
Track track = new Track(
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.of(19999),
Speed.of(100),
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -139,18 +162,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_metric_distance_rounding_check_two() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.of(19900),
Speed.of(100),
Track track = new Track(
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.of(19900),
Speed.of(100),
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -166,7 +197,7 @@ public class VoiceAnnouncementUtilsTest {
public void getAnnouncement_withInterval_metric_speed() {
// given
Pair<Track, Statistics> data = buildTrackWithTrackPoints();
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -179,18 +210,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_metric_pace() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
Track track = new Track(
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -219,18 +258,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_imperial_speed() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
Track track = new Track(
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -244,18 +291,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_imperial_speed_1() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.ofMile(1.1),
Track track = new Track(
null,
null,
null,
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.ofMile(1.1),
null,
null,
null,
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -269,18 +324,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_imperial_meter_speed_1() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.ofMile(1.1),
Track track = new Track(
null,
null,
null,
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.ofMile(1.1),
null,
null,
null,
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -294,18 +357,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_metric_speed_1() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.ofKilometer(1.1),
Track track = new Track(
null,
null,
null,
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1),
Distance.ofKilometer(1.1),
null,
null,
null,
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -334,18 +405,26 @@ public class VoiceAnnouncementUtilsTest {
@Test
public void getAnnouncement_imperial_pace() {
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
Track track = new Track(
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ofHours(2).plusMinutes(5).plusSeconds(10),
Duration.ofHours(1).plusMinutes(5).plusSeconds(10),
Distance.of(20000),
Speed.of(100),
null,
new AltitudeGainLoss(6000, 6000),
null,
null
));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class);
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused"));
@@ -445,18 +524,26 @@ public class VoiceAnnouncementUtilsTest {
intervalStatistics.addTrackPoints(trackPoints.iterator());
Statistics lastInterval = intervalStatistics.getLastInterval();
Track track = new Track(new Statistics(
Instant.EPOCH,
Instant.EPOCH.plusSeconds(999),
Duration.ofMinutes(16).plusSeconds(39),
Duration.ofMinutes(16).plusSeconds(39),
Distance.of(14208),
Speed.of(104),
Track track = new Track(
null,
null,
null,
null
));
"",
"",
"",
ActivityType.AIRPLANE,
ZoneOffset.UTC,
new Statistics(
Instant.EPOCH,
Instant.EPOCH.plusSeconds(999),
Duration.ofMinutes(16).plusSeconds(39),
Duration.ofMinutes(16).plusSeconds(39),
Distance.of(14208),
Speed.of(104),
null,
null,
null,
null
));
return new Pair<>(track, lastInterval);
}
@@ -30,6 +30,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Locale;
import de.dennisguse.opentracks.LocaleRule;
@@ -63,19 +64,26 @@ public class TrackShareSummaryGeneratorTest {
@Test
public void testGenerateTrackDescription() {
Track track = new Track(new Statistics(
START_TIME,
START_TIME,
Duration.ofSeconds(600),
Duration.ofSeconds(300),
Distance.of(20000),
Speed.of(100),
new AltitudeExtremities(-500, 550),
new AltitudeGainLoss(6000, 6000),
Track track = new Track(
null,
null
));
track.setActivityTypeLocalized("hiking");
null,
"",
"",
"hiking",
null,
ZoneOffset.UTC,
new Statistics(
START_TIME,
START_TIME,
Duration.ofSeconds(600),
Duration.ofSeconds(300),
Distance.of(20000),
Speed.of(100),
new AltitudeExtremities(-500, 550),
new AltitudeGainLoss(6000, 6000),
null,
null
));
String expected = //"Created by"
"OpenTracks (Debug)\n\n"
@@ -1,128 +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.stats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
/**
* Tests for {@link SegmentStatisticUpdater}.
* This only tests non-trivial pieces of that class.
*
* @author Rodrigo Damazio
*/
@RunWith(AndroidJUnit4.class)
public class SegmentStatisticUpdaterTest {
private SegmentStatisticUpdater statistics;
@Before
public void setUp() {
statistics = new SegmentStatisticUpdater();
}
@Test
public void testMerge_no_data() {
// given
SegmentStatisticUpdater statistics2 = new SegmentStatisticUpdater();
// when
statistics.merge(statistics2);
// then
assertNull(statistics.getStartTime());
assertNull(statistics.getStopTime());
assertEquals(Duration.ofSeconds(0), statistics.getMovingTime());
assertEquals(Duration.ofSeconds(0), statistics.getTotalTime());
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);
assertNull(statistics.getAverageHeartRate());
}
@Test
public void testMerge() {
// given
SegmentStatisticUpdater statistics2 = new SegmentStatisticUpdater();
statistics.setStartTime(Instant.ofEpochMilli(1000)); // Resulting start time
statistics.setStopTime(Instant.ofEpochMilli(2500));
statistics2.setStartTime(Instant.ofEpochMilli(3000));
statistics2.setStopTime(Instant.ofEpochMilli(4000)); // Resulting stop time
statistics.setTotalTime(Duration.ofMillis(1500));
statistics2.setTotalTime(Duration.ofMillis(1000)); // Result: 1500+1000
statistics.setMovingTime(Duration.ofMillis(700));
statistics2.setMovingTime(Duration.ofMillis(600)); // Result: 700+600
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);
statistics.setAverageHeartRate(HeartRate.of(100f));
statistics2.setAverageHeartRate(HeartRate.of(200f));
// when
statistics.merge(statistics2);
// then
assertEquals(Instant.ofEpochMilli(1000), statistics.getStartTime());
assertEquals(Instant.ofEpochMilli(4000), statistics.getStopTime());
assertEquals(Duration.ofMillis(2500), statistics.getTotalTime());
assertEquals(Duration.ofMillis(1300), statistics.getMovingTime());
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);
assertEquals(150.0, statistics.getAverageHeartRate().getBPM(), 0.001);
}
@Test
public void testGetAverageSpeed() {
statistics.setTotalDistance(Distance.of(1000.0));
statistics.setTotalTime(Duration.ofMillis(50000));
assertEquals(20.0, statistics.getAverageSpeed().toMPS(), 0.001);
}
@Test
public void testGetAverageMovingSpeed() {
statistics.setTotalDistance(Distance.of(1000.0));
statistics.setMovingTime(Duration.ofMillis(20000));
assertEquals(50.0, statistics.getAverageMovingSpeed().toMPS(), 0.001);
}
}
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.stats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -16,36 +15,21 @@ import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
@RunWith(AndroidJUnit4.class)
public class TrackStatisticsUpdaterTest {
@Test
public void empty() {
// when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
// then
SegmentStatisticUpdater statistics = subject.getSegmentStatisticUpdater();
assertNull(statistics.getStartTime());
assertNull(statistics.getStopTime());
assertEquals(Duration.ZERO, statistics.getTotalTime());
assertEquals(Duration.ZERO, statistics.getMovingTime());
assertEquals(Speed.of(0), statistics.getAverageSpeed());
assertEquals(Speed.of(0), statistics.getAverageMovingSpeed());
assertEquals(Speed.of(0), statistics.getMaxSpeed());
assertNull(statistics.getTotalAltitudeGain());
assertNull(statistics.getTotalAltitudeLoss());
assertNull(statistics.getAverageHeartRate());
}
@Test
public void startTime() {
// given
@@ -53,24 +37,24 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, startTime);
// when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
subject.addTrackPoint(tp);
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(tp);
// then
SegmentStatisticUpdater statistics = subject.getSegmentStatisticUpdater();
assertEquals(startTime, statistics.getStartTime());
assertEquals(startTime, statistics.getStopTime());
assertEquals(Duration.ZERO, statistics.getTotalTime());
assertEquals(Duration.ZERO, statistics.getMovingTime());
assertEquals(Speed.of(0), statistics.getAverageSpeed());
assertEquals(Speed.of(0), statistics.getAverageMovingSpeed());
assertEquals(Speed.of(0), statistics.getMaxSpeed());
assertNull(statistics.getTotalAltitudeGain());
assertNull(statistics.getTotalAltitudeLoss());
assertNull(statistics.getAverageHeartRate());
assertEquals(
new Statistics(
startTime,
startTime,
Duration.ZERO,
Duration.ZERO,
Distance.ZERO,
Speed.ZERO,
null,
null,
null,
null
),
subject.getTrackStatistics()
);
}
@Test
@@ -79,50 +63,45 @@ public class TrackStatisticsUpdaterTest {
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
// when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
data.trackPoints().forEach(subject::addTrackPoint);
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(data.trackPoints());
// then
SegmentStatisticUpdater statistics = subject.getSegmentStatisticUpdater();
assertEquals(142.26, statistics.getTotalDistance().toM(), 0.01);
assertEquals(Duration.ofSeconds(12), statistics.getTotalTime());
assertEquals(Duration.ofSeconds(12), statistics.getMovingTime());
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
assertEquals(32.5, statistics.getMaxAltitude(), 0.01);
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
assertEquals(11.85, statistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(11.85, statistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(11.85, statistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(106.834f, statistics.getAverageHeartRate().getBPM(), 0.01);
Statistics result = subject.getTrackStatistics();
assertEquals(
new Statistics(
Instant.EPOCH,
Instant.EPOCH.plusSeconds(13),
Duration.ofSeconds(12),
Duration.ofSeconds(12),
Distance.of(142.2637701034546),
Speed.of(12.448093056678772),
new AltitudeExtremities(2.5, 32.5),
new AltitudeGainLoss(36, 36),
HeartRate.of(106.833336f),
Power.of(405.2778f)
),
result
);
}
@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 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
TrackPoint tp3 = createTrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
tp3.setSpeed(Speed.of(5f));
// when
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3));
// then
assertEquals(1.10, subject.getSegmentStatisticUpdater().getTotalDistance().toM(), 0.01);
assertEquals(Distance.of(1.1057428121566772), subject.getTrackStatistics().totalDistance());
}
@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 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
@@ -135,26 +114,22 @@ public class TrackStatisticsUpdaterTest {
tp5.setSensorDistance(Distance.of(10f));
// when
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3));
// then
assertEquals(110.57, subject.getSegmentStatisticUpdater().getTotalDistance().toM(), 0.01);
assertEquals(Distance.of(110.57427215576172), subject.getTrackStatistics().totalDistance());
// when
subject.addTrackPoint(tp4);
subject.addTrackPoint(tp5);
// then
assertEquals(125.57, subject.getSegmentStatisticUpdater().getTotalDistance().toM(), 0.01);
assertEquals(Distance.of(125.57427215576172), subject.getTrackStatistics().totalDistance());
}
@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 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
@@ -166,27 +141,22 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
// when
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3));
// then
assertEquals(5, subject.getSegmentStatisticUpdater().getTotalDistance().toM(), 0.01);
assertEquals(Distance.of(5), subject.getTrackStatistics().totalDistance());
// when
subject.addTrackPoint(tp4);
subject.addTrackPoint(tp5);
// then
assertEquals(59.18, subject.getSegmentStatisticUpdater().getTotalDistance().toM(), 0.01);
assertEquals(Distance.of(59.181396484375), subject.getTrackStatistics().totalDistance());
}
@Test
public void addTrackPoint_maxSpeed_multiple_segments() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
assertEquals(Speed.of(0f), subject.getSegmentStatisticUpdater().getMaxSpeed());
subject.addTrackPoints(List.of(
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1))
.setSpeed(Speed.of(2f)),
@@ -194,28 +164,26 @@ public class TrackStatisticsUpdaterTest {
.setSpeed(Speed.of(2f)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(4))
));
assertEquals(Speed.of(2f), subject.getSegmentStatisticUpdater().getMaxSpeed());
assertEquals(Speed.of(2), subject.getTrackStatistics().maxSpeed());
// when
subject.addTrackPoints(List.of(
List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(5)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(6))
.setSpeed(Speed.of(1f)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(7))
.setSpeed(Speed.of(1f)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(8))
));
).forEach(subject::addTrackPoint);
// then
assertEquals(Speed.of(2f), subject.getSegmentStatisticUpdater().getMaxSpeed());
assertEquals(Speed.of(2f), subject.getTrackStatistics().maxSpeed());
}
@Test
public void addTrackPoint_idle_withoutDistance() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
// when
subject.addTrackPoints(List.of(
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1))
.setSpeed(Speed.of(2f)),
@@ -234,15 +202,13 @@ public class TrackStatisticsUpdaterTest {
));
// then
assertEquals(Duration.ofSeconds(35), subject.getSegmentStatisticUpdater().getMovingTime());
assertEquals(Duration.ofSeconds(35), subject.getTrackStatistics().movingDuration());
}
@Test
public void addTrackPoint_idle_withDistance() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
// when
subject.addTrackPoints(List.of(
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1))
.setSensorDistance(Distance.of(10)),
@@ -264,72 +230,69 @@ public class TrackStatisticsUpdaterTest {
));
// then
assertEquals(Duration.ofSeconds(40), subject.getSegmentStatisticUpdater().getMovingTime());
assertEquals(Distance.of(1040), subject.getSegmentStatisticUpdater().getTotalDistance());
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().movingDuration());
assertEquals(Distance.of(1040), subject.getTrackStatistics().totalDistance());
}
@Test
public void addTrackPoint_idle_remain_idle() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
// when
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)));
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)));
subject.addTrackPoint(
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(10))
.setSensorDistance(Distance.of(10)));
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)));
// then
assertTrue(subject.getSegmentStatisticUpdater().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getSegmentStatisticUpdater().getMovingTime());
assertEquals(Duration.ofSeconds(30), subject.getSegmentStatisticUpdater().getTotalTime());
assertEquals(Distance.of(10), subject.getSegmentStatisticUpdater().getTotalDistance());
assertTrue(subject.isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().totalDuration());
assertEquals(Distance.of(10), subject.getTrackStatistics().totalDistance());
// when
subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40))
.setSensorDistance(Distance.of(0)));
.setSensorDistance(Distance.ZERO));
// then
assertTrue(subject.getSegmentStatisticUpdater().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getSegmentStatisticUpdater().getMovingTime());
assertEquals(Duration.ofSeconds(40), subject.getSegmentStatisticUpdater().getTotalTime());
assertEquals(Distance.of(10), subject.getSegmentStatisticUpdater().getTotalDistance());
assertTrue(subject.isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration());
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().totalDuration());
assertEquals(Distance.of(10), subject.getTrackStatistics().totalDistance());
// when
subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45))
.setSensorDistance(Distance.of(1)));
// then
assertTrue(subject.getSegmentStatisticUpdater().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getSegmentStatisticUpdater().getMovingTime());
assertEquals(Duration.ofSeconds(45), subject.getSegmentStatisticUpdater().getTotalTime());
assertEquals(Distance.of(11), subject.getSegmentStatisticUpdater().getTotalDistance());
assertTrue(subject.isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration());
assertEquals(Duration.ofSeconds(45), subject.getTrackStatistics().totalDuration());
assertEquals(Distance.of(11), subject.getTrackStatistics().totalDistance());
// when
subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(50))
.setSensorDistance(Distance.of(10)));
// then
assertFalse(subject.getSegmentStatisticUpdater().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getSegmentStatisticUpdater().getMovingTime());
assertEquals(Duration.ofSeconds(50), subject.getSegmentStatisticUpdater().getTotalTime());
assertEquals(Distance.of(21), subject.getSegmentStatisticUpdater().getTotalDistance());
assertFalse(subject.isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration());
assertEquals(Duration.ofSeconds(50), subject.getTrackStatistics().totalDuration());
assertEquals(Distance.of(21), subject.getTrackStatistics().totalDistance());
// when
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(60)));
// then
assertFalse(subject.getSegmentStatisticUpdater().isIdle());
assertEquals(Duration.ofSeconds(40), subject.getSegmentStatisticUpdater().getMovingTime());
assertEquals(Duration.ofSeconds(60), subject.getSegmentStatisticUpdater().getTotalTime());
assertEquals(Distance.of(21), subject.getSegmentStatisticUpdater().getTotalDistance());
assertFalse(subject.isIdle());
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().movingDuration());
assertEquals(Duration.ofSeconds(60), subject.getTrackStatistics().totalDuration());
assertEquals(Distance.of(21), subject.getTrackStatistics().totalDistance());
}
@Test
public void copy_constructor() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
@@ -339,19 +302,36 @@ public class TrackStatisticsUpdaterTest {
tp4.setSpeed(Speed.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
subject.addTrackPoint(tp4);
TrackStatisticsUpdater initial = new TrackStatisticsUpdater(List.of(tp1, tp2,tp3, tp4));
// when
TrackStatisticsUpdater copy = new TrackStatisticsUpdater(subject);
subject.addTrackPoint(tp5);
copy.addTrackPoint(tp5);
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(initial, tp5);
// then
assertEquals(55.287, subject.getSegmentStatisticUpdater().getTotalDistance().toM(), 0.01);
assertEquals(55.287, copy.getSegmentStatisticUpdater().getTotalDistance().toM(), 0.01);
assertEquals(new Statistics(
Instant.ofEpochMilli(1000),
Instant.ofEpochMilli(4000),
Duration.ofSeconds(3),
Duration.ofSeconds(3),
Distance.of(55.28713929653168),
Speed.of(18.429046432177227),
new AltitudeExtremities(5, 5),
null,
null,
null
), initial.getTrackStatistics());
assertEquals(new Statistics(
Instant.ofEpochMilli(1000),
Instant.ofEpochMilli(5000),
Duration.ofSeconds(4),
Duration.ofSeconds(4),
Distance.of(55.28713929653168),
Speed.of(18.429046432177227),
new AltitudeExtremities(5, 5),
null,
null,
null
), subject.getTrackStatistics());
}
public TrackPoint createTrackPoint(double latitude, double longitude, Altitude altitude, Instant time) {
@@ -1,21 +1,26 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
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 java.time.ZoneOffset;
import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.AggregatedStatistic;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackSelection;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
@@ -23,15 +28,22 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
@RunWith(JUnit4.class)
public class AggregatedStatisticsTest {
private final Context context = ApplicationProvider.getApplicationContext();
private static Track createTrack(Context context, Distance totalDistance, Duration totalTime, String activityTypeLocalized) {
Track track = new Track(new Statistics(
private static void createTrack(Context context, Distance totalDistance, Duration totalTime, String activityTypeLocalized) {
Track track = new Track(
null,
UUID.randomUUID(), //TODO Use constant UUID
"",
"",
activityTypeLocalized,
ActivityType.findByLocalizedString(context, activityTypeLocalized),
ZoneOffset.UTC,
new Statistics(
Instant.ofEpochMilli(1000),
Instant.ofEpochMilli(1000).plus(totalTime),
totalTime,
@@ -43,210 +55,128 @@ public class AggregatedStatisticsTest {
null,
null
));
track.setActivityType(ActivityType.findByLocalizedString(context, activityTypeLocalized));
track.setActivityTypeLocalized(activityTypeLocalized);
return track;
new ContentProviderUtils(context).insertTrack(track);
}
@Before
public void setUp() {
new ContentProviderUtils(context).deleteAllTracks(context);
}
@Test
public void testAggregate() {
public void testAggregate_empty() {
// when
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
// then
assertEquals(List.of(), result);
}
@Test
public void testAggregate_oneBikingTrack() {
// given
// 10km in 40 minutes.
Distance totalDistance = Distance.of(10000);
Duration totalTime = Duration.ofMillis(2400000);
Distance totalDistance = Distance.ofKilometer(10);
Duration totalTime = Duration.ofMinutes(40);
String biking = context.getString(R.string.activity_type_biking);
Track track = createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, biking);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
// then
assertEquals(1, aggregatedStatistics.getCount());
assertNotNull(aggregatedStatistics.get(biking));
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
}
@Test
public void testAggregate_mountainBiking() {
// given
// 10km in 40 minutes.
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);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
// then
assertNotNull(aggregatedStatistics.get(mountainBiking));
}
@Test
public void testAggregate_trailRunning() {
// given
// 10km in 40 minutes.
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);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
// then
assertNotNull(aggregatedStatistics.get(trailRunning));
assertEquals(List.of(
new AggregatedStatistic("biking", 1, Duration.ofMinutes(40), Distance.ofKilometer(10), Speed.of(50))
), result);
}
@Test
public void testAggregate_twoBikingTracks() {
// given
// 10km in 40 minutes.
Distance totalDistance = Distance.of(10000);
Duration totalTime = Duration.ofMillis(2400000);
Distance totalDistance = Distance.ofKilometer(10);
Duration totalTime = Duration.ofMinutes(40);
String biking = context.getString(R.string.activity_type_biking);
List<Track> tracks = List.of(createTrack(context, totalDistance, totalTime, biking), createTrack(context, totalDistance, totalTime, biking));
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, biking);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
// then
assertEquals(1, aggregatedStatistics.getCount());
assertNotNull(aggregatedStatistics.get(biking));
assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
}
@Test
public void testAggregate_threeDifferentTracks() {
// given
// 10km in 40 minutes.
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);
Duration totalTime = Duration.ofMillis(2400000);
List<Track> tracks = List.of(
createTrack(context, totalDistance, totalTime, biking),
createTrack(context, totalDistance, totalTime, running),
createTrack(context, totalDistance, totalTime, walking)
);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
// then
assertEquals(3, aggregatedStatistics.getCount());
assertNotNull(aggregatedStatistics.get(biking));
assertNotNull(aggregatedStatistics.get(running));
assertNotNull(aggregatedStatistics.get(walking));
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
}
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
}
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
}
assertEquals(List.of(
new AggregatedStatistic("biking", 2, Duration.ofMinutes(80), Distance.ofKilometer(20), Speed.of(50))
), result);
}
@Test
public void testAggregate_severalTracksWithSeveralActivities() {
// given
// 10km in 40 minutes.
Distance totalDistance = Distance.of(10000);
Duration totalTime = Duration.ofMillis(2400000);
Distance totalDistance = Distance.ofKilometer(10);
Duration totalTime = Duration.ofMinutes(40);
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);
String driving = context.getString(R.string.activity_type_driving);
List<Track> tracks = List.of(
createTrack(context, totalDistance, totalTime, biking),
createTrack(context, totalDistance, totalTime, running),
createTrack(context, totalDistance, totalTime, walking),
createTrack(context, totalDistance, totalTime, biking),
createTrack(context, totalDistance, totalTime, running),
createTrack(context, totalDistance, totalTime, walking),
createTrack(context, totalDistance, totalTime, biking),
createTrack(context, totalDistance, totalTime, biking),
createTrack(context, totalDistance, totalTime, biking),
createTrack(context, totalDistance, totalTime, driving)
);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, running);
createTrack(context, totalDistance, totalTime, walking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, running);
createTrack(context, totalDistance, totalTime, walking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, driving);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
// then
// 4 sports.
assertEquals(4, aggregatedStatistics.getCount());
assertEquals(List.of(
new AggregatedStatistic("biking", 5, Duration.ofMinutes(200), Distance.ofKilometer(50), Speed.of(50)),
new AggregatedStatistic("walking", 2, Duration.ofMinutes(80), Distance.ofKilometer(20), Speed.of(50)),
new AggregatedStatistic("running", 2, Duration.ofMinutes(80), Distance.ofKilometer(20), Speed.of(50)),
new AggregatedStatistic("driving", 1, Duration.ofMinutes(40), Distance.ofKilometer(10), Speed.of(50))
), result);
}
@Test
public void testAggregate_severalTracksWithSeveralActivities_TrackSelection() {
// given
Distance totalDistance = Distance.ofKilometer(10);
Duration totalTime = Duration.ofMinutes(40);
// There is a map for every sport.
assertNotNull(aggregatedStatistics.get(biking));
assertNotNull(aggregatedStatistics.get(running));
assertNotNull(aggregatedStatistics.get(walking));
assertNotNull(aggregatedStatistics.get(driving));
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);
String driving = context.getString(R.string.activity_type_driving);
// Number of tracks by sport.
assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
assertEquals(2, aggregatedStatistics.get(running).getCountTracks()); // Running.
assertEquals(2, aggregatedStatistics.get(walking).getCountTracks()); // Walking.
assertEquals(1, aggregatedStatistics.get(driving).getCountTracks()); // Driving.
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, running);
createTrack(context, totalDistance, totalTime, walking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, running);
createTrack(context, totalDistance, totalTime, walking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, biking);
createTrack(context, totalDistance, totalTime, driving);
// Biking.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(5), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(5), statistics2.getMovingTime());
}
TrackSelection trackSelection = new TrackSelection();
trackSelection.addActivityType("biking");
trackSelection.addActivityType("driving");
// Running.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
}
// when
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(trackSelection);
// Walking.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
}
// Driving.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
}
// Check order
{
assertEquals(biking, aggregatedStatistics.getItem(0).getActivityTypeLocalized());
assertEquals(driving, aggregatedStatistics.getItem(3).getActivityTypeLocalized());
}
// then
assertEquals(List.of(
new AggregatedStatistic("biking", 5, Duration.ofMinutes(200), Distance.ofKilometer(50), Speed.of(50)),
new AggregatedStatistic("driving", 1, Duration.ofMinutes(40), Distance.ofKilometer(10), Speed.of(50))
), result);
}
}
@@ -34,13 +34,14 @@ import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoAggregatedFilterTest {
private final String CATEGORY = "category";
private final String ACTIVITY_TYPE_LOCALIZED = "activityTypeLocalized";
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@@ -51,8 +52,9 @@ public class EspressoAggregatedFilterTest {
@Before
public void setUp() {
Pair<Track, List<TrackPoint>> pair = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 20);
pair.first.setActivityTypeLocalized(CATEGORY);
TestDataUtil.insertTrackWithLocations(new ContentProviderUtils(ApplicationProvider.getApplicationContext()), pair.first, pair.second);
TrackBuilder trackBuilder = new TrackBuilder(pair.first);
trackBuilder.setActivityTypeLocalized(ACTIVITY_TYPE_LOCALIZED);
TestDataUtil.insertTrackWithLocations(new ContentProviderUtils(ApplicationProvider.getApplicationContext()), trackBuilder.getTrack(), pair.second);
}
@Test
@@ -67,9 +69,9 @@ public class EspressoAggregatedFilterTest {
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
onView(withId(R.id.aggregated_statistics_filter)).perform(click());
// check there's a checkbox with CATEGORY text
// check there's a checkbox with ACTIVITY_TYPE_LOCALIZED text
ViewInteraction checkBox = onView(
allOf(withId(R.id.filter_dialog_check_button), withText(CATEGORY),
allOf(withId(R.id.filter_dialog_check_button), withText(ACTIVITY_TYPE_LOCALIZED),
withParent(allOf(withId(R.id.filter_items),
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup.class)))),
isDisplayed()));
@@ -9,15 +9,17 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
@RunWith(JUnit4.class)
public class IntervalStatisticsUpdaterTest {
@@ -99,18 +101,25 @@ public class IntervalStatisticsUpdaterTest {
// given
float distanceInterval = 1000f;
int numberOfPoints = 10000;
Track dummyTrack = new Track();
dummyTrack.setId(new Track.Id(System.currentTimeMillis()));
dummyTrack.setName("Dummy Track Without Elevation Loss");
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
for (int i = 0; i < numberOfPoints; i++) {
trackPoints.add(TestDataUtil.createTrackPoint(i).setAltitudeGainLoss(null));
trackPoints.add(TestDataUtil.createTrackPoint(i)
.setAltitudeGainLoss(null));
}
trackStatisticsUpdater.addTrackPoints(trackPoints);
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoints);
Track dummyTrack = new Track(
new Track.Id(System.currentTimeMillis()),
null,
"Dummy Track Without Elevation Loss",
null,
"",
null,
ZoneOffset.UTC,
trackStatisticsUpdater.getTrackStatistics()
);
dummyTrack.setStatistics(trackStatisticsUpdater.getTrackStatistics());
Pair<Track, List<TrackPoint>> trackWithStats = new Pair<>(dummyTrack, trackPoints);
// when and then
@@ -128,7 +137,7 @@ public class IntervalStatisticsUpdaterTest {
intervalStatistics.addTrackPoints(trackPoints.iterator());
List<Statistics> intervalList = intervalStatistics.getIntervalList();
Distance totalDistance = Distance.of(0);
Distance totalDistance = Distance.ZERO;
float totalTime = 0L;
Float totalGain = null;
Float totalLoss = null;
@@ -150,9 +159,9 @@ public class IntervalStatisticsUpdaterTest {
}
// then
assertEquals(track.getStatistics().totalTime().toSeconds(), totalTime, 0.01);
assertEquals(track.getStatistics().totalDistance().toM(), totalDistance.toM(), 0.01);
assertEquals(intervalList.size(), (int) Math.ceil(track.getStatistics().totalDistance().toM() / distanceInterval));
assertEquals(track.statistics().totalDuration().toSeconds(), totalTime, 0.01);
assertEquals(track.statistics().totalDistance().toM(), totalDistance.toM(), 0.01);
assertEquals(intervalList.size(), (int) Math.ceil(track.statistics().totalDistance().toM() / distanceInterval));
if (totalGain != null && totalLoss != null) {
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
assertEquals(totalLoss, numberOfPoints * TestDataUtil.ALTITUDE_LOSS, 0.1);
@@ -169,14 +178,22 @@ public class IntervalStatisticsUpdaterTest {
}
private static Pair<Track, List<TrackPoint>> buildTrackWithTrackPoints(int numberOfPoints) {
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
for (int i = 0; i < numberOfPoints; i++) {
trackPoints.add(TestDataUtil.createTrackPoint(i));
}
trackStatisticsUpdater.addTrackPoints(trackPoints);
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoints);
return new Pair<>(new Track(trackStatisticsUpdater.getTrackStatistics()), trackPoints);
Track track = new Track(
null,
null,
"",
"",
"",
ActivityType.UNKNOWN,
ZoneOffset.UTC,
trackStatisticsUpdater.getTrackStatistics());
return new Pair<>(track, trackPoints);
}
}
@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gpxtpx="http://www.garmin.com/xmlschemes/TrackPointExtension/v2" xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1" xmlns:topografix="http://www.topografix.com/GPX/Private/TopoGrafix/0/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1" creator="OpenTracks (Debug)" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
<metadata>
<name><![CDATA[2021-01-07 22:51]]></name>
<desc><![CDATA[the description]]></desc>
</metadata>
<wpt lat="3" lon="14">
<ele>10</ele>
<time>2021-01-07T21:51:59.179Z</time>
<name><![CDATA[Marker 1]]></name>
<desc><![CDATA[Marker 1 desc]]></desc>
<type><![CDATA[Marker 1 category]]></type>
</wpt>
<wpt lat="3" lon="14.002">
<ele>10</ele>
<time>2021-01-07T21:52:01.010Z</time>
<name><![CDATA[Marker 2]]></name>
<desc><![CDATA[Marker 2 desc]]></desc>
<type><![CDATA[Marker 2 category]]></type>
</wpt>
<trk>
<name><![CDATA[2021-01-07 22:51]]></name>
<desc><![CDATA[the description]]></desc>
<type><![CDATA[the category]]></type>
<extensions>
<topografix:color>c0c0c0</topografix:color>
<opentracks:trackid>7002101e-4198-4613-8c24-544e01ca3981</opentracks:trackid>
</extensions>
<trkseg>
<trkpt lat="3" lon="14">
<ele>10</ele>
<time>2021-01-07T21:51:59.179Z</time>
</trkpt>
<trkpt lat="3" lon="14.001">
<ele>10</ele>
<time>2021-01-07T21:52:00.653Z</time>
</trkpt>
<trkpt lat="3" lon="14.002">
<ele>10</ele>
<time>2021-01-07T21:52:01.010Z</time>
</trkpt>
<trkpt lat="100" lon="0">
<time>2021-01-07T21:52:02.658Z</time>
</trkpt>
<trkpt lat="200" lon="0">
<time>2021-01-07T21:52:03.873Z</time>
</trkpt>
<trkpt lat="3" lon="14.003">
<ele>10</ele>
<time>2021-01-07T21:52:04.103Z</time>
</trkpt>
</trkseg>
</trk>
</gpx>
@@ -28,6 +28,7 @@ import java.time.ZoneOffset;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.databinding.TrackEditBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.ui.ZoneOffsetAdapter;
@@ -46,7 +47,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
private static final String ICON_VALUE_KEY = "icon_value_key";
private ContentProviderUtils contentProviderUtils;
private Track track;
private TrackBuilder trackBuilder;
private ActivityType activityType;
private TrackEditBinding viewBinding;
@@ -63,16 +64,17 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
}
contentProviderUtils = new ContentProviderUtils(this);
track = contentProviderUtils.getTrack(trackId);
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
Log.e(TAG, "No track for " + trackId.id());
finish();
return;
}
trackBuilder = new TrackBuilder(track);
viewBinding.trackEditName.setText(track.getName());
viewBinding.trackEditName.setText(track.name());
viewBinding.trackEditActivityTypeLocalized.setText(track.getActivityTypeLocalized());
viewBinding.trackEditActivityTypeLocalized.setText(track.activityTypeLocalized());
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
viewBinding.trackEditActivityTypeLocalized.setAdapter(adapter);
viewBinding.trackEditActivityTypeLocalized.setOnItemClickListener((parent, view, position, id) -> {
@@ -91,25 +93,25 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
activityType = (ActivityType) bundle.getSerializable(ICON_VALUE_KEY);
}
if (activityType == null) {
activityType = track.getActivityType();
activityType = track.activityType();
}
setActivityTypeIcon(activityType);
viewBinding.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), this, viewBinding.trackEditActivityTypeLocalized.getText().toString()));
viewBinding.trackEditDescription.setText(track.getDescription());
viewBinding.trackEditDescription.setText(track.description());
final ArrayAdapter<ZoneOffset> zoneOffsetAdapter = new ZoneOffsetAdapter(this, android.R.layout.simple_dropdown_item_1line);
viewBinding.trackEditTimeOffset.setText(track.getZoneOffset().toString());
viewBinding.trackEditTimeOffset.setText(track.zoneOffset().toString());
viewBinding.trackEditTimeOffset.setAdapter(zoneOffsetAdapter);
viewBinding.trackEditTimeOffset.setOnItemClickListener((parent, view, position, id) -> track.setZoneOffset(zoneOffsetAdapter.getItem(position)));
viewBinding.trackEditTimeOffset.setOnItemClickListener((parent, view, position, id) -> trackBuilder.setZoneOffset(zoneOffsetAdapter.getItem(position)));
viewBinding.trackEditSave.setOnClickListener(v -> {
track.setName(viewBinding.trackEditName.getText().toString());
track.setDescription(viewBinding.trackEditDescription.getText().toString());
trackBuilder.setName(viewBinding.trackEditName.getText().toString());
trackBuilder.setDescription(viewBinding.trackEditDescription.getText().toString());
track.setActivityTypeLocalizedAndUpdateActivityType(this, viewBinding.trackEditActivityTypeLocalized.getText().toString());
trackBuilder.setActivityTypeLocalizedAndUpdateActivityType(this, viewBinding.trackEditActivityTypeLocalized.getText().toString());
contentProviderUtils.updateTrack(track);
contentProviderUtils.updateTrack(trackBuilder.getTrack());
finish();
});
@@ -19,7 +19,6 @@ package de.dennisguse.opentracks;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.location.LocationManager;
import android.os.Bundle;
@@ -311,9 +310,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
viewBinding.trackListToolbar.setTitle(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.app_name)));
Cursor tracks = new ContentProviderUtils(this).searchTracks(searchQuery);
adapter.swapData(tracks);
adapter.swapData(new ContentProviderUtils(this).searchTracks(searchQuery));
}
@Override
@@ -33,6 +33,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.TrackRecordingBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
@@ -277,7 +278,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
Intent intent = IntentUtils
.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId)
.putExtra(MarkerEditActivity.EXTRA_LOCATION, trackPoint.getLocation());
.putExtra(MarkerEditActivity.EXTRA_LOCATION, trackPoint.getPosition().toLocation());
startActivity(intent);
return true;
}
@@ -324,11 +325,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override
public void onChooseActivityTypeDone(ActivityType activityType) {
Track track = contentProviderUtils.getTrack(trackId);
TrackBuilder track = new TrackBuilder(contentProviderUtils.getTrack(trackId));
String activityTypeLocalized = getString(activityType.getLocalizedStringId());
track.setActivityTypeLocalizedAndUpdateActivityType(this, activityTypeLocalized);
contentProviderUtils.updateTrack(track);
contentProviderUtils.updateTrack(track.getTrack());
}
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
@@ -14,6 +14,7 @@ import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.databinding.TrackStoppedBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.io.file.exporter.ExportUtils;
@@ -46,9 +47,9 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Track track = contentProviderUtils.getTrack(trackId);
viewBinding.trackEditName.setText(track.getName());
viewBinding.trackEditName.setText(track.name());
viewBinding.trackEditActivityType.setText(track.getActivityTypeLocalized());
viewBinding.trackEditActivityType.setText(track.activityTypeLocalized());
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
viewBinding.trackEditActivityType.setAdapter(adapter);
@@ -63,19 +64,19 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
}
});
setActivityTypeIcon(track.getActivityType());
setActivityTypeIcon(track.activityType());
viewBinding.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), this, viewBinding.trackEditActivityType.getText().toString()));
viewBinding.trackEditDescription.setText(track.getDescription());
viewBinding.trackEditDescription.setText(track.description());
viewBinding.time.setText(StringUtils.formatElapsedTime(track.getStatistics().movingTime()));
viewBinding.time.setText(StringUtils.formatElapsedTime(track.statistics().movingDuration()));
{
Pair<String, String> parts = SpeedFormatter.Builder()
.setUnit(PreferencesUtils.getUnitSystem())
.setReportSpeedOrPace(PreferencesUtils.isReportSpeed(track))
.build(this)
.getSpeedParts(track.getStatistics().getAverageMovingSpeed());
.getSpeedParts(track.statistics().getAverageMovingSpeed());
viewBinding.speed.setText(parts.first);
viewBinding.speedUnit.setText(parts.second);
}
@@ -84,7 +85,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
Pair<String, String> parts = DistanceFormatter.Builder()
.setUnit(PreferencesUtils.getUnitSystem())
.build(this)
.getDistanceParts(track.getStatistics().totalDistance());
.getDistanceParts(track.statistics().totalDistance());
viewBinding.distance.setText(parts.first);
viewBinding.distanceUnit.setText(parts.second);
}
@@ -104,11 +105,12 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
}
private void storeTrackMetaData(ContentProviderUtils contentProviderUtils, Track track) {
track.setName(viewBinding.trackEditName.getText().toString());
track.setDescription(viewBinding.trackEditDescription.getText().toString());
track.setActivityTypeLocalizedAndUpdateActivityType(this, viewBinding.trackEditActivityType.getText().toString());
TrackBuilder trackBuilder = new TrackBuilder(track);
trackBuilder.setName(viewBinding.trackEditName.getText().toString());
trackBuilder.setDescription(viewBinding.trackEditDescription.getText().toString());
trackBuilder.setActivityTypeLocalizedAndUpdateActivityType(this, viewBinding.trackEditActivityType.getText().toString());
contentProviderUtils.updateTrack(track);
contentProviderUtils.updateTrack(trackBuilder.getTrack());
}
@NonNull
@@ -32,15 +32,14 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.ChartBinding;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.data.statistics.SegmentStatisticUpdater;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
/**
* A fragment to display track chart to the user.
@@ -174,14 +173,14 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
}
@Override
public void onTrackUpdated(Track track) {
public void onTrackUpdated(@NonNull Track track) {
if (isResumed()) {
if (track == null) {
activityTypeLocalized = "";
return;
}
activityTypeLocalized = track.getActivityTypeLocalized();
activityTypeLocalized = track.activityTypeLocalized();
boolean reportSpeed = PreferencesUtils.isReportSpeed(activityTypeLocalized);
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
viewBinding.chartView.setReportSpeed(reportSpeed);
@@ -220,27 +219,6 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
}
}
@Override
public void clearMarkers() {
if (isResumed()) {
viewBinding.chartView.clearMarker();
}
}
@Override
public void onNewMarker(@NonNull Marker marker) {
if (isResumed()) {
viewBinding.chartView.addMarker(marker);
}
}
@Override
public void onNewMarkersDone() {
if (isResumed()) {
runOnUiThread(updateChart);
}
}
/**
* Checks the chart settings.
*/
@@ -26,7 +26,7 @@ public record ChartPoint(
return new ChartPoint(
chartByDistance
? trackStatistics.totalDistance().toKM_Miles(unitSystem)
: trackStatistics.totalTime().toMillis(),
: trackStatistics.totalDuration().toMillis(),
trackPoint.hasAltitude()
? Distance.of(trackPoint.getAltitude().toM()).toM_FT(unitSystem)
: null,
@@ -29,7 +29,7 @@ import androidx.core.content.ContextCompat;
import java.text.NumberFormat;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.ExtremityMonitor;
import de.dennisguse.opentracks.data.statistics.ExtremityMonitor;
/**
* This class encapsulates the meta data for one series of the chart values.
@@ -45,10 +45,8 @@ import java.util.LinkedList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.ExtremityMonitor;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.data.statistics.ExtremityMonitor;
import de.dennisguse.opentracks.ui.util.ThemeUtils;
import de.dennisguse.opentracks.util.StringUtils;
@@ -74,9 +72,6 @@ public class ChartView extends View {
private static final int SPACER = 4;
private static final int Y_AXIS_OFFSET = 16;
//TODO Determine from actual size of the used drawable
private static final float MARKER_X_ANCHOR = 13f / 48f;
static {
X_FRACTION_FORMAT.setMaximumFractionDigits(1);
X_FRACTION_FORMAT.setMinimumFractionDigits(1);
@@ -89,17 +84,12 @@ public class ChartView extends View {
private final ChartValueSeries heartRateSeries;
private final LinkedList<ChartPoint> chartPoints = new LinkedList<>();
private final List<Marker> markers = new LinkedList<>();
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
private final int backgroundColor;
private final Paint axisPaint;
private final Paint xAxisMarkerPaint;
private final Paint gridPaint;
private final Paint markerPaint;
private final Drawable pointer;
private final Drawable markerPin;
private final int markerWidth;
private final int markerHeight;
private final Scroller scroller;
private double maxX = 1.0;
private int zoomLevel = 1;
@@ -128,7 +118,7 @@ public class ChartView extends View {
private final GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
public boolean onDown(@NonNull MotionEvent e) {
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
@@ -136,7 +126,7 @@ public class ChartView extends View {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
if (Math.abs(distanceX) > 0) {
int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
if (availableToScroll > 0) {
@@ -147,7 +137,7 @@ public class ChartView extends View {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
fling((int) -velocityX);
return true;
}
@@ -157,7 +147,7 @@ public class ChartView extends View {
private final ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
public boolean onScale(@NonNull ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
if (scaleFactor >= 1.1f) {
zoomIn();
@@ -333,18 +323,9 @@ public class ChartView extends View {
gridPaint.setAntiAlias(false);
gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0));
markerPaint = new Paint();
markerPaint.setStyle(Style.STROKE);
markerPaint.setAntiAlias(false);
pointer = ContextCompat.getDrawable(context, R.drawable.ic_logo_color_24dp);
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
markerPin = MarkerUtils.getDefaultPhoto(context);
markerWidth = markerPin.getIntrinsicWidth();
markerHeight = markerPin.getIntrinsicHeight();
markerPin.setBounds(0, 0, markerWidth, markerHeight);
scroller = new Scroller(context);
setFocusable(true);
setClickable(true);
@@ -467,18 +448,6 @@ public class ChartView extends View {
scrollTo(0, 0);
}
public void addMarker(Marker marker) {
synchronized (markers) {
markers.add(marker);
}
}
public void clearMarker() {
synchronized (markers) {
markers.clear();
}
}
private boolean canZoomIn() {
return zoomLevel < MAX_ZOOM_LEVEL;
}
@@ -0,0 +1,26 @@
package de.dennisguse.opentracks.data;
import androidx.annotation.NonNull;
import java.time.Duration;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
public record AggregatedStatistic(
@NonNull
String activityTypeLocalized,
int countTracks,
@NonNull
Duration totalMovingTime,
@NonNull
Distance totalDistance,
@NonNull
Speed maxSpeed
) {
public Speed getAverageMovingSpeed() {
return Speed.of(totalDistance, totalMovingTime);
}
}
@@ -1,46 +0,0 @@
package de.dennisguse.opentracks.data;
import android.database.Cursor;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
/**
* A cache of track points indexes.
*/
class CachedTrackPointsIndexes {
final int idIndex;
final int typeIndex;
final int longitudeIndex;
final int latitudeIndex;
final int timeIndex;
final int altitudeIndex;
final int accuracyIndex;
final int accuracyVerticalIndex;
final int speedIndex;
final int bearingIndex;
final int sensorHeartRateIndex;
final int sensorCadenceIndex;
final int sensorDistanceIndex;
final int sensorPowerIndex;
final int altitudeGainIndex;
final int altitudeLossIndex;
CachedTrackPointsIndexes(Cursor cursor) {
idIndex = cursor.getColumnIndex(TrackPointsColumns._ID);
typeIndex = cursor.getColumnIndex(TrackPointsColumns.TYPE);
longitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.HORIZONTAL_ACCURACY);
accuracyVerticalIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.VERTICAL_ACCURACY);
speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
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);
altitudeGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_GAIN);
altitudeLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_LOSS);
}
}
@@ -25,7 +25,6 @@ import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.io.File;
@@ -34,7 +33,6 @@ import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -56,9 +54,8 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
import de.dennisguse.opentracks.util.FileUtils;
/**
@@ -66,13 +63,13 @@ import de.dennisguse.opentracks.util.FileUtils;
*
* @author Leif Hendrik Wilden
*/
//TODO Check if do {} while can be replaced by while (cursor.moveToNext)
public class ContentProviderUtils {
private static final String TAG = ContentProviderUtils.class.getSimpleName();
// The authority (the first part of the URI) for the app's content provider.
@VisibleForTesting
public static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
// The base URI for the app's content provider.
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
@@ -81,6 +78,7 @@ public class ContentProviderUtils {
private final ContentResolver contentResolver;
//TODO Check if this can be used in more than one place. If not remove.
public interface ContentProviderSelectionInterface {
SelectionData buildSelection();
}
@@ -94,11 +92,6 @@ public class ContentProviderUtils {
this.contentResolver = contentResolver;
}
/**
* Creates a {@link Track} from a cursor.
*
* @param cursor the cursor pointing to the track
*/
public static Track createTrack(Cursor cursor) {
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
int uuidIndex = cursor.getColumnIndexOrThrow(TracksColumns.UUID);
@@ -118,35 +111,15 @@ public class ContentProviderUtils {
int altitudeGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ALTITUDE_GAIN);
int altitudeLossIndex = cursor.getColumnIndexOrThrow(TracksColumns.ALTITUDE_LOSS);
Track track = new Track(ZoneOffset.ofTotalSeconds(cursor.getInt(startTimeOffsetIndex)));
if (!cursor.isNull(idIndex)) {
track.setId(new Track.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(uuidIndex)) {
track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)));
}
if (!cursor.isNull(nameIndex)) {
track.setName(cursor.getString(nameIndex));
}
if (!cursor.isNull(descriptionIndex)) {
track.setDescription(cursor.getString(descriptionIndex));
}
if (!cursor.isNull(activityTypeIndex)) {
track.setActivityType(ActivityType.findBy(cursor.getString(activityTypeIndex)));
}
if (!cursor.isNull(activityTypeLocalizedIndex)) {
track.setActivityTypeLocalized(cursor.getString(activityTypeLocalizedIndex));
}
Statistics trackStatistics = new Statistics(
cursor.isNull(startTimeIndex) ? null : Instant.ofEpochMilli(cursor.getLong(startTimeIndex)),
cursor.isNull(stopTimeIndex) ? null : Instant.ofEpochMilli(cursor.getLong(stopTimeIndex)),
cursor.isNull(totalTimeIndex) ? null : Duration.ofMillis(cursor.getLong(totalTimeIndex)),
cursor.isNull(movingTimeIndex) ? null : Duration.ofMillis(cursor.getLong(movingTimeIndex)),
Instant.ofEpochMilli(cursor.getLong(startTimeIndex)),
Instant.ofEpochMilli(cursor.getLong(stopTimeIndex)),
Duration.ofMillis(cursor.getLong(totalTimeIndex)),
Duration.ofMillis(cursor.getLong(movingTimeIndex)),
cursor.isNull(totalDistanceIndex) ? null : Distance.of(cursor.getFloat(totalDistanceIndex)),
Distance.of(cursor.getFloat(totalDistanceIndex)),
cursor.isNull(maxSpeedIndex) ? null : Speed.of(cursor.getFloat(maxSpeedIndex)),
Speed.of(cursor.getFloat(maxSpeedIndex)),
cursor.isNull(minAltitudeIndex) || cursor.isNull(maxAltitudeIndex) ? null : new AltitudeExtremities(cursor.getFloat(minAltitudeIndex), cursor.getFloat(maxAltitudeIndex)),
cursor.isNull(altitudeGainIndex) || cursor.isNull(altitudeLossIndex) ? null : new AltitudeGainLoss(cursor.getFloat(altitudeGainIndex), cursor.getFloat(altitudeLossIndex)),
@@ -154,17 +127,21 @@ public class ContentProviderUtils {
null
);
track.setStatistics(trackStatistics);
return track;
return new Track(
new Track.Id(cursor.getLong(idIndex)),
UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)),
!cursor.isNull(nameIndex) ? cursor.getString(nameIndex) : "",
!cursor.isNull(descriptionIndex) ? cursor.getString(descriptionIndex) : "",
!cursor.isNull(activityTypeLocalizedIndex) ? cursor.getString(activityTypeLocalizedIndex) : "",
!cursor.isNull(activityTypeIndex) ? ActivityType.findBy(cursor.getString(activityTypeIndex)) : ActivityType.findBy(null), //TODO Can this happen?
ZoneOffset.ofTotalSeconds(cursor.getInt(startTimeOffsetIndex)),
trackStatistics
);
}
@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
// Delete tracks last since it triggers a database vacuum call
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
File dir = FileUtils.getPhotoDir(context);
@@ -202,28 +179,12 @@ public class ContentProviderUtils {
return tracks;
}
public List<Track> getTracks(ContentProviderSelectionInterface selection) {
SelectionData selectionData = selection.buildSelection();
ArrayList<Track> tracks = new ArrayList<>();
try (Cursor cursor = getTrackCursor(selectionData.selection(), selectionData.selectionArgs(), TracksColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
tracks.ensureCapacity(cursor.getCount());
do {
tracks.add(createTrack(cursor));
} while (cursor.moveToNext());
}
}
return tracks;
}
public Cursor searchTracks(String searchQuery) {
public TrackListIterator searchTracks(String searchQuery) {
// Needed, because MARKER_COUNT is a virtual column and has to be explicitly requested.
// Used only be TrackListAdapter
final String[] PROJECTION = new String[]{
TracksColumns._ID,
TracksColumns.NAME,
TracksColumns.DESCRIPTION, //TODO Needed?
TracksColumns.DESCRIPTION,
TracksColumns.ACTIVITY_TYPE,
TracksColumns.ACTIVITY_TYPE_LOCALIZED,
TracksColumns.STARTTIME,
@@ -244,7 +205,7 @@ public class ContentProviderUtils {
selectionArgs = new String[]{"%" + searchQuery + "%", "%" + searchQuery + "%", "%" + searchQuery + "%"};
}
return contentResolver.query(TracksColumns.CONTENT_URI, PROJECTION, selection, selectionArgs, sortOrder);
return new TrackListIterator(contentResolver.query(TracksColumns.CONTENT_URI, PROJECTION, selection, selectionArgs, sortOrder));
}
public Track getTrack(@NonNull Track.Id trackId) {
@@ -274,7 +235,7 @@ public class ContentProviderUtils {
* @param selectionArgs the selection arguments. Can be null
* @param sortOrder the sort order. Can be null
*/
public Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder) {
Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder) {
return contentResolver.query(TracksColumns.CONTENT_URI, null, selection, selectionArgs, sortOrder);
}
@@ -286,6 +247,7 @@ public class ContentProviderUtils {
* @return the content provider URI of the inserted track.
*/
public Track.Id insertTrack(Track track) {
//TODO Unset trackId
Uri uri = contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track));
return new Track.Id(ContentUris.parseId(uri));
}
@@ -297,44 +259,23 @@ public class ContentProviderUtils {
* @param track the track
*/
public void updateTrack(Track track) {
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId().id())});
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.id().id())});
}
private ContentValues createContentValues(Track track) {
ContentValues values = new ContentValues();
Statistics trackStatistics = track.getStatistics();
if (track.getId() != null) {
values.put(TracksColumns._ID, track.getId().id());
if (track.id() != null) {
values.put(TracksColumns._ID, track.id().id());
}
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
values.put(TracksColumns.NAME, track.getName());
values.put(TracksColumns.DESCRIPTION, track.getDescription());
values.put(TracksColumns.ACTIVITY_TYPE, track.getActivityType() != null ? track.getActivityType().getId() : null);
values.put(TracksColumns.ACTIVITY_TYPE_LOCALIZED, track.getActivityTypeLocalized());
values.put(TracksColumns.STARTTIME_OFFSET, track.getZoneOffset().getTotalSeconds());
if (trackStatistics.startTime() != null) {
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
}
if (trackStatistics.stopTime() != null) {
values.put(TracksColumns.STOPTIME, trackStatistics.stopTime().toEpochMilli());
}
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.totalDistance().toM());
values.put(TracksColumns.TOTALTIME, trackStatistics.totalTime().toMillis());
values.put(TracksColumns.MOVINGTIME, trackStatistics.movingTime().toMillis());
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
values.put(TracksColumns.MAXSPEED, trackStatistics.maxSpeed().toMPS());
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.uuid()));
values.put(TracksColumns.NAME, track.name());
values.put(TracksColumns.DESCRIPTION, track.description());
values.put(TracksColumns.ACTIVITY_TYPE, track.activityType() != null ? track.activityType().getId() : null);
values.put(TracksColumns.ACTIVITY_TYPE_LOCALIZED, track.activityTypeLocalized());
values.put(TracksColumns.STARTTIME_OFFSET, track.zoneOffset().getTotalSeconds());
if (trackStatistics.altitudeExtremities() != null) {
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
}
if (trackStatistics.altitudeGainLoss() != null) {
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.altitudeGainLoss().gain_m());
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.altitudeGainLoss().loss_m());
}
values.putAll(createContentValues(track.statistics()));
return values;
}
@@ -345,20 +286,16 @@ public class ContentProviderUtils {
private ContentValues createContentValues(Statistics trackStatistics) {
ContentValues values = new ContentValues();
if (trackStatistics.startTime() != null) {
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
}
if (trackStatistics.stopTime() != null) {
values.put(TracksColumns.STOPTIME, trackStatistics.stopTime().toEpochMilli());
}
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
values.put(TracksColumns.STOPTIME, trackStatistics.stopTime().toEpochMilli());
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.totalDistance().toM());
values.put(TracksColumns.TOTALTIME, trackStatistics.totalTime().toMillis());
values.put(TracksColumns.MOVINGTIME, trackStatistics.movingTime().toMillis());
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
values.put(TracksColumns.TOTALTIME, trackStatistics.totalDuration().toMillis());
values.put(TracksColumns.MOVINGTIME, trackStatistics.movingDuration().toMillis());
values.put(TracksColumns.MAXSPEED, trackStatistics.maxSpeed().toMPS());
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
if (trackStatistics.altitudeExtremities() != null) {
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.altitudeExtremities().min_m());
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.altitudeExtremities().max_m());
}
if (trackStatistics.altitudeGainLoss() != null) {
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.altitudeGainLoss().gain_m());
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.altitudeGainLoss().loss_m());
@@ -366,18 +303,18 @@ public class ContentProviderUtils {
return values;
}
//TODO Align with fillTrackPoint
public Marker createMarker(Cursor cursor) {
int idIndex = cursor.getColumnIndexOrThrow(MarkerColumns._ID);
int nameIndex = cursor.getColumnIndexOrThrow(MarkerColumns.NAME);
int descriptionIndex = cursor.getColumnIndexOrThrow(MarkerColumns.DESCRIPTION);
int categoryIndex = cursor.getColumnIndexOrThrow(MarkerColumns.CATEGORY);
int iconIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ICON);
int typeLocalizedIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TYPE_LOCALIZED);
int trackIdIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TRACKID);
int longitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LONGITUDE);
int latitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LATITUDE);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
int altitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ALTITUDE);
int accuracyIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ACCURACY);
int accuracyIndex = cursor.getColumnIndexOrThrow(MarkerColumns.HORIZONTAL_ACCURACY);
int bearingIndex = cursor.getColumnIndexOrThrow(MarkerColumns.BEARING);
int photoUrlIndex = cursor.getColumnIndexOrThrow(MarkerColumns.PHOTOURL);
@@ -411,37 +348,26 @@ public class ContentProviderUtils {
bearing,
null);
Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
Marker marker = new Marker(trackId, position);
if (!cursor.isNull(idIndex)) {
marker.setId(new Marker.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(nameIndex)) {
marker.setName(cursor.getString(nameIndex));
}
if (!cursor.isNull(descriptionIndex)) {
marker.setDescription(cursor.getString(descriptionIndex));
}
if (!cursor.isNull(categoryIndex)) {
marker.setCategory(cursor.getString(categoryIndex));
}
if (!cursor.isNull(iconIndex)) {
marker.setIcon(cursor.getString(iconIndex));
}
Uri photoUrl = null;
if (!cursor.isNull(photoUrlIndex)) {
String photoUrl = cursor.getString(photoUrlIndex);
if (photoUrl.isEmpty()) {
if (cursor.getString(photoUrlIndex).isEmpty()) {
// Before v4.18.0: a marker without a picture as URL ""
// TODO Data should be migrated.
marker.setPhotoUrl(null);
photoUrl = null;
} else {
marker.setPhotoUrl(Uri.parse(cursor.getString(photoUrlIndex)));
photoUrl = Uri.parse(cursor.getString(photoUrlIndex));
}
}
return marker;
return new Marker(
new Marker.Id(cursor.getLong(idIndex)),
new Track.Id(cursor.getLong(trackIdIndex)),
!cursor.isNull(nameIndex) ? cursor.getString(nameIndex) : "",
!cursor.isNull(descriptionIndex) ? cursor.getString(descriptionIndex) : "",
!cursor.isNull(typeLocalizedIndex) ? cursor.getString(typeLocalizedIndex) : "",
position,
photoUrl
);
}
public void deleteMarker(Context context, Marker.Id markerId) {
@@ -474,51 +400,55 @@ public class ContentProviderUtils {
return null;
}
public MarkerIterator getMarkerIterator(@NonNull Track.Id trackId) {
return new MarkerIterator(this, trackId, -1);
}
public MarkerIterator getMarkerIterator(@NonNull Track.Id trackId, int maxCount) {
return new MarkerIterator(this, trackId, maxCount);
}
/**
* The caller owns the returned cursor and is responsible for closing it.
*
* @param trackId the track id
* @param minMarkerId the minimum marker id. null to ignore
* @param maxCount the maximum number of markers to return. -1 for no limit
* @param trackId the track id
* @param maxCount the maximum number of markers to return. -1 for no limit
*/
public Cursor getMarkerCursor(@NonNull Track.Id trackId, @Nullable Marker.Id minMarkerId, int maxCount) {
Cursor getMarkerCursor(@NonNull Track.Id trackId, int maxCount) {
String selection;
String[] selectionArgs;
if (minMarkerId != null) {
selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId.id()), Long.toString(minMarkerId.id())};
} else {
selection = MarkerColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId.id())};
}
selection = MarkerColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId.id())};
return getMarkerCursor(null, selection, selectionArgs, MarkerColumns._ID, maxCount);
}
@Deprecated //TODO Move to test package
@VisibleForTesting
public List<Marker> getMarkers(Track.Id trackId) {
ArrayList<Marker> markers = new ArrayList<>();
try (Cursor cursor = getMarkerCursor(trackId, null, -1)) {
if (cursor.moveToFirst()) {
do {
markers.add(createMarker(cursor));
} while (cursor.moveToNext());
}
/**
* @param updateMarker the marker with updated data.
* @return true if successful.
*/
public boolean updateMarker(Context context, Marker updateMarker) {
Marker savedMarker = getMarker(updateMarker.id());
if (updateMarker.photoUrl() == null) {
deleteMarkerPhoto(context, savedMarker);
}
return markers;
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker, updateMarker.trackId()), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.id().id())});
return rows == 1;
}
// TODO Merge with updateMarker
public Marker.Id insertMarker(@NonNull Marker marker) {
marker.setId(null);
Uri uri = contentResolver.insert(MarkerColumns.CONTENT_URI, createContentValues(marker));
assert marker.id() == null;
assert marker.trackId() != null;
Uri uri = contentResolver.insert(MarkerColumns.CONTENT_URI, createContentValues(marker, marker.trackId()));
return new Marker.Id(ContentUris.parseId(uri));
}
private void deleteMarkerPhoto(Context context, Marker marker) {
if (marker != null && marker.hasPhoto()) {
Uri uri = marker.getPhotoUrl();
File file = MarkerUtils.buildInternalPhotoFile(context, marker.getTrackId(), uri);
if (marker != null && marker.photoUrl() != null) {
Uri uri = marker.photoUrl();
File file = MarkerUtils.buildInternalPhotoFile(context, marker.trackId(), uri);
if (file.exists()) {
File parent = file.getParentFile();
file.delete();
@@ -529,46 +459,32 @@ public class ContentProviderUtils {
}
}
/**
* @param updateMarker the marker with updated data.
* @return true if successful.
*/
public boolean updateMarker(Context context, Marker updateMarker) {
Marker savedMarker = getMarker(updateMarker.getId());
if (!updateMarker.hasPhoto()) {
deleteMarkerPhoto(context, savedMarker);
}
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.getId().id())});
return rows == 1;
}
ContentValues createContentValues(@NonNull Marker marker) {
ContentValues createContentValues(@NonNull Marker marker, @NonNull Track.Id trackId) {
ContentValues values = new ContentValues();
if (marker.getId() != null) {
values.put(MarkerColumns._ID, marker.getId().id());
if (marker.id() != null) {
values.put(MarkerColumns._ID, marker.id().id());
}
values.put(MarkerColumns.NAME, marker.getName());
values.put(MarkerColumns.DESCRIPTION, marker.getDescription());
values.put(MarkerColumns.CATEGORY, marker.getCategory());
values.put(MarkerColumns.ICON, marker.getIcon());
values.put(MarkerColumns.TRACKID, marker.getTrackId().id());
values.put(MarkerColumns.LONGITUDE, (int) (marker.getPosition().longitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (marker.getPosition().latitude() * 1E6));
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
if (marker.hasAltitude()) {
values.put(MarkerColumns.ALTITUDE, marker.getAltitude().toM());
values.put(MarkerColumns.TRACKID, trackId.id());
values.put(MarkerColumns.NAME, marker.name());
values.put(MarkerColumns.DESCRIPTION, marker.description());
values.put(MarkerColumns.TYPE_LOCALIZED, marker.typeLocalized());
values.put(MarkerColumns.LONGITUDE, (int) (marker.position().longitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (marker.position().latitude() * 1E6));
values.put(MarkerColumns.TIME, marker.position().time().toEpochMilli());
if (marker.position().hasAltitude()) {
values.put(MarkerColumns.ALTITUDE, marker.position().altitude().toM());
}
if (marker.hasAccuracy()) {
values.put(MarkerColumns.ACCURACY, marker.getAccuracy().toM());
if (marker.position().hasHorizontalAccuracy()) {
values.put(MarkerColumns.HORIZONTAL_ACCURACY, marker.position().horizontalAccuracy().toM());
}
if (marker.hasBearing()) {
values.put(MarkerColumns.BEARING, marker.getBearing());
if (marker.position().hasBearing()) {
values.put(MarkerColumns.BEARING, marker.position().bearing());
}
if (marker.hasPhoto()) {
values.put(MarkerColumns.PHOTOURL, marker.getPhotoUrl().toString());
}
values.put(MarkerColumns.PHOTOURL, marker.photoUrl() != null ? marker.photoUrl().toString() : null);
return values;
}
@@ -589,7 +505,7 @@ public class ContentProviderUtils {
return contentResolver.query(MarkerColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
}
public List<Marker> searchMarkers(Track.Id trackId, String query) {
public MarkerIterator searchMarkers(Track.Id trackId, String query) {
String selection = null;
String[] selectionArgs = null;
String sortOrder = null;
@@ -602,65 +518,67 @@ public class ContentProviderUtils {
} else {
selection = MarkerColumns.NAME + " LIKE ? OR " +
MarkerColumns.DESCRIPTION + " LIKE ? OR " +
MarkerColumns.CATEGORY + " LIKE ?";
MarkerColumns.TYPE_LOCALIZED + " LIKE ?";
selectionArgs = new String[]{"%" + query + "%", "%" + query + "%", "%" + query + "%"};
sortOrder = MarkerColumns.DEFAULT_SORT_ORDER + " DESC";
}
ArrayList<Marker> markers = new ArrayList<>();
try (Cursor cursor = getMarkerCursor(null, selection, selectionArgs, sortOrder, -1)) {
if (cursor.moveToFirst()) {
do {
markers.add(createMarker(cursor));
} while (cursor.moveToNext());
}
}
return markers;
return new MarkerIterator(this, getMarkerCursor(null, selection, selectionArgs, sortOrder, -1));
}
/**
* Fills a {@link TrackPoint} from a cursor.
*
* @param cursor the cursor pointing to a trackPoint.
* @param indexes the cached trackPoints indexes
*/
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
static TrackPoint fillTrackPoint(Cursor cursor) {
int idIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
int typeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TYPE);
int longitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
int latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
int timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
int altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
int accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.HORIZONTAL_ACCURACY);
int accuracyVerticalIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.VERTICAL_ACCURACY);
int speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
int bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
int sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
int sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
int sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE);
int sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
int altitudeGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_GAIN);
int altitudeLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_LOSS);
TrackPoint trackPoint = new TrackPoint(
new TrackPoint.Id(cursor.getInt(indexes.idIndex)),
TrackPoint.Type.getById(cursor.getInt(indexes.typeIndex)),
new TrackPoint.Id(cursor.getInt(idIndex)),
TrackPoint.Type.getById(cursor.getInt(typeIndex)),
new Position(
Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex)),
!cursor.isNull(indexes.latitudeIndex) ? ((double) cursor.getInt(indexes.latitudeIndex)) / 1E6 : null,
!cursor.isNull(indexes.longitudeIndex) ? ((double) cursor.getInt(indexes.longitudeIndex)) / 1E6 : null,
!cursor.isNull(indexes.accuracyIndex) ? Distance.of(cursor.getFloat(indexes.accuracyIndex)) : null,
!cursor.isNull(indexes.altitudeIndex) ? Altitude.WGS84.of(cursor.getFloat(indexes.altitudeIndex)) : null,
!cursor.isNull(indexes.accuracyVerticalIndex) ? Distance.of(cursor.getFloat(indexes.accuracyVerticalIndex)) : null,
!cursor.isNull(indexes.bearingIndex) ? cursor.getFloat(indexes.bearingIndex) : null,
!cursor.isNull(indexes.speedIndex) ? Speed.of(cursor.getFloat(indexes.speedIndex)) : null
Instant.ofEpochMilli(cursor.getLong(timeIndex)),
!cursor.isNull(latitudeIndex) ? ((double) cursor.getInt(latitudeIndex)) / 1E6 : null,
!cursor.isNull(longitudeIndex) ? ((double) cursor.getInt(longitudeIndex)) / 1E6 : null,
!cursor.isNull(accuracyIndex) ? Distance.of(cursor.getFloat(accuracyIndex)) : null,
!cursor.isNull(altitudeIndex) ? Altitude.WGS84.of(cursor.getFloat(altitudeIndex)) : null,
!cursor.isNull(accuracyVerticalIndex) ? Distance.of(cursor.getFloat(accuracyVerticalIndex)) : null,
!cursor.isNull(bearingIndex) ? cursor.getFloat(bearingIndex) : null,
!cursor.isNull(speedIndex) ? Speed.of(cursor.getFloat(speedIndex)) : null
));
if (!cursor.isNull(indexes.sensorHeartRateIndex)) {
trackPoint.setHeartRate(cursor.getFloat(indexes.sensorHeartRateIndex));
if (!cursor.isNull(sensorHeartRateIndex)) {
trackPoint.setHeartRate(cursor.getFloat(sensorHeartRateIndex));
}
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
trackPoint.setCadence(cursor.getFloat(indexes.sensorCadenceIndex));
if (!cursor.isNull(sensorCadenceIndex)) {
trackPoint.setCadence(cursor.getFloat(sensorCadenceIndex));
}
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
if (!cursor.isNull(sensorDistanceIndex)) {
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(sensorDistanceIndex)));
}
if (!cursor.isNull(indexes.sensorPowerIndex)) {
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
if (!cursor.isNull(sensorPowerIndex)) {
trackPoint.setPower(cursor.getFloat(sensorPowerIndex));
}
if (!cursor.isNull(indexes.altitudeGainIndex) && !cursor.isNull(indexes.altitudeLossIndex)) {
trackPoint.setAltitudeGainLoss(cursor.getFloat(indexes.altitudeGainIndex), cursor.getFloat(indexes.altitudeLossIndex));
if (!cursor.isNull(altitudeGainIndex) && !cursor.isNull(altitudeLossIndex)) {
trackPoint.setAltitudeGainLoss(cursor.getFloat(altitudeGainIndex), cursor.getFloat(altitudeLossIndex));
}
return trackPoint;
}
//TODO Only used for file import; might be better to replace it.
//TODO Rename to bulkInsert
public int bulkInsertTrackPoint(List<TrackPoint> trackPoints, Track.Id trackId) {
ContentValues[] values = new ContentValues[trackPoints.size()];
for (int i = 0; i < trackPoints.size(); i++) {
@@ -673,53 +591,11 @@ public class ContentProviderUtils {
public int bulkInsertMarkers(List<Marker> markers, Track.Id trackId) {
ContentValues[] values = new ContentValues[markers.size()];
for (int i = 0; i < markers.size(); i++) {
values[i] = createContentValues(markers.get(i));
values[i] = createContentValues(markers.get(i), trackId);
}
return contentResolver.bulkInsert(MarkerColumns.CONTENT_URI, values);
}
/**
* Gets the last location id for a track.
* Returns -1L if it doesn't exist.
*
* @param trackId the track id
*/
@Deprecated
public TrackPoint.Id getLastTrackPointId(@NonNull Track.Id trackId) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId.id())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
}
}
return null;
}
/**
* Gets the trackPoint id for a location.
*/
@Deprecated
public TrackPoint.Id getTrackPointId(Track.Id trackId, Position position) {
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.id()), Long.toString(position.time().toEpochMilli())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
}
}
return null;
}
/**
* Creates a {@link TrackPoint} object from a cursor.
*
* @param cursor the cursor pointing to the location
*/
public TrackPoint createTrackPoint(Cursor cursor) {
return fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor));
}
/**
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
*
@@ -727,7 +603,7 @@ public class ContentProviderUtils {
* @param startTrackPointId the starting trackPoint id. `null` to ignore
*/
@NonNull
public Cursor getTrackPointCursor(@NonNull Track.Id trackId, TrackPoint.Id startTrackPointId) {
Cursor getTrackPointCursor(@NonNull Track.Id trackId, TrackPoint.Id startTrackPointId) {
String selection;
String[] selectionArgs;
if (startTrackPointId != null) {
@@ -741,19 +617,6 @@ public class ContentProviderUtils {
return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER);
}
/**
* Gets the last valid location for a track.
* Returns null if it doesn't exist.
*
* @param trackId the track id
*/
@Deprecated
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC.type_db + "," + TrackPoint.Type.TRACKPOINT.type_db + "))";
String[] selectionArgs = new String[]{Long.toString(trackId.id())};
return findTrackPointBy(selection, selectionArgs);
}
/**
* Inserts a trackPoint.
*
@@ -816,28 +679,15 @@ public class ContentProviderUtils {
/**
* Creates a new read-only iterator over a given track's points.
* It provides a lightweight way of iterating over long tracks without failing due to the underlying cursor limitations.
* Since it's a read-only iterator, {@link Iterator#remove()} always throws {@link UnsupportedOperationException}.
* Each call to {@link TrackPointIterator#next()} may advance to the next DB record.
* When done with iteration, {@link TrackPointIterator#close()} must be called.
*
* @param trackId the track id
* @param startTrackPointId the starting trackPoint id. `null` to ignore
*/
public AutoCloseableIterator<TrackPoint> getTrackPointLocationIterator(final Track.Id trackId, final TrackPoint.Id startTrackPointId) {
public TrackPointIterator getTrackPointIterator(Track.Id trackId, TrackPoint.Id startTrackPointId) {
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()) {
return createTrackPoint(cursor);
}
}
return null;
}
/**
* Gets a trackPoint cursor.
*
@@ -903,4 +753,33 @@ public class ContentProviderUtils {
}
return sensorStatistics;
}
@NonNull
public List<AggregatedStatistic> getAggregatedStatisticsForTracks(ContentProviderSelectionInterface selection) {
SelectionData data = selection.buildSelection();
try (Cursor cursor = contentResolver.query(TracksColumns.CONTENT_URI_AGGREGATED_STATISTICS, null, data.selection(), data.getSelectionArgs(), null)) {
ArrayList<AggregatedStatistic> aggregatedStatistics = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
aggregatedStatistics.add(new AggregatedStatistic(
cursor.getString(cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE_LOCALIZED)),
cursor.getInt(cursor.getColumnIndexOrThrow(TracksColumns.TRACK_COUNT)),
Duration.ofMillis(cursor.getLong(cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME))),
Distance.of(cursor.getFloat(cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE))),
Speed.of(cursor.getFloat(cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED)))
));
}
return aggregatedStatistics;
}
}
public record SelectionData(
String selection,
List<String> selectionArgs
) {
public String[] getSelectionArgs() {
if (selectionArgs == null) return null;
return selectionArgs.toArray(String[]::new);
}
}
}
@@ -19,7 +19,6 @@ package de.dennisguse.opentracks.data;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
@@ -27,11 +26,11 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Arrays;
@@ -66,7 +65,7 @@ public class CustomContentProvider extends ContentProvider {
* Finally, it ignores manual pause (SEGMENT_START_MANUAL).
*/
private final String SENSOR_STATS_QUERY =
"WITH time_select as " +
"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) " +
@@ -95,6 +94,7 @@ public class CustomContentProvider extends ContentProvider {
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
public CustomContentProvider() {
//TODO Add tests for used URIs (incl. Dashboard API)
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
@@ -102,6 +102,7 @@ public class CustomContentProvider extends ContentProvider {
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_AGGREGATED_STATISTICS.getPath(), UrlType.TRACKS_AGGREGATED_STATISTICS.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());
@@ -111,18 +112,7 @@ public class CustomContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return onCreate(getContext());
}
/**
* Helper method to make onCreate is testable.
*
* @param context context to creates database
* @return true means run successfully
*/
@VisibleForTesting
boolean onCreate(Context context) {
CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(context);
CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(getContext());
try {
db = databaseHelper.getWritableDatabase();
// Necessary to enable cascade deletion from Track to TrackPoints and Markers
@@ -182,11 +172,10 @@ public class CustomContentProvider extends ContentProvider {
public String getType(@NonNull Uri url) {
return switch (getUrlType(url)) {
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
case TRACKPOINTS_BY_ID, TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
case TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
case TRACKS -> TracksColumns.CONTENT_TYPE;
case TRACKS_BY_ID -> TracksColumns.CONTENT_ITEMTYPE;
case MARKERS -> MarkerColumns.CONTENT_TYPE;
case MARKERS_BY_ID, MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
case MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
default -> throw new IllegalArgumentException("Unknown URL " + url);
};
}
@@ -235,6 +224,8 @@ public class CustomContentProvider extends ContentProvider {
public Cursor query(@NonNull Uri url, String[] projection, String selection, String[] selectionArgs, String sort) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
String sortOrder = null;
String groupBy = null;
//TODO queryBuilder.appendWhere() should use selectionArgs?
switch (getUrlType(url)) {
case TRACKPOINTS -> {
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
@@ -261,9 +252,23 @@ public class CustomContentProvider extends ContentProvider {
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
}
case TRACKS_SENSOR_STATS -> {
//TODO Replace with SQLQueryBuilder?
long trackId = ContentUris.parseId(url);
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
}
case TRACKS_AGGREGATED_STATISTICS -> {
queryBuilder.setTables(TracksColumns.TABLE_NAME);
projection = new String[]{
"COUNT(*) AS " + TracksColumns.TRACK_COUNT,
TracksColumns.ACTIVITY_TYPE_LOCALIZED,
"SUM(" + TracksColumns.MOVINGTIME + ") AS " + TracksColumns.MOVINGTIME,
"SUM(" + TracksColumns.TOTALDISTANCE + ") AS " + TracksColumns.TOTALDISTANCE,
"MAX(" + TracksColumns.MAXSPEED + ") AS " + TracksColumns.MAXSPEED
};
sortOrder = TracksColumns.TRACK_COUNT + " DESC";
groupBy = TracksColumns.ACTIVITY_TYPE_LOCALIZED;
}
case MARKERS -> {
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
@@ -273,65 +278,41 @@ public class CustomContentProvider extends ContentProvider {
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
}
case MARKERS_BY_TRACKID -> {
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
queryBuilder.setTables(MarkerColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT 'unused' AS ICON)"); //TODO Workaround: Column icon was removed in v4.24.0, but is still required for OSM Dashboard v6.4.1.
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
}
default -> throw new IllegalArgumentException("Unknown url " + url);
}
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), url);
return cursor;
}
@Override
public int update(@NonNull Uri url, ContentValues values, String where, String[] selectionArgs) {
// TODO Use SQLiteQueryBuilder
String table;
String whereClause;
switch (getUrlType(url)) {
case TRACKPOINTS -> {
table = TrackPointsColumns.TABLE_NAME;
whereClause = where;
}
case TRACKPOINTS_BY_ID -> {
table = TrackPointsColumns.TABLE_NAME;
whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url);
if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")";
}
}
case TRACKS -> {
table = TracksColumns.TABLE_NAME;
whereClause = where;
}
case TRACKS_BY_ID -> {
table = TracksColumns.TABLE_NAME;
whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url);
if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")";
}
}
case MARKERS -> {
table = MarkerColumns.TABLE_NAME;
whereClause = where;
}
case MARKERS_BY_ID -> {
table = MarkerColumns.TABLE_NAME;
whereClause = MarkerColumns._ID + "=" + ContentUris.parseId(url);
if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")";
}
}
case TRACKPOINTS -> table = TrackPointsColumns.TABLE_NAME;
case TRACKS -> table = TracksColumns.TABLE_NAME;
case MARKERS -> table = MarkerColumns.TABLE_NAME;
default -> throw new IllegalArgumentException("Unknown url " + url);
}
int count;
try {
db.beginTransaction();
count = db.update(table, values, whereClause, selectionArgs);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(table);
count = queryBuilder.update(db, values, where, selectionArgs);
} else {
try {
db.beginTransaction();
count = db.update(table, values, where, selectionArgs);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
getContext().getContentResolver().notifyChange(url, null, false);
return count;
}
@@ -391,14 +372,14 @@ public class CustomContentProvider extends ContentProvider {
throw new SQLException("Failed to insert a marker " + url);
}
@VisibleForTesting
enum UrlType {
private enum UrlType {
TRACKPOINTS,
TRACKPOINTS_BY_ID,
TRACKPOINTS_BY_TRACKID,
TRACKS,
TRACKS_BY_ID,
TRACKS_SENSOR_STATS,
TRACKS_AGGREGATED_STATISTICS,
MARKERS,
MARKERS_BY_ID,
MARKERS_BY_TRACKID
@@ -29,7 +29,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 38;
private static final int DATABASE_VERSION = 40;
private final Context context;
@@ -82,6 +82,8 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 36 -> upgradeFrom35to36(db);
case 37 -> upgradeFrom36to37(db);
case 38 -> upgradeFrom37to38(db);
case 39 -> upgradeFrom38to39(db);
case 40 -> upgradeFrom39to40(db);
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
}
}
@@ -107,6 +109,8 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 35 -> downgradeFrom36to35(db);
case 36 -> downgradeFrom37to36(db);
case 37 -> downgradeFrom38to37(db);
case 38 -> downgradeFrom39to38(db);
case 39 -> downgradeFrom40to39(db);
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
}
}
@@ -670,4 +674,64 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.endTransaction();
}
private void upgradeFrom38to39(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("DROP INDEX tracks_uuid_index");
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, uuid BLOB, name TEXT, description TEXT, activity_type TEXT, activity_type_localized TEXT, time_offset INTEGER, time_start INTEGER, time_stop INTEGER, duration_total INTEGER, duration_moving INTEGER, distance FLOAT, speed_max FLOAT, altitude_min FLOAT, altitude_max FLOAT, altitude_gain FLOAT, altitude_loss FLOAT)");
db.execSQL("INSERT INTO tracks SELECT _id, uuid, name, description, activity_type, category, starttime_offset, starttime, stoptime, totaltime, movingtime, totaldistance, maxspeed, minelevation, maxelevation, elevationgain, elevationloss FROM tracks_old");
db.execSQL("DROP TABLE tracks_old");
db.execSQL("CREATE UNIQUE INDEX tracks_uuid_index ON tracks(uuid)");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom39to38(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("DROP INDEX tracks_uuid_index");
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, icon TEXT, uuid BLOB, elevationloss FLOAT, starttime_offset INTEGER)");
db.execSQL("INSERT INTO tracks SELECT _id, name, description, activity_type_localized, time_start, time_stop, 0, distance, duration_total, duration_moving, 0, 0, speed_max, altitude_min, altitude_max, altitude_gain, null, uuid, altitude_loss, time_offset FROM tracks_old");
db.execSQL("DROP TABLE tracks_old");
db.execSQL("CREATE UNIQUE INDEX tracks_uuid_index ON tracks(uuid)");
db.setTransactionSuccessful();
db.endTransaction();
}
private void upgradeFrom39to40(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("ALTER TABLE markers RENAME TO markers_old");
db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
db.execSQL("INSERT INTO markers SELECT _id, name, description, category, trackid, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM markers_old");
db.execSQL("DROP TABLE markers_old");
db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom40to39(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("ALTER TABLE markers RENAME TO markers_old");
db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
db.execSQL("INSERT INTO markers SELECT _id, name, description, category, 'http://maps.google.com/mapfiles/ms/micons/blue-pushpin.png', trackid, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM markers_old");
db.execSQL("DROP TABLE markers_old");
db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)");
db.setTransactionSuccessful();
db.endTransaction();
}
}
@@ -0,0 +1,30 @@
package de.dennisguse.opentracks.data;
import android.database.Cursor;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
public class MarkerIterator extends AutoCloseableIterator<Marker> {
private final ContentProviderUtils contentProviderUtils;
MarkerIterator(ContentProviderUtils contentProviderUtils, Cursor cursor) {
super(cursor);
this.contentProviderUtils = contentProviderUtils;
}
MarkerIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, int maxCount) {
super(contentProviderUtils.getMarkerCursor(trackId, maxCount));
this.contentProviderUtils = contentProviderUtils;
}
@NonNull
@Override
public Marker get() {
return contentProviderUtils.createMarker(cursor);
}
}
@@ -1,11 +0,0 @@
package de.dennisguse.opentracks.data;
public record SelectionData(
String selection,
String[] selectionArgs //TODO Refactor to List<String>
) {
public SelectionData() {
this(null, null);
}
}
@@ -52,7 +52,6 @@ public class ShareContentProvider extends CustomContentProvider {
private static final int URI_GPX = 0;
private static final int URI_KML_WITH_TRACKDETAIL_SENSORDATA = 3;
private static final int URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA = 6;
private static final int URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES = 7;
private static final int URI_CSV = 8;
@@ -64,7 +63,6 @@ public class ShareContentProvider extends CustomContentProvider {
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getPreferenceId() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getPreferenceId() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getPreferenceId() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.CSV.getPreferenceId() + "/*/*", URI_CSV);
}
@@ -133,8 +131,6 @@ public class ShareContentProvider extends CustomContentProvider {
case URI_GPX -> TrackFileFormat.GPX;
case URI_KML_WITH_TRACKDETAIL_SENSORDATA ->
TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA ->
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES ->
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
case URI_CSV -> TrackFileFormat.CSV;
@@ -19,7 +19,6 @@ package de.dennisguse.opentracks.data;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
@@ -31,18 +30,15 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.handlers.AltitudeCorrectionManager;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
/**
* Track data hub.
@@ -92,7 +88,6 @@ public class TrackDataHub {
// Registered listeners
private ContentObserver tracksTableObserver;
private ContentObserver markersTableObserver;
private ContentObserver trackPointsTableObserver;
public TrackDataHub(Context context) {
@@ -127,14 +122,6 @@ public class TrackDataHub {
};
contentResolver.registerContentObserver(TracksColumns.CONTENT_URI, false, tracksTableObserver);
markersTableObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
notifyMarkersTableUpdate(listeners);
}
};
contentResolver.registerContentObserver(MarkerColumns.CONTENT_URI, false, markersTableObserver);
trackPointsTableObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
@@ -153,7 +140,6 @@ public class TrackDataHub {
//Unregister listeners
ContentResolver contentResolver = context.getContentResolver();
contentResolver.unregisterContentObserver(tracksTableObserver);
contentResolver.unregisterContentObserver(markersTableObserver);
contentResolver.unregisterContentObserver(trackPointsTableObserver);
if (handlerThread != null) {
@@ -220,7 +206,6 @@ public class TrackDataHub {
listener.clearTrackPoints();
}
notifyTrackPointsTableUpdate(true, listeners);
notifyMarkersTableUpdate(listeners);
}
/**
@@ -241,9 +226,6 @@ public class TrackDataHub {
resetSamplingState();
}
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners);
//Markers
notifyMarkersTableUpdate(trackDataListeners);
}
/**
@@ -261,37 +243,6 @@ public class TrackDataHub {
}
}
/**
* Notifies marker table update.
* Currently, reloads all the markers up to {@link #MAX_DISPLAYED_MARKERS}. To be run in the {@link #handler} thread.
*
* @param trackDataListeners the track data listeners to notify
*/
private void notifyMarkersTableUpdate(Set<Listener> trackDataListeners) {
if (trackDataListeners.isEmpty()) {
return;
}
for (Listener trackDataListener : trackDataListeners) {
trackDataListener.clearMarkers();
}
try (Cursor cursor = contentProviderUtils.getMarkerCursor(selectedTrackId, null, MAX_DISPLAYED_MARKERS)) {
if (cursor != null && cursor.moveToFirst()) {
do {
Marker marker = contentProviderUtils.createMarker(cursor);
for (Listener trackDataListener : trackDataListeners) {
trackDataListener.onNewMarker(marker);
}
} while (cursor.moveToNext());
}
}
for (Listener trackDataListener : trackDataListeners) {
trackDataListener.onNewMarkersDone();
}
}
/**
* Notifies track points table update; to be run in the {@link #handler} thread.
*
@@ -321,22 +272,17 @@ public class TrackDataHub {
return;
}
TrackPoint.Id lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
TrackPoint.Id next = null;
if (localLastSeenTrackPointIdId != null) {
next = new TrackPoint.Id(localLastSeenTrackPointIdId.id() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
}
TrackPoint trackPoint = null;
try (AutoCloseableIterator<TrackPoint> trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointIterator(selectedTrackId, next)) {
long numTotalPoints = trackPointIterator.getCount();
int samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
while (trackPointIterator.hasNext()) {
//Prevents a NPE if stop() is happening while notifyTrackPointsTableUpdate()
TrackStatisticsUpdater currentUpdater = trackStatisticsUpdater;
if (!isStarted()) {
return;
}
@@ -355,22 +301,17 @@ public class TrackDataHub {
localFirstSeenTrackPointId = trackPointId;
}
if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId.id() - localFirstSeenTrackPointId.id()); //TODO That is an assumption; should be derived from the DB.
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
if (trackStatisticsUpdater == null) {
trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoint);
} else {
trackStatisticsUpdater.addTrackPoint(trackPoint);
}
currentUpdater.addTrackPoint(trackPoint);
Statistics statistics = trackStatisticsUpdater.getTrackStatistics();
// Also include the last point if the selected track is not recording.
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
for (Listener trackDataListener : listeners) {
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics());
}
} else {
for (Listener trackDataListener : listeners) {
trackDataListener.onSampledOutTrackPoint(trackPoint, currentUpdater.getTrackStatistics());
}
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (!trackPointIterator.hasNext() && !isSelectedTrackRecording())) {
for (Listener trackDataListener : listeners)
trackDataListener.onSampledInTrackPoint(trackPoint, statistics);
}
localNumLoadedTrackPoints++;
@@ -391,7 +332,6 @@ public class TrackDataHub {
}
/**
* Resets the track points sampling states.
*/
@@ -399,7 +339,7 @@ public class TrackDataHub {
numLoadedPoints = 0;
firstSeenTrackPointId = null;
lastSeenTrackPointId = null;
trackStatisticsUpdater = new TrackStatisticsUpdater();
trackStatisticsUpdater = null;
}
private boolean isStarted() {
@@ -432,39 +372,10 @@ public class TrackDataHub {
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Statistics trackStatistics) {
}
/**
* Called when a sampled out track point is read.
*
* @param trackPoint the trackPoint
*/
default void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Statistics trackStatistics) {
}
/**
* Called when finish sending new track points.
*/
default void onNewTrackPointsDone() {
}
/**
* Called to clear previously sent markers.
*/
default void clearMarkers() {
}
/**
* Called when a new marker is read.
*
* @param marker the marker
*/
default void onNewMarker(@NonNull Marker marker) {
}
/**
* Called when finish sending new markers.
* This gets called after every batch of calls to {@link #clearMarkers()} and {@link #onNewMarker(Marker)}.
*/
default void onNewMarkersDone() {
}
}
}
@@ -0,0 +1,82 @@
package de.dennisguse.opentracks.data;
import android.database.Cursor;
import androidx.annotation.NonNull;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
public class TrackListIterator extends AutoCloseableIterator<TrackListIterator.Item> {
TrackListIterator(Cursor cursor) {
super(cursor);
}
@NonNull
@Override
public Item get() {
final int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
final int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
final int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
final int activityTypeIndex = cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE);
final int activityTypeLocalizedIndex = cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE_LOCALIZED);
final int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
final int startTimeOffsetIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME_OFFSET);
final int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
final int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
final int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
return new Item(
new Track.Id(cursor.getLong(idIndex)),
cursor.getString(nameIndex),
cursor.getString(descriptionIndex),
ActivityType.findBy(cursor.getString(activityTypeIndex)),
cursor.getString(activityTypeLocalizedIndex),
Instant.ofEpochMilli(cursor.getLong(startTimeIndex)),
ZoneOffset.ofTotalSeconds(cursor.getInt(startTimeOffsetIndex)),
Duration.ofMillis(cursor.getLong(totalTimeIndex)),
Distance.of(cursor.getFloat(totalDistanceIndex)),
cursor.getInt(markerCountIndex)
);
}
public record Item(
@NonNull
Track.Id id,
@NonNull
String name,
@NonNull
String description,
@NonNull
ActivityType activityType,
@NonNull
String activityTypeLocalized,
@NonNull
Instant startTime,
@NonNull
ZoneOffset zoneOffset,
@NonNull
Duration totalTime,
@NonNull
Distance totalDistance,
int markerCount
) {
}
}
@@ -1,72 +1,20 @@
package de.dennisguse.opentracks.data;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.NoSuchElementException;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
/**
* A lightweight wrapper around the original {@link Cursor}.
*/
class TrackPointIterator extends AutoCloseableIterator<TrackPoint> {
private static final String TAG = TrackPointIterator.class.getSimpleName();
private final ContentProviderUtils contentProviderUtils;
private final Track.Id trackId;
private final CachedTrackPointsIndexes indexes;
private Cursor cursor;
public class TrackPointIterator extends AutoCloseableIterator<TrackPoint> {
TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
this.contentProviderUtils = contentProviderUtils;
this.trackId = trackId;
cursor = getCursor(startTrackPointId);
indexes = new CachedTrackPointsIndexes(cursor);
}
private Cursor getCursor(TrackPoint.Id trackPointId) {
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId);
}
@Override
public boolean hasNext() {
if (cursor == null) {
return false;
}
return !cursor.isLast() && !cursor.isAfterLast();
super(contentProviderUtils.getTrackPointCursor(trackId, startTrackPointId));
}
@Override
@NonNull
public TrackPoint next() {
if (cursor == null || !cursor.moveToNext()) {
throw new NoSuchElementException();
}
return ContentProviderUtils.fillTrackPoint(cursor, indexes);
}
@VisibleForTesting
public int getCount() {
return cursor.getCount();
}
@Override
public void close() {
if (cursor != null) {
cursor.close();
cursor = null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
public TrackPoint get() {
return ContentProviderUtils.fillTrackPoint(cursor);
}
}
@@ -41,37 +41,32 @@ public class TrackSelection implements ContentProviderUtils.ContentProviderSelec
}
@Override
public SelectionData buildSelection() {
public ContentProviderUtils.SelectionData buildSelection() {
String selection = "";
String[] selectionArgs;
ArrayList<String> fromToArgs = new ArrayList<>();
List<String> selectionArgs = new ArrayList<>();
// Builds selection.
if (!trackIds.isEmpty()) {
selection = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
selectionArgs.addAll(trackIds.stream().map(id -> Long.toString(id.id())).toList());
}
if (!categories.isEmpty()) {
selection += selection.isEmpty() ? "" : " AND ";
selection += String.format(TracksColumns.ACTIVITY_TYPE_LOCALIZED + " IN (%s)", TextUtils.join(",", Collections.nCopies(categories.size(), "?")));
selectionArgs.addAll(categories);
}
if (from != null && to != null) {
selection += selection.isEmpty() ? "" : " AND ";
selection += TracksColumns.STARTTIME + " BETWEEN ? AND ?";
fromToArgs.add(Long.toString(from.toEpochMilli()));
fromToArgs.add(Long.toString(to.toEpochMilli()));
selectionArgs.add(Long.toString(from.toEpochMilli()));
selectionArgs.add(Long.toString(to.toEpochMilli()));
}
if (selection.isEmpty()) {
return new SelectionData();
return new ContentProviderUtils.SelectionData(null, null);
}
// Builds selection arguments.
ArrayList<String> args = new ArrayList<>();
args.addAll(trackIds.stream().map(id -> Long.toString(id.id())).toList());
args.addAll(categories);
args.addAll(fromToArgs);
selectionArgs = args.toArray(String[]::new);
return new SelectionData(selection, selectionArgs);
return new ContentProviderUtils.SelectionData(selection, selectionArgs);
}
}
@@ -41,7 +41,7 @@ public enum ActivityType {
KITE_SURFING("kite surfing", R.string.activity_type_kite_surfing, ActivityIcon.ICON_UNKNOWN, true),
LAND_SAILING("land sailing", R.string.activity_type_land_sailing, ActivityIcon.ICON_UNKNOWN, true),
MIXED_TYPE("mixed type", R.string.activity_type_mixed_type, ActivityIcon.ICON_UNKNOWN, true),
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_mtb_24dp, true),
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp, true),
MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true),
MOUNTAIN_BIKING("mountain biking", R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
@@ -7,6 +7,8 @@ import de.dennisguse.opentracks.settings.UnitSystem;
public record Distance(double distance_m) {
public static final Distance ZERO = Distance.of(0);
public static Distance of(double distance_m) {
return new Distance(distance_m);
}
@@ -17,9 +19,7 @@ public record Distance(double distance_m) {
return Distance.of((double) distance_m);
}
}
public static Distance of(String distance_m) {
return of(Float.parseFloat(distance_m));
}
@Nullable
public static Distance ofOrNull(Double distance_m) {
if (distance_m == null) {
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.data.models;
public record HeartRate(float value) {
public static HeartRate INVALID = HeartRate.of(0.0f);
public static HeartRate of(float value) {
return new HeartRate(value);
@@ -9,4 +10,8 @@ public record HeartRate(float value) {
public float getBPM() {
return value;
}
public boolean isValid() {
return !this.equals(INVALID);
}
}
@@ -1,19 +1,3 @@
/*
* Copyright 2009 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.data.models;
import android.net.Uri;
@@ -23,151 +7,30 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.time.Instant;
import java.util.Objects;
/**
* NOTE: A marker is indirectly (via it's {@link Position}) assigned to one {@link TrackPoint} via position.time.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
* * NOTE: A marker is indirectly (via it's {@link Position}) assigned to one {@link TrackPoint} via position.time.
*/
//TODO All data should be final; no default values.
public final class Marker {
public record Marker(
private Id id;
private String name = "";
private String description = "";
private String category = "";
private String icon = "";
private Track.Id trackId;
@NonNull
Id id,
//Some data might not be used.
private final Position position;
@NonNull
Track.Id trackId,
private Uri photoUrl = null;
@NonNull
String name,
@NonNull
String description,
@NonNull
String typeLocalized,
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
this.trackId = trackId;
if (!trackPoint.hasLocation())
throw new RuntimeException("Marker requires a trackpoint with a location.");
this.position = trackPoint.getPosition();
}
public Marker(@Nullable Track.Id trackId, @NonNull Position position) {
this.trackId = trackId;
Objects.requireNonNull(position);
this.position = position;
}
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint, String name, String description, String category, String icon, Uri photoUrl) {
this(trackId, trackPoint);
this.name = name;
this.description = description;
this.category = category;
this.icon = icon;
this.photoUrl = photoUrl;
}
/**
* May be null if the Marker was not loaded from the database.
*/
@Nullable
public Id getId() {
return id;
}
public void setId(Id id) {
this.id = id;
}
public Instant getTime() {
return position.time();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Track.Id getTrackId() {
return trackId;
}
@Deprecated
public void setTrackId(@NonNull Track.Id trackId) {
this.trackId = trackId;
}
public Position getPosition() {
return position;
}
public boolean hasAccuracy() {
return position.hasHorizontalAccuracy();
}
public Distance getAccuracy() {
return position.horizontalAccuracy();
}
public boolean hasAltitude() {
return position.hasAltitude();
}
public Altitude getAltitude() {
return position.altitude();
}
public boolean hasBearing() {
return position.hasBearing();
}
public Float getBearing() {
return position.bearing();
}
public Uri getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(Uri photoUrl) {
this.photoUrl = photoUrl;
}
public boolean hasPhoto() {
return photoUrl != null;
}
//Some data might not be used.
Position position,
@Nullable
Uri photoUrl
) {
public record Id(long id) implements Parcelable {
@NonNull
@@ -0,0 +1,136 @@
/*
* Copyright 2009 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.data.models;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
/**
* NOTE: A marker is indirectly (via it's {@link Position}) assigned to one {@link TrackPoint} via position.time.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
//TODO All data should be final; no default values.
public final class MarkerBuilder {
private Marker.Id id;
private String name;
private String description;
private String typeLocalized;
private final Track.Id trackId;
//Some data might not be used.
private final Position position;
private Uri photoUrl;
@VisibleForTesting
public MarkerBuilder(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
this.id = null;
this.trackId = trackId;
this.name = "";
this.description = "";
this.typeLocalized = "";
this.photoUrl = null;
if (!trackPoint.hasLocation())
throw new RuntimeException("Marker requires a trackpoint with a location.");
this.position = trackPoint.getPosition();
}
public MarkerBuilder(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint, String name, String description, String typeLocalized, Uri photoUrl) {
this(trackId, trackPoint);
this.name = name;
this.description = description;
this.typeLocalized = typeLocalized;
this.photoUrl = photoUrl;
}
public MarkerBuilder(Marker marker) {
this.id = marker.id();
this.trackId = marker.trackId();
this.name = marker.name();
this.description = marker.description();
this.typeLocalized = marker.typeLocalized();
this.position = marker.position();
this.photoUrl = marker.photoUrl();
}
/**
* May be null if the Marker was not loaded from the database.
*/
@Nullable
public Marker.Id getId() {
return id;
}
//TODO Remove
@VisibleForTesting
@Deprecated
public void setId(Marker.Id id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void setTypeLocalized(String typeLocalized) {
this.typeLocalized = typeLocalized;
}
public Track.Id getTrackId() {
return trackId;
}
public Uri getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(Uri photoUrl) {
this.photoUrl = photoUrl;
}
public boolean hasPhoto() {
return photoUrl != null;
}
public Marker getMarker() {
return new Marker(
id,
trackId,
name,
description,
typeLocalized,
position,
photoUrl
);
}
}
@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import java.time.Instant;
//TODO Use everywhere instead of android.location.Location.
public record Position(
@Nullable Instant time,
@Nullable Double latitude,
@@ -6,9 +6,11 @@ import de.dennisguse.opentracks.settings.UnitSystem;
public record Speed(double speed_mps) {
public static final Speed ZERO = Speed.of(0);
public static Speed of(Distance distance, Duration duration) {
if (duration.isZero()) {
return zero();
return ZERO;
}
return new Speed(distance.toM() / (duration.toMillis() / (double) Duration.ofSeconds(1).toMillis()));
@@ -18,19 +20,10 @@ public record Speed(double speed_mps) {
return new Speed(speed_mps);
}
public static Speed of(String speed_mps) {
return of(Float.parseFloat(speed_mps));
}
public static Speed ofKMH(double speed_kmh) {
return of(Distance.ofKilometer(speed_kmh), Duration.ofHours(1));
}
//TODO Like Duration.ZERO
public static Speed zero() {
return of(0.0);
}
public static Speed max(Speed speed1, Speed speed2) {
if (speed1.greaterThan(speed2)) {
return speed1;
@@ -40,7 +33,7 @@ public record Speed(double speed_mps) {
}
public boolean isZero() {
return speed_mps == 0;
return this.equals(ZERO);
}
public boolean isInvalid() {
@@ -84,7 +77,7 @@ public record Speed(double speed_mps) {
public Duration toPace(UnitSystem unitSystem) {
if (isZero()) {
return Duration.ofSeconds(0);
return Duration.ZERO;
}
double distance = Distance.of(speed_mps).toKM_Miles(unitSystem);

Some files were not shown because too many files have changed in this diff Show More