Compare commits

...

311 Commits

Author SHA1 Message Date
Dennis Guse 2692cc553c Release: v3.25.0 2022-02-22 03:11:50 +01:00
FTno 3db4dc9422 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (490 of 490 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-02-21 22:52:44 +01:00
FTno 01bb4429ad Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.3% (487 of 490 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-02-21 22:52:44 +01:00
Vinicius ca817e789e Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (485 of 486 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2022-02-21 22:52:44 +01:00
FTno 7bf2110a54 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (486 of 486 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-02-21 22:52:44 +01:00
Mario 78662a330f Translated using Weblate (Spanish)
Currently translated at 100.0% (486 of 486 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2022-02-21 22:52:44 +01:00
Mario 8bfaa1d542 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (486 of 486 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2022-02-21 22:52:44 +01:00
Mario 5a1bee13f9 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (486 of 486 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2022-02-21 22:52:44 +01:00
Dennis Guse b01045abe4 CI: enable test and lint for PRs from forks. 2022-02-21 22:52:27 +01:00
dependabot[bot] d7fbae9321 Bump reactivecircus/android-emulator-runner from 2.22.0 to 2.23.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.22.0 to 2.23.0.
- [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases)
- [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/v2.22.0...v2.23.0)

---
updated-dependencies:
- dependency-name: reactivecircus/android-emulator-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-18 06:44:15 +01:00
Dennis Guse ab5e6cbfcd Bugfix: emojis cannot be used as filenames.
Fixes #1129.
2022-02-17 18:36:06 +01:00
Dennis Guse 0848897ed0 Bugfix: speed showed sometimes 0.0 in UI.
Fixes #1117.
2022-02-17 18:19:17 +01:00
Arjan Schrijver 1e4e7b8c15 Allow starting the Dashboard API automatically from the StartRecording API
Fixes #1116.
2022-02-16 17:06:24 +01:00
Román Ginés 7ca6925e7d Recording without GPS: show a Snackbar to warn user that recording is not using GPS. 2022-02-15 17:20:44 +01:00
Dennis Guse 5c06c7548f GPS is now optional: request GPS access in TrackRecordingActivity.
Fixes #1061.
2022-02-14 22:17:10 +01:00
Dennis Guse 8001c03a74 Bugfix: do not leak TTS in voice announcements. 2022-02-14 22:01:52 +01:00
Dennis Guse 30f9b6ffdc Cleanup in VoiceAnnouncement: rename to start() and stop(). 2022-02-14 22:01:52 +01:00
Dennis Guse a7fd6b8dfa TrackRecordingService: call stopSelf after triggering post-workout export.
Might fix #1080.
2022-02-14 22:01:52 +01:00
Dennis Guse cc93384d1f TrackRecordingService: create/ release EGM2008. 2022-02-14 22:01:52 +01:00
Dennis Guse 78519883c0 Refactored ExportServiceResultReceiver to make TrackRecordingService simpler. 2022-02-14 22:01:52 +01:00
Dennis Guse 29051ac978 Cleanup: move creating manual TrackPoints into TrackRecordingManager. 2022-02-14 22:01:52 +01:00
Dennis Guse cf32c81035 Cleanup: extract RecordingData and RecordingStatus from TrackRecordingService. 2022-02-14 22:01:52 +01:00
Dennis Guse 094c97f78d Share: removed PNG share pic.
Fixes #1127.
2022-02-14 21:52:39 +01:00
FTno 8dca84456b Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (483 of 483 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-02-11 10:38:49 +01:00
Katto f962f27309 Translated using Weblate (Estonian)
Currently translated at 97.7% (469 of 480 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2022-02-07 07:28:00 +01:00
FTno 2f0826baf6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (480 of 480 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-02-07 07:28:00 +01:00
Riku Viitanen b7ff0e2fac Translated using Weblate (Finnish)
Currently translated at 93.9% (451 of 480 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2022-02-07 07:28:00 +01:00
Katto fe6ef29f75 Translated using Weblate (Estonian)
Currently translated at 97.7% (469 of 480 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2022-02-07 07:28:00 +01:00
Katto 152cf72e8a Translated using Weblate (Estonian)
Currently translated at 87.3% (69 of 79 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/et/
2022-02-07 07:28:00 +01:00
mondstern 4fd66a3830 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2022-02-07 07:27:59 +01:00
mondstern 9f715207e1 Translated using Weblate (Ukrainian)
Currently translated at 99.1% (473 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2022-02-07 07:27:59 +01:00
mondstern e63ddbc7ae Translated using Weblate (Slovak)
Currently translated at 99.5% (475 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sk/
2022-02-07 07:27:59 +01:00
mondstern 06e5545ac0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2022-02-07 07:27:59 +01:00
mondstern 65413e0304 Translated using Weblate (Polish)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2022-02-07 07:27:59 +01:00
FTno 5e268463e1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-02-07 07:27:58 +01:00
mondstern d31ffad2c9 Translated using Weblate (Italian)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2022-02-07 07:27:58 +01:00
mondstern 46925a4c67 Translated using Weblate (Galician)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2022-02-07 07:27:58 +01:00
mondstern 2a1aeb5dd8 Translated using Weblate (French)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2022-02-07 07:27:58 +01:00
mondstern 454d5d16b5 Translated using Weblate (Basque)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/eu/
2022-02-07 07:27:58 +01:00
mondstern 45f51ff2dd Translated using Weblate (Estonian)
Currently translated at 98.5% (470 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2022-02-07 07:27:58 +01:00
Katto 785511913c Translated using Weblate (Estonian)
Currently translated at 98.5% (470 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2022-02-07 07:27:57 +01:00
mondstern b937e8ad9b Translated using Weblate (Spanish)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2022-02-07 07:27:57 +01:00
mondstern 40534cd7f5 Translated using Weblate (Danish)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/da/
2022-02-07 07:27:56 +01:00
mondstern f50b1c2f6c Translated using Weblate (Czech)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2022-02-07 07:27:56 +01:00
dependabot[bot] 7c19621bfb Bump gradle from 7.1.0 to 7.1.1
Bumps gradle from 7.1.0 to 7.1.1.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 07:27:46 +01:00
Peter Storch 7c06327084 fix #1119 add new activities "swimming", "swimming open" and "workout" 2022-02-06 20:09:08 +01:00
Dennis Guse 19a958ccbb Cleanup. 2022-01-29 19:21:54 +01:00
Dennis Guse 2dc362c105 Cleanup. 2022-01-29 19:21:54 +01:00
Dennis Guse 708245bd50 TrackRecordingServices: managers register for sharedPreference changes. 2022-01-27 22:04:45 +01:00
Dennis Guse f4c07463b3 TrackDataHub: removed unused property. 2022-01-27 22:04:45 +01:00
Dennis Guse d8ffc1318f Refactor: move methods to NotificationManager. 2022-01-27 22:04:45 +01:00
Dennis Guse 5998f9127d TrackPointCreator needs to reset GPSHandler as it might have been running beforehand. 2022-01-27 22:04:45 +01:00
Dennis Guse 40fb9b05bc Lint: make missing plural quantity a warning as they have to be properly translated.
Occurred when upgrading to Android Gradle plugin 7.1.0.
2022-01-27 21:59:37 +01:00
Dennis Guse 8219bfe428 Remove unused plural.
Fixes #1105.
2022-01-27 21:59:37 +01:00
dependabot[bot] bcdea5b08b Bump preference from 1.1.1 to 1.2.0
Bumps preference from 1.1.1 to 1.2.0.

---
updated-dependencies:
- dependency-name: androidx.preference:preference
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-27 21:59:22 +01:00
dependabot[bot] 5e49a38dce Bump mockito-android from 4.3.0 to 4.3.1
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.3.0...v4.3.1)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-26 19:58:16 +01:00
Dennis Guse 32e07c9d1f Reproducible builds: disable PNG crunching.
https://developer.android.com/studio/build/optimize-your-build#disable_crunching
2022-01-25 22:31:00 +01:00
Dennis Guse efc37527f3 Upgrade to Gradle 7.2/ Android Gradle Plugin 7.1. 2022-01-25 22:04:37 +01:00
dependabot[bot] 90a2710138 Bump mockito-android from 4.2.0 to 4.3.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-25 07:36:02 +01:00
Dennis Guse bf4dd96235 Update TrackPoint documentation. 2022-01-24 18:41:28 +01:00
Dennis Guse f578c6bc19 Release: v3.24.1 2022-01-23 21:17:57 +01:00
Dennis Guse a97bf6b883 Track name: use a short version of ISO8601.
Fixes #1093.
2022-01-23 21:04:08 +01:00
Dennis Guse 838f3dc622 Public API: enable other applications to start recordings.
Fixes #671.
2022-01-23 20:13:00 +01:00
Dennis Guse 8ae4d75708 Merge remote-tracking branch 'weblate/main' 2022-01-23 20:03:55 +01:00
mondstern 0c6813aca1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (477 of 477 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-01-22 12:41:41 +01:00
Dennis Guse 05bd2e6a71 Voice announcement: fix French seconds.
Fixes #1101.
2022-01-21 19:56:53 +01:00
mondstern 8cad813d56 Translated using Weblate (Ukrainian)
Currently translated at 98.3% (468 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2022-01-20 12:28:29 +01:00
mondstern 52626d5886 Translated using Weblate (Finnish)
Currently translated at 85.9% (409 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2022-01-20 12:28:29 +01:00
mondstern c238aa7c6d Translated using Weblate (Basque)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/eu/
2022-01-20 12:28:29 +01:00
mondstern 019568144a Translated using Weblate (Danish)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/da/
2022-01-20 12:28:29 +01:00
Dennis Guse 9abe3cf05b PTets: close cursors. 2022-01-20 12:28:23 +01:00
Peter Storch 1099102e29 Change handling of Uri and DocumentFile handling for #1080
The Uris from Intent.getData() and DocumentFile.getUri() seems to be different. I think PreferencesUtils should only work with the original Uri as well as the permission handling logic.

IntentUtils.toDocumentFile can be used to convert it to a DocumentFile later on to actually work with.

Adds "No file manager found" message if no activity with OPEN_DOCUMENT_TREE is found. #1095

Removed deprecation of `startActivityForResult`.
2022-01-19 22:26:52 +01:00
Dennis Guse f64c1feb1a Bugfix: prevent rare crash due to Actvity lifecycle events in AbstractTrackDeleteActivity. 2022-01-19 20:53:30 +01:00
Dennis Guse e1d3b04dd0 Bugfix: MediaPlayer creation might fail for ttsFallback.
Seen on a Fairphone 4.

Part of #963.
2022-01-16 21:39:16 +01:00
Dennis Guse 619b82a23e Package structure: moved functionality from FileUtils to MarkerUtils.
Part of #963.
2022-01-16 21:39:16 +01:00
Dennis Guse d18c67a648 Cleanup: MarkerEditActivity now uses ActivityResultLauncher and implemented onDestroy(). 2022-01-16 21:39:16 +01:00
Dennis Guse 0eda1eed1d Introduced value objects for heartrate, cadence, and power.
Fixes #896.
2022-01-16 15:30:08 +01:00
Dennis Guse 1df1fe699a Cleanup: removed unused parameter. 2022-01-16 15:30:08 +01:00
Dennis Guse 9a7183738f Update .gitignore 2022-01-16 15:20:17 +01:00
Dennis Guse 864bc8fad9 Cleanup. 2022-01-15 23:16:53 +01:00
Dennis Guse 5ea651d340 Merge pull request #1096 from OpenTracksApp/refactor#800
Refactoring package structure.
2022-01-15 22:59:19 +01:00
Dennis Guse 7484ab03ce Package structure: moved UI utils
Part of #963.
2022-01-15 22:40:03 +01:00
Dennis Guse 7caf33fb66 Package structure: moved share functionality.
Part of #963.
2022-01-15 22:28:47 +01:00
Dennis Guse 88c4e1a109 Package structure: moved sensors.
Part of #963.
2022-01-15 22:24:59 +01:00
Dennis Guse 0645628d7e Package structure: moved models and database.
Part of #963.
2022-01-15 22:24:59 +01:00
Dennis Guse cf993b73d7 Package structure: moved custom recording layout.
Part of #963.
2022-01-15 22:24:59 +01:00
Dennis Guse d49386259a Package structure: moved support files.
Part of #963.
2022-01-15 22:08:30 +01:00
Dennis Guse 10f00c4c99 Package structure: moved UI utilities.
Part of #963.
2022-01-15 22:08:30 +01:00
Dennis Guse 2ca69df447 Package structure: moved Marker UI code.
Part of #963.
2022-01-15 22:08:21 +01:00
Dennis Guse 1a125ca60d Package structure: moved aggregated statistics.
Part of #963.
2022-01-15 22:08:06 +01:00
Dennis Guse 5053c41bec Package structure: moved intervals.
Part of #963.
2022-01-15 22:07:53 +01:00
Dennis Guse 46cf7f9acf TrackRecordingServiceConnection: use a callback instead of a Runnable. 2022-01-15 20:53:46 +01:00
Dennis Guse 522d8b41d8 Bugfix: use monotonic clock.
Fixes #800.
2022-01-15 19:52:27 +01:00
Dennis Guse 43e1c99642 Refactoring package structure.
Part of #963.
2022-01-14 23:34:05 +01:00
mondstern 81d516d11a Translated using Weblate (Slovak)
Currently translated at 99.5% (474 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sk/
2022-01-14 07:25:36 +01:00
mondstern b8ce386556 Translated using Weblate (Slovak)
Currently translated at 72.0% (343 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sk/
2022-01-14 07:25:36 +01:00
mondstern bda8a9ea83 Translated using Weblate (Slovak)
Currently translated at 65.7% (313 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sk/
2022-01-14 07:25:36 +01:00
dependabot[bot] 79b3e4ea5b Bump material from 1.4.0 to 1.5.0
Bumps [material](https://github.com/material-components/material-components-android) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.4.0...1.5.0)

---
updated-dependencies:
- dependency-name: com.google.android.material:material
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-14 07:25:17 +01:00
dependabot[bot] 1e15222e99 Bump appcompat from 1.4.0 to 1.4.1
Bumps appcompat from 1.4.0 to 1.4.1.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-13 08:00:42 +01:00
Dennis Guse 58b1396d64 Refactoring package structure.
Part of #963.
2022-01-12 22:57:35 +01:00
Dennis Guse a9f1381dae Refactoring package structure.
Part of #963.
2022-01-12 22:42:23 +01:00
Dennis Guse e508c53ca9 TrackRecordingManager: store GPS-based TrackPoints based upon the distance to the previously stored GPS-based TrackPoint. 2022-01-12 18:07:48 +01:00
Dennis Guse d691c958b9 Cleanup: TrackRecordingManager should use onNewTrackPoint(). 2022-01-12 18:07:48 +01:00
Dennis Guse bc1edec372 Bugfix: ChartView needs data.
Fixes #1086.
2022-01-12 18:06:26 +01:00
Dennis Guse 11de7d5868 Cleanup. 2022-01-09 22:35:15 +01:00
Dennis Guse 44e8cd1491 Bugfix: TrackDataHub should abort processing if stopped. 2022-01-09 19:02:54 +01:00
Dennis Guse 9dac5d3e72 Release: v3.24.0 2022-01-09 18:39:08 +01:00
Dennis Guse cd7bf36aa9 TrackRecordingService: add integration tests. 2022-01-09 18:28:35 +01:00
Dennis Guse ab07d3b130 TrackRecordingService: re-worked TrackPoint recording tests for state changes. 2022-01-09 18:28:35 +01:00
Dennis Guse 8b5320e40e TrackRecordingService: cleanup tests. 2022-01-09 18:28:35 +01:00
Dennis Guse ef15632d7b TrackRecordingService: refactored and unified tests. 2022-01-09 18:28:35 +01:00
Dennis Guse f5ce76955b TrackRecordingService: add tests for state changes. 2022-01-09 18:28:35 +01:00
mondstern 3701b61852 Translated using Weblate (Spanish)
Currently translated at 91.0% (71 of 78 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/es/
2022-01-08 19:19:57 +01:00
mondstern 3b8a243f55 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2022-01-08 19:19:57 +01:00
mondstern e19f55a618 Translated using Weblate (Ukrainian)
Currently translated at 97.4% (464 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2022-01-08 19:19:57 +01:00
mondstern 9e6a787ed9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2022-01-08 19:19:57 +01:00
mondstern 9626c5dd0b Translated using Weblate (Polish)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2022-01-08 19:19:57 +01:00
FTno 1e42ef5e6c Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-01-08 19:19:57 +01:00
mondstern e5792336a8 Translated using Weblate (Italian)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2022-01-08 19:19:57 +01:00
Xosé M 9d4ec8ba2a Translated using Weblate (Galician)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2022-01-08 19:19:56 +01:00
mondstern e928adf3b5 Translated using Weblate (French)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2022-01-08 19:19:56 +01:00
mondstern f061243a7d Translated using Weblate (Estonian)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2022-01-08 19:19:56 +01:00
mondstern 1d623a076f Translated using Weblate (Spanish)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2022-01-08 19:19:56 +01:00
mondstern 13ed210933 Translated using Weblate (Czech)
Currently translated at 100.0% (476 of 476 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2022-01-08 19:19:56 +01:00
Román Ginés eba4918b01 StringUtilsTest.testFormatDateTime removed. Fixes #1082. 2022-01-08 19:19:45 +01:00
Dennis Guse 9bde8944b9 Workaround: Clock.systemUTC() seems to be not monotonic.
Introduced in 25e590da02
2022-01-06 22:12:53 +01:00
Dennis Guse 1163f74b98 SENSORPOINT and TRACKPOINT require a distance to be stored.
TrackRecording and TrackStatisticsUpdater.

Part of #500
Fixes #1074.
2022-01-06 22:12:53 +01:00
Dennis Guse c4b8082efc Bluetooth sensors: event-driven TrackPoint creation.
Part of #500.
2022-01-06 22:12:53 +01:00
Dennis Guse 1fb8fc6a09 Bugfix: GpxTrackImporter needs to reset data when it encounters a new TrackPoint; affected only sensor data. 2022-01-06 22:12:53 +01:00
Dennis Guse dbc29a88ce Cleanup: removed useless tests. 2022-01-06 22:12:53 +01:00
mondstern 05af52a21b Translated using Weblate (Ukrainian)
Currently translated at 97.4% (460 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2022-01-03 16:27:56 +01:00
mondstern 1a5de37f8a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2022-01-03 16:27:56 +01:00
mondstern 015ddabf3a Translated using Weblate (French)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2022-01-03 16:27:56 +01:00
mondstern 12a008a99a Translated using Weblate (Vietnamese)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2022-01-03 16:27:56 +01:00
mondstern 99ccf65cc2 Translated using Weblate (Polish)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2022-01-03 16:27:56 +01:00
FTno a159d4c375 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-01-03 16:27:55 +01:00
mondstern 01ac31716a Translated using Weblate (Italian)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2022-01-03 16:27:55 +01:00
mondstern cfb2d0c2d3 Translated using Weblate (Estonian)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2022-01-03 16:27:55 +01:00
mondstern c05fa638a0 Translated using Weblate (Czech)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2022-01-03 16:27:55 +01:00
mondstern 519f2b016e Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-01-03 16:27:55 +01:00
Román Ginés 5cd6992c90 Proper handling of timezone data on UI. Fixes #301.
Details:
- Date and time handled by OffsetDateTime Java class.
- Track default name with offset (for date local and ISO 8601 options).
- TrackListActivity: shows relative today's date (like Today, Yesterday, 22 Dec, 22 Dec 2021, etc).
- StatisticsRecordedFragment: always shows the original date and time. For example, if you record an activity at 20h then it always will show 20h regardless the zone are where you are.
- Added new tests.
2022-01-03 16:27:40 +01:00
Peter Storch 1631836327 Try to takkle down export dir issues 2022-01-02 21:19:43 +01:00
Dennis Guse 07074ad260 TrackStatistics and ChartView: handle altitude and speed as optional with null as default value.
Then only the relevant part of the y-axis is shown.

Fixes #795.
2022-01-02 18:17:20 +01:00
Dennis Guse 8c20d5583a Charts: elevation is now optional.
Part of #500.
2022-01-02 18:17:20 +01:00
Xosé M 39bce2a487 Translated using Weblate (Galician)
Currently translated at 100.0% (472 of 472 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-12-30 12:16:45 +01:00
Dennis Guse a221194ab1 Translated using Weblate (German)
Currently translated at 89.5% (422 of 471 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-12-30 12:16:45 +01:00
Dennis Guse 55d3502ff9 Cleanup using AndroidStudio analyze. 2021-12-30 12:16:31 +01:00
Dennis Guse 0cd22a8ba0 README: add nightly repo. 2021-12-29 10:33:08 +01:00
Dennis Guse aff4bce3fe Cleanup: tests also should close cursors. 2021-12-28 23:17:52 +01:00
Dennis Guse bb290e2ee4 Settings: add option to disable filter of BLE sensors.
Fixes #1000.
2021-12-28 22:56:29 +01:00
Dennis Guse 6d04659370 Timezone support: store timezone offset per Track.
When a recording is started the offset of the phone is used.
Timezone is exported to KML and GPX.

Fixes #301.
2021-12-28 15:42:38 +01:00
Dennis Guse 7193171a09 Merge pull request #1033 from OpenTracksApp/indoor#500
Storing data from sensors without GPS
2021-12-28 14:24:57 +01:00
Dennis Guse 7d0d87e962 Tests: ServiceTestRule does not (always) restart the service.
Thus, resetting it's status is not happening and then leading to failures in subsequent tests.

Fixes #1058.
2021-12-28 13:05:24 +01:00
Dennis Guse d6cc7a0321 TrackPointCreator: only try fill TrackPoint with sensor data if started. 2021-12-28 13:05:24 +01:00
Dennis Guse c7bbaddc06 Cleanup: fix various issues in tests. 2021-12-28 13:05:24 +01:00
Dennis Guse 9af213fd12 Bugfix: selected interval may be null. 2021-12-28 13:05:24 +01:00
Dennis Guse 77f56a5376 Test: disable automatic creation of TrackPoints for tests.
Part of #500.
2021-12-28 13:05:24 +01:00
Dennis Guse ea5ac186d0 README: add sensor-only feature.
Part of #500.
2021-12-28 13:05:24 +01:00
Dennis Guse 84c51ba8bb GPX: export cumulative sensor data from SENSORPOINTs.
Part of #500.
2021-12-28 13:05:24 +01:00
Dennis Guse 89f9f25c55 GPX: export track statistics using TrackStatsExtensionv1.
Fixes #652.
2021-12-28 13:05:24 +01:00
Dennis Guse f8cc257895 KMZ/KML: Export/Import SENSORPOINTs.
Part of #500.
2021-12-28 13:05:23 +01:00
Dennis Guse ba66c45005 Recording data from BLE sensors alone (need to provide distance).
GPS is optional in this case, but no coordinates will be available.

Naive implementation tries to store data every second using the UI timer.

Part of #500.
2021-12-28 13:05:23 +01:00
Dennis Guse 24b638aa30 Add TrackPoint type for BLE sensor only data (i.e., no GPS).
Part of #500.
2021-12-28 13:05:23 +01:00
Dennis Guse 45125bcda3 Refactor: rename LocationHandler to GPSHandler. 2021-12-28 13:05:23 +01:00
Dennis Guse 3359a30907 TrackRecordingService: first process the TrackPoint, then update the notification. 2021-12-28 13:05:23 +01:00
dependabot[bot] 6f822afc71 Bump reactivecircus/android-emulator-runner from 2.21.0 to 2.22.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.21.0 to 2.22.0.
- [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases)
- [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/v2.21.0...v2.22.0)

---
updated-dependencies:
- dependency-name: reactivecircus/android-emulator-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 06:44:32 +01:00
Dennis Guse 230e97147c Update bug report template. 2021-12-25 19:54:59 +01:00
Román Ginés b4ce99440a AggregatedStatisticsActivity: added filter option (by category, start and/or end date).
Fixes #1039.
2021-12-24 11:50:32 +01:00
Dennis Guse 4d333bbafa Merge pull request #1056 from OpenTracksApp/whaoo#1050 2021-12-22 22:29:09 +01:00
Dennis Guse 0ee1c77680 Sensors: added Wahoo Blue SC. 2021-12-22 22:27:25 +01:00
Dennis Guse 183b47a1ea Release: v3.23.0 2021-12-21 22:48:35 +01:00
Dennis Guse 6fe6cee475 BLE: CSC remove workarounds as those are not necessary.
The implementation of OpenTracks for the CSC parsing was broken and fixed in 35de5b6b26

Fixes #1046.
2021-12-21 22:00:38 +01:00
Dennis Guse 52228d44a9 Cleanup. 2021-12-21 21:55:41 +01:00
mondstern bc84e8cb9a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-12-21 20:53:19 +01:00
mondstern fb9e67ae85 Translated using Weblate (Estonian)
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2021-12-21 20:53:19 +01:00
mondstern 8877f3fe14 Translated using Weblate (Czech)
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-12-21 20:53:18 +01:00
mondstern 963a37b877 Translated using Weblate (Italian)
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2021-12-21 20:53:18 +01:00
Dennis Guse 3be1348df1 Merge pull request #1047 from OpenTracksApp/movingTime#1046
Restore moving time implementation via idle/non-idle TrackPoints.
Fixes #1046.
2021-12-21 20:52:59 +01:00
Dennis Guse 530967f4f6 Moving time: disable test on API23. 2021-12-21 17:46:23 +01:00
Dennis Guse 25e590da02 Moving time: re-implement idle/non-idle TrackPoints.
Simplify computation of TrackStatistics.

Part of #1046.
2021-12-19 22:01:35 +01:00
Dennis Guse 0be4b25508 Moving time: fix test.
Part of #1046.
2021-12-19 21:56:53 +01:00
Dennis Guse 4c7d9384fb Revert "Only record a TrackPoint if the traveled distance is greater than recording distance interval."
This reverts commit 08ba7d50c7.

Fix #1046.
2021-12-19 21:56:53 +01:00
Dennis Guse 101701c6ff CI: use Android 23 and 30. 2021-12-19 21:56:39 +01:00
Allan Nordhøy 8f003e2f7f Fastlane: reworked description 2021-12-19 21:54:55 +01:00
Dennis Guse 03d825d2ec Cleanup: reference resources manually. 2021-12-19 21:54:13 +01:00
dependabot[bot] 0977d34889 Bump mockito-android from 4.1.0 to 4.2.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.1.0...v4.2.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 07:41:26 +01:00
Quang Trung 6dee9884ff Translated using Weblate (Vietnamese)
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-12-15 21:39:24 +01:00
ThePPK 7b44b42def Translated using Weblate (Polish)
Currently translated at 92.2% (71 of 77 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/pl/
2021-12-15 21:39:24 +01:00
ThePPK ad712f28c6 Translated using Weblate (Polish)
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2021-12-15 21:39:24 +01:00
Peter Storch b287967bc4 fix persistable directory permissions 2021-12-15 21:39:10 +01:00
dependabot[bot] 29c72802aa Bump orchestrator from 1.4.0 to 1.4.1
Bumps orchestrator from 1.4.0 to 1.4.1.

---
updated-dependencies:
- dependency-name: androidx.test:orchestrator
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-14 08:07:06 +01:00
FTno f5241e8046 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-12-13 18:21:06 +01:00
Xosé M 1e720b2fa7 Translated using Weblate (Galician)
Currently translated at 100.0% (466 of 466 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-12-13 18:21:06 +01:00
Román Martínez 19382f19c4 Aggregated stats for only selected tracks. 2021-12-12 21:38:08 +01:00
Dennis Guse 14b8ab21db Add Github funding 2021-12-09 17:25:33 +01:00
dependabot[bot] 00b0e1face Bump gradle from 7.0.3 to 7.0.4
Bumps gradle from 7.0.3 to 7.0.4.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-09 08:15:48 +01:00
Román Martínez 7f9057c519 Added profiles for custom layouts.
Users can add severals custom layouts and select one of them in recording fragment. The last selected layout is the one that will be use in the next recording.
Fixes #938.
2021-12-07 18:07:25 +01:00
Dennis Guse 02cec48ae4 Merge pull request #1036 from OpenTracksApp/npe#1035
Fix NPE #1035
2021-12-04 22:23:27 +01:00
Dennis Guse 0d45efdf07 Cleanup. 2021-12-04 21:47:43 +01:00
Dennis Guse b6d935cf34 Bugfix: prevent NPE as handler should not be executed anymore.
Fixes #1035.
2021-12-04 21:47:43 +01:00
mondstern 3228f58187 Translated using Weblate (Estonian)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2021-12-03 19:06:43 +01:00
Dennis Guse 1e27542aea Bugfix: free memory in onDestroy(). 2021-11-29 21:33:12 +01:00
mondstern fd7892bdf7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-11-29 08:15:25 +01:00
mondstern 2ae8a753c9 Translated using Weblate (Estonian)
Currently translated at 99.1% (453 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2021-11-29 08:15:25 +01:00
Peter Storch bb7efaa87c fix #1025 exportdir failed to create document
fix #254 illegal filename characters
2021-11-29 08:15:08 +01:00
mondstern 2eab7425f2 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-11-26 23:06:46 +01:00
FTno a19bbd6153 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-11-26 23:06:45 +01:00
mondstern b523b8af9e Translated using Weblate (French)
Currently translated at 92.2% (71 of 77 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/fr/
2021-11-24 20:06:44 +01:00
mondstern e9a71a10f6 Translated using Weblate (Vietnamese)
Currently translated at 97.1% (444 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-11-24 20:06:43 +01:00
silkevicious a0d69b589b Translated using Weblate (Italian)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2021-11-24 20:06:42 +01:00
mondstern 71af495835 Translated using Weblate (French)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-11-24 20:06:41 +01:00
mondstern ed4fb30b35 Translated using Weblate (Czech)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-11-24 20:06:40 +01:00
mondstern 7f1c014bf8 Translated using Weblate (Galician)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-11-24 00:06:57 +01:00
FTno affa72fad7 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (457 of 457 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-11-22 18:24:14 +01:00
FTno 14a14cd91f Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-11-22 18:24:14 +01:00
dependabot[bot] ff89d2c5ec Bump mockito-android from 4.0.0 to 4.1.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 18:24:07 +01:00
Peter Storch 54f1690478 Change versionCode for nightlies to commit count 2021-11-20 23:25:17 +01:00
Dennis Guse 681058c3e6 i18n: move translatable settings strings into strings.xml 2021-11-20 22:05:08 +01:00
Dennis Guse d93e025963 BLE sensors: clear data when disconnected.
Part of #1027.
2021-11-20 19:43:38 +01:00
Dennis Guse 1f0a99c49a Recording: ignore TrackPoints that contain sensor data and do not fulfill recording distance interval.
Follow-up to 08ba7d50c7

Part of #1027.
2021-11-20 19:43:38 +01:00
Román Martínez 976d4f848f Bugfixed: illegal state exception crashed fixed. Fixes #1022. 2021-11-18 17:15:53 +01:00
dependabot[bot] 540a3340b3 Bump appcompat from 1.3.1 to 1.4.0
Bumps appcompat from 1.3.1 to 1.4.0.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-18 12:06:56 +01:00
mondstern 2b4c68f3ec Translated using Weblate (Vietnamese)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-11-16 22:42:36 +01:00
mondstern 90d55e419e Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-11-16 22:42:36 +01:00
mondstern ac31f4fd6a Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-11-16 22:42:36 +01:00
mondstern 32a75bb479 Translated using Weblate (Galician)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-11-16 22:42:36 +01:00
mondstern d32f1e05ec Translated using Weblate (French)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-11-16 22:42:36 +01:00
mondstern defd6aed85 Translated using Weblate (Spanish)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-11-16 22:42:36 +01:00
vsevjednom-cz 7463dc703c Translated using Weblate (Czech)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-11-16 22:42:36 +01:00
mondstern 2ab04da936 Translated using Weblate (Czech)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-11-16 22:42:36 +01:00
Dennis Guse 6b84d3b9ba Nightly build: icon has now a black background.
Fixes #952.
2021-11-16 22:42:30 +01:00
vsevjednom-cz 78b57cdc67 Voice announcements: normalize non-TTS audio replacement 2021-11-15 22:40:30 +01:00
Dennis Guse 35de5b6b26 Cleanup: BLE cycling speed/distance sensor parsing should be the other way around./
Was seemingly still working...
2021-11-15 18:53:14 +01:00
Dennis Guse d6da5f0862 Release: v3.22.0 2021-11-14 21:28:31 +01:00
Dennis Guse c3d55127fe Bugfix: running sensor data total is distance is UINT_32. 2021-11-14 21:20:24 +01:00
Román Martínez b1032f898a New field for recording layout: clock.
With this change fields can be: short, wide, clock short or clock wide.
Fixes #1015.
2021-11-14 16:27:37 +01:00
Dennis Guse d4a3762641 Merge remote-tracking branch 'weblate/main' 2021-11-13 20:10:19 +01:00
Dennis Guse ce5eec6a68 Bugfix: prevent NPE when disabling voice announcements while recording.
Fixes #940.
2021-11-13 20:07:32 +01:00
FTno cf71829bec Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.1% (70 of 76 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/nb_NO/
2021-11-11 08:50:24 +01:00
FTno 39606ccd9f Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-11-11 08:50:24 +01:00
Dennis Guse 9dd67e4db7 CI: nightly build with flexible file names. 2021-11-11 08:50:02 +01:00
Peter Storch 212345dea1 Set distinct versionCode and name for nightlies 2021-11-11 08:50:02 +01:00
Dennis Guse 08ba7d50c7 Only record a TrackPoint if the traveled distance is greater than recording distance interval.
Code seemed to be a leftover from the removed idle/non-idle functionality.

Fixed #1007.

Co-authored-by: vlmendz <91810638+vlmendz@users.noreply.github.com>
2021-11-08 22:42:14 +01:00
Dennis Guse 62401b2c5c Cleanup. 2021-11-07 22:19:02 +01:00
Dennis Guse 7cfdacecc3 UI: Remove "Sensor:" prefix from speed description. 2021-11-07 17:58:28 +01:00
mondstern 8f2e234c63 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-11-04 23:06:38 +01:00
mondstern dcd101a20a Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-11-04 23:06:38 +01:00
mondstern cb5f3ace5a Translated using Weblate (Galician)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-11-04 23:06:37 +01:00
mondstern 8e889fbce2 Translated using Weblate (Czech)
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-11-04 23:06:36 +01:00
mondstern ec390c4a81 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (439 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-11-04 01:55:09 +01:00
Vinicius 30e9e930ab Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (438 of 439 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-11-03 06:08:52 +01:00
mondstern 3719d9ac8f Translated using Weblate (Czech)
Currently translated at 98.8% (429 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-11-03 06:08:51 +01:00
dependabot[bot] e33cfa8666 Bump actions/checkout from 2.3.5 to 2.4.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.5 to 2.4.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.5...v2.4.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-03 06:08:33 +01:00
Dennis Guse 8ff1db6a23 README: add link to Amazon App Store 2021-11-02 22:03:09 +01:00
Dennis Guse a9228017fc Setting: add option to adjust idle speed.
New default: 0.5km/h (replaces 0.5mp/h).

Fixes #1004.
2021-11-01 22:36:29 +01:00
Román Martínez 41e5e71804 Bugfix: intervals expected gain and loss to be null or not (both or neither). But only one of them could be null. Fixes #996. 2021-10-31 18:12:28 +01:00
Dennis Guse 9f858168f8 Release: v3.21.0 2021-10-29 07:54:59 +02:00
Andrij Mizyk cfdcea2d88 Translated using Weblate (Ukrainian)
Currently translated at 92.6% (402 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-10-29 05:58:50 +02:00
Quang Trung 2d2db41c5d Translated using Weblate (Vietnamese)
Currently translated at 100.0% (434 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-10-29 05:58:50 +02:00
Xosé M 2c73d0645a Translated using Weblate (Galician)
Currently translated at 100.0% (434 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-10-29 05:58:50 +02:00
☆Verdulo ed1027eccc Translated using Weblate (Esperanto)
Currently translated at 92.0% (69 of 75 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/eo/
2021-10-29 05:58:49 +02:00
Dennis Guse 0dec17eba5 Bugfix: prevent NPE in onDestroy. 2021-10-29 05:58:22 +02:00
dependabot[bot] 774d701a09 Bump reactivecircus/android-emulator-runner from 2.20.0 to 2.21.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.20.0 to 2.21.0.
- [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases)
- [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/v2.20.0...v2.21.0)

---
updated-dependencies:
- dependency-name: reactivecircus/android-emulator-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-25 20:18:39 +02:00
Dennis Guse 23bcbf09a5 GPX importer supports speed without namespace (compatibility with other tools).
Fixed #774.
2021-10-20 21:50:51 +02:00
Dennis Guse 115966cbdf Bugfix: NPE using a cycling sensor. 2021-10-20 19:44:47 +02:00
Dennis Guse 1862bcc6f4 Cleanup. 2021-10-19 17:53:08 +02:00
Dennis Guse 900910e9f7 While recording: show cadence and speed from different sensor types correctly.
Fixes #970.
2021-10-19 17:53:08 +02:00
Dennis Guse 45571c68c9 Rename TrackPoint's cycling cadence to cadence as it may contain running data.
Part of #970.
2021-10-19 17:53:08 +02:00
Román Martínez 083b620e41 GPS data recording: remove idle/non-idle changes due to reported speed greater than 0.5mph.
This avoids that to storing GPS locations if the user r actually is not moving (likely bad GPS reception).

Fixes #968.

Co-authored-by: vlmendz <91810638+vlmendz@users.noreply.github.com>
Co-authored-by: Dennis Guse <dennis.guse@alumni.tu-berlin.de>
2021-10-18 23:26:07 +02:00
Dennis Guse 2f7a56bb17 Bugfix: importing files without speed resulted in moving time 0s and max speed of 0.
Fixes #982.
2021-10-18 18:41:53 +02:00
dependabot[bot] d8988663cb Bump actions/checkout from 2.3.4 to 2.3.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.3.5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 12:42:34 +02:00
FTno c742dae6e0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (434 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-10-16 10:23:21 +02:00
FTno 292437e6bd Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-10-16 10:23:20 +02:00
J. Lavoie 8e9cde7222 Translated using Weblate (French)
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-10-16 10:23:20 +02:00
Dennis Guse d07ab3cc22 TrackRecordingManager: do not store altitude changes twice (previous …
…TrackPoint's storage was delayed).

Fixes #969.
2021-10-16 10:23:09 +02:00
Dennis Guse 90493ae498 TrackRecordingManager: do not store sensor distance twice (previous TrackPoint's storage was delayed).
Fixes #969.
2021-10-16 10:23:09 +02:00
Dennis Guse f6b994572a TrackRecordingManager: cache last stored TrackPoint. 2021-10-16 10:23:09 +02:00
Dennis Guse 24826ff442 TrackRecordingManager: cleanup. 2021-10-16 10:23:09 +02:00
Dennis Guse d97e5ab1ef TrackRecordingManager: only keep lastTrackPoint if was not stored. 2021-10-16 10:23:09 +02:00
Dennis Guse 52a110d998 Bugfix: reset on SensorDataSet should only take place if the TrackPoint was stored
Fixes #969.
2021-10-16 10:23:09 +02:00
Román Martínez 12e18b0bcd Bugfix: activity type preference has to update the icon when user change the activity type. Fixes #985. 2021-10-15 22:16:17 +02:00
Román Martínez 66dac4b0ca Reorganize settings into multiple pages. The main page has icons, titles and summaries. Fixes #960. 2021-10-15 22:04:54 +02:00
Dennis Guse 1bb8cbb82a Running sensor Stryd works.
Fixes #962.
2021-10-15 07:57:29 +02:00
Dennis Guse 2f5b7b63e4 Opening from notification: pressing back goes to TrackListActivity.
Fixes #928.
2021-10-14 18:33:44 +02:00
Ristello 58ca78b976 Removed interface AppConfig, since it is technically not needed. 2021-10-13 22:02:38 +02:00
Ristello 8079a25a42 Use an in-memory database when running instrumented tests.
- added new TestRunner and new TestApplication
- The TestRunner loads TestApplication as the apps Application instance when running the tests
- added new interface AppConfig to retrieve the database name
- changed Startup (Application) so that it implements AppConfig
- changed CustomSQLiteOpenHelper so that it retrieves the database name from the Application instance
- Startup provides the production database name
- TestApplication provides null which yields an in-mememory database
2021-10-13 22:02:38 +02:00
Dennis Guse 0527e4ab14 Cleanup: move AnnouncementUtils to VoiceAnnouncement.
Part of #963
2021-10-12 22:24:51 +02:00
Dennis Guse b6b4c5aa39 Cleanup: move PreferencesUtils into settings package.
Part of #963
2021-10-12 22:24:51 +02:00
Dennis Guse 8c5c9d7947 Cleanup: extract preference functionality from StringUtils to PreferenceHelper.
Part of #963.
2021-10-12 22:24:51 +02:00
Dennis Guse eac3d4b3db Cleanup: SharedPreference as static variable.
Fixes #961.
2021-10-12 22:24:51 +02:00
dependabot[bot] d7bc550b29 Bump gradle from 7.0.2 to 7.0.3
Bumps gradle from 7.0.2 to 7.0.3.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-12 22:16:13 +02:00
Dennis Guse d76d5673a8 Bugfix: running sensor's data should be null if not connected.
Fixes #950.
2021-10-09 14:24:50 +02:00
dependabot[bot] 847be686ea Bump mockito-android from 3.12.4 to 4.0.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.12.4 to 4.0.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.12.4...v4.0.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-08 07:12:11 +02:00
Dennis Guse 1e3445cfa2 Nightly build: app name with suffix.
Part of #952.
2021-10-05 19:23:00 +02:00
Román Martínez d0f7939f1a Added phones with barometric sensor information. 2021-10-01 16:16:16 +02:00
vsevjednom-cz 000b8149f8 Translated using Weblate (Czech)
Currently translated at 99.0% (425 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-09-28 11:06:25 +02:00
Quang Trung 05ffa5a373 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-09-25 00:06:30 +02:00
Vinicius e92f1107aa Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-09-25 00:06:30 +02:00
Quang Trung 2ad1fab670 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-09-22 22:30:43 +02:00
mondstern 8c04129ef9 Translated using Weblate (Vietnamese)
Currently translated at 98.3% (420 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-09-22 22:30:43 +02:00
Mario 400d9fe948 Translated using Weblate (Spanish)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-09-22 22:30:42 +02:00
Mario 5d79510750 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2021-09-22 22:30:42 +02:00
vsevjednom-cz 18bb64580f Translated using Weblate (Slovak)
Currently translated at 71.6% (306 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sk/
2021-09-22 22:30:42 +02:00
vsevjednom-cz 7adebfc0d1 Translated using Weblate (Czech)
Currently translated at 97.8% (418 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-09-22 22:30:42 +02:00
Román Martínez 2178ff97d3 Release: v3.20.2 2021-09-22 22:30:16 +02:00
Timo Schwarzer f070495fb4 Ask for permission before using Bluetooth 2021-09-22 22:14:53 +02:00
Román Martínez d99fb5ea47 Cadence set to 0 when connected but user is not pedaling. Fixes #931.
Speed set to 0 when connected but last value is outdated. Fixes #930.
Bug: overflow when reset sensors (cadence and speed). Fixes #833.
Moving time stops when is not moving (the last track point is not moving) and restarts when last track point is moving.
2021-09-21 18:07:23 +02:00
Román Martínez d69a726e0e Renaming variables. 2021-09-18 16:17:29 +02:00
Román Martínez e0111c2ffd Gain and loss are integers. 2021-09-18 16:17:29 +02:00
Timo Schwarzer 487bd6c3a0 Set FLAG_IMMUTABLE for PendingIntents
This fixes a crash on Android 12, since it explicitly requires setting either FLAG_MUTABLE or FLAG_IMMUTABLE
2021-09-07 23:18:14 +02:00
Román Martínez 40aab9444a Time interval setting is in minutes. 2021-09-06 22:06:48 +02:00
377 changed files with 13501 additions and 7160 deletions
+1
View File
@@ -1,4 +1,5 @@
# These are supported funding model platforms
liberapay: OpenTracks
github: [rgmf, pstorch, dennisguse]
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+2 -1
View File
@@ -23,4 +23,5 @@ If applicable:
**Technical information**
- Device: [e.g. Nexus 7 (2013)]
- OS: [e.g. LineageOS 15.1]
- Version [e.g. v3.2.4]
- OpenTracks version: [e.g. v3.2.4]
- OpenTracks commit id (only for nightly builds): [e.g. 4b333bb]
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
- uses: actions/setup-java@v2
with:
@@ -29,6 +29,6 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: OpenTracks-nightly.apk
path: ./build/outputs/apk/nightly/OpenTracks-nightly.apk
path: ./build/outputs/apk/nightly/*.apk
retention-days: 7
+4 -2
View File
@@ -1,6 +1,8 @@
name: Linting
on: [ push ]
on:
push:
pull_request_target:
jobs:
analyse:
@@ -9,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
- uses: actions/setup-java@v2
with:
+8 -4
View File
@@ -1,17 +1,21 @@
#https://github.com/marketplace/actions/android-emulator-runner
name: Test
on: [ push ]
on:
push:
pull_request_target:
jobs:
test:
timeout-minutes: 45
runs-on: macos-latest
strategy:
matrix:
api-level: [ 23, 26 ] #TODO Re-enable 29
api-level: [ 23, 30 ]
target: [ default ]
fail-fast: false
steps:
- name: checkout
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
- uses: actions/setup-java@v2
with:
@@ -19,7 +23,7 @@ jobs:
java-version: '11'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.20.0
uses: reactivecircus/android-emulator-runner@v2.23.0
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
+1
View File
@@ -69,6 +69,7 @@ captures/
.idea/jarRepositories.xml
.idea/markdown-navigator-enh.xml
.idea/markdown-navigator.xml
.idea/deploymentTargetDropDown.xml
# Keystore files
*.jks
+15
View File
@@ -1,5 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="src/main/res/drawable/ic_activity_bike_24dp.xml" value="0.1765" />
<entry key="src/main/res/drawable/ic_activity_sailing_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_snowboarding_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_swimming_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_swimming_open_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_workout_24dp.xml" value="0.1935" />
<entry key="src/main/res/menu/aggregated_statistics.xml" value="0.10208333333333333" />
<entry key="src/main/res/xml/settings_gps.xml" value="0.22604166666666667" />
<entry key="src/main/res/xml/settings_sensors.xml" value="0.2101851851851852" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
+46 -8
View File
@@ -6,7 +6,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
<table>
<tr>
<th>Free</th>
<th>Free (F-Droid)</th>
<th>Free (Nightly for F-Droid)</th>
<th>Donations</th>
<th>Translations</th>
</tr>
@@ -16,6 +17,11 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
<img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="60" align="middle">
</a>
</td>
<td align="center">
<a href="https://fdroid.storchp.de/fdroid/repo?fingerprint=99985A7E73DCB0B16C9BDDCE7A0B4996F88068AE7C771ED53E217E69CD1FF196">
<img alt="Nightly builds (for F-Droid client)" src="https://opentracksapp.com/static/img/fdroid.storchp.de.png" height="90" align="middle">
</a>
</td>
<td align="center">
<a href="https://liberapay.com/OpenTracks/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
@@ -31,14 +37,16 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
<td align="center">
<img alt="OpenTracks version published on F-Droid" src="https://img.shields.io/f-droid/v/de.dennisguse.opentracks.svg" align="middle" >
</td>
<td></td>
<td align="center">
<a href="bitcoin:bc1qckpls3lpjlcpyxzm66hcy65hnesas9zgacy6yc">
<img alt="bitcoin:bc1qckpls3lpjlcpyxzm66hcy65hnesas9zgacy6yc" src="promotion/donations-bitcoin.png" height="90" align="middle">
</a>
</td align="center">
</td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td align="center">
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
@@ -47,6 +55,16 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
</td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td align="center">
<a href="https://www.amazon.de/dp/B09K2KNQ7X/">
<img alt="Get it on Amazon App Store" src="https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-white.png" height="40" align="middle">
</a>
</td>
<td></td>
</tr>
</table>
## Screenshots
@@ -69,7 +87,7 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
* __Photos and Markers:__ mark interesting locations while tracking
* __Export:__
* export tracks either as KMZ (incl. photos), KML, or GPX
* export automatically after each recording (e.g., to sync)
* export automatically after each recording (e.g., to sync via [Nextcloud](https://nextcloud.com/))
* avoid duplication: each exported file contain a random unique identifier (i.e., `opentracks:trackid`)
* __Altitude:__
* gain/loss via barometric sensor (if present)
@@ -80,6 +98,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
* cycling: cadence
* cycling: power meter
* running: speed and cadence
* support for BLE sensor training only (i.e., without GPS) for indoor training
An overview of tested sensors: [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md)
### Gadgetbridge integration
@@ -97,16 +117,34 @@ OpenTracks can be used with [Gadgetbridge](https://www.gadgetbridge.org/):
__Only required permission:__
* _ACCESS_FINE_LOCATION_: required to use the GPS.
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
### API
OpenTracks includes an API for starting/stopping recording by another installed application (e.g., [Automate](https://llamalab.com/automate/), [Tasker](https://tasker.joaoapps.com), or [Easer](https://github.com/renyuneyun/Easer)).
The API can be invoked by sending an explicit Intent to start an activity.
`Package` (depends on the variant installed):
* F-Droid: `de.dennisguse.opentracks`
* GooglePlay: `de.dennisguse.opentracks.playStore`
* Debug: `de.dennisguse.opentracks.debug`
* Nightly: `de.dennisguse.opentracks.nightly`
`Class`:
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
For testing via adb: `adb shell am start -n "package/class"`
The Start/Stop API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings.
## Custom Dashboards (incl. map)
As of v3.3.1, OpenTracks supports custom dashboards.
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
The developer documentation is in [README_API.md](README_API.md).
As of v3.3.1, OpenTracks supports custom dashboards for displaying previously recorded and live tracks.
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates). The Dashboard API is also used by [Gadgetbridge](https://codeberg.org/Freeyourgadget/Gadgetbridge/) for displaying live track statistics on supported wearables.
Alternatively, recorded tracks can be shared as KMZ/GPX with installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)).
However, this is rather slow and does not provide updates while recording.
The developer documentation is in [README_API.md](README_API.md).
## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
@@ -132,4 +170,4 @@ Artwork, logos and user interface remained more or less unchanged.
More information about _Google My Tracks_:
* [Wikipedia page](https://en.wikipedia.org/wiki/MyTracks)
* [Google code archive](https://code.google.com/archive/p/mytracks/)
* [Google code archive](https://code.google.com/archive/p/mytracks/)
+13 -6
View File
@@ -4,11 +4,13 @@
OpenTracks by itself only provides basic features for data visualization and also lacks functionality for analytics.
By itself OpenTracks provides a basic dashboard that can show summary of a track (while recording as well as afterwards).
Although this might be (mostly) sufficient, it has some limitations.
This include limited configurability (e.g., ordering of shown elements) and the lack of Internet access.
In fact, the later is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
This includes limited configurability (e.g., ordering of shown elements) and the lack of internet access.
In fact, the latter is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
The Data API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings.
## Implementation
OpenTracks by default does not expose any data to 3rd-party applications.
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary), the `TrackPoint`s, and the `Waypoint`s of one or more selected tracks.
@@ -17,11 +19,16 @@ These URIs notify observers if on content changes (e.g., a new `TrackPoint` was
Limitations:
* only tracks selected by the user are exposed,
* `Waypoints` are not exposed,
* `Waypoint`s are not exposed,
* access is only granted temporarily and automatically revoked (see `FLAG_GRANT_READ_URI_PERMISSION`),
* no write access is possible, and
* neither track recordings can be started, paused, or stopped.
* no write access is possible
The Data API can also be started automatically by leveraging Extra fields in the explicit Intent for the Start/Stop API.
An application that's starting a recording with an explicit Intent can add the `STATS_TARGET_PACKAGE` and `STATS_TARGET_CLASS` Extra fields to let OpenTracks know it wants to receive the Data API URIs.
OpenTracks will then (if enabled in the settings) automatically return an explicit Intent to the Package + Class as defined in the received Extra fields, including the Data API URIs and read permissions.
By using explicit Intents for the calls and URIs with specific temporary read permissions, OpenTracks tries to protect against data leaking as much as possible.
All required information can be found in `IntentDashboardUtils.java`.
A _reference implementation_ is the [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard).
A _reference implementation_ is the [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard).
+31 -12
View File
@@ -1,6 +1,6 @@
# Overview of tested Bluetooth LE sensors
## HRS: Heartrate Service
## 0x180D: Heartrate Service
* Decathlon|Geonaute|Kalenji Dual HRM Belt
* moofit heart rate monitor
@@ -13,30 +13,28 @@
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
* Garmin HRM-Dual (Reference: 010-12883-00)
## CSCP: Cycling Cadence and Speed Service
## 0x1816: Cycling Cadence and Speed Service
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
However, often only one value is provided.
### Speed
### Speed & Cadence
* Wahoo Blue SC
### Speed only
* Garmin Speed Sensor 2
* Has updatable firmware that requires an account for garmin.com
* This sensor reports speed data as cadence.
A workaround is in place.
* Wahoo Speed (Model: WFRPMSPD)
* CooSpo cadence and speed sensor (model: BK467)
### Cadence
### Cadence only
* Garmin Cadence Sensor 2
* Wahoo Cadence (Model: WFPODCAD2)
This sensor reports cadence data as speed.
A workaround is in place.
## Cycling Power Measurement
## 0x1818: Power Measurement
We only support _Instantaneous Power_.
@@ -44,6 +42,7 @@ _WARNING_: we only support physically movements with valid GPS coordinates.
Using a device indoors (no GPS available) will only show the current power values, but won't store anything.
Also the distance is not computed.
For cycling:
* Assioma Duo Powermeter
* Elite drivo II
* QUARQ Red DZero Powermeter
@@ -51,8 +50,28 @@ Also the distance is not computed.
* Wahoo Kickr v4.0
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061)
## RSC: running cadence and speed
For running:
* Stryd
## 0x1814: running cadence and speed
We do not support type of movement (i.e., walking vs running).
* Polar Stride
* Stryd (incl. power)
## Tested Barometric Sensor Smartphones
There are smartphones that have a barometric sensor that OpenTracks uses to compute altitude gain and loss.
In our tests there are smartphones whose barometric sensor works well and others don't. Here is a list of the smartphones tested:
|Brand|Model|Android Version|Tests|
|-----|-----|---------------|-----------|
|Google|Pixel|LineageOS 17.1|Ok|
|Google|Pixel 2 XL|LineageOS 17.1|Ok on the bike handlebars but it fails when I carry it in a pocket or backpack|
|Google|Pixel 3|Android 11|Failure|
|Google|Pixel 3a|Android 11|Ok|
|Google|Pixel 3a|Lineage 18.1|Ok|
|Google|Pixel 4a|Android 11|Ok|
|Sony|Xperia XZ1 Compact|LineageOS 16.1|Failure|
+32 -16
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.2'
classpath 'com.android.tools.build:gradle:7.1.1'
}
}
@@ -30,6 +30,19 @@ def getVersionName = { ->
}
}
def getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', 'HEAD', '--count'
standardOutput = stdout
}
return Integer.valueOf(stdout.toString().trim())
} catch (ignored) {
return null
}
}
android {
compileOptions {
coreLibraryDesugaringEnabled true
@@ -41,25 +54,20 @@ android {
compileSdkVersion 31
buildFeatures {
viewBinding = true
}
aaptOptions {
cruncherEnabled = false
viewBinding true
}
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4441
versionName "v3.20.1"
versionCode 4752
versionName "v3.25.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdkVersion 21
targetSdkVersion 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
signingConfigs {
@@ -82,10 +90,10 @@ android {
nightly {
signingConfig signingConfigs.nightly
applicationIdSuffix ".nightly"
versionNameSuffix "-nightly"
}
release {
crunchPngs false
minifyEnabled false
}
@@ -97,16 +105,24 @@ android {
applicationVariants.all { variant ->
variant.resValue "string", "applicationId", variant.applicationId
if (variant.buildType.name == 'nightly') {
variant.outputs.all {
setVersionCodeOverride(getVersionCode())
setVersionNameOverride(getVersionName())
outputFileName = "${applicationId}_${variant.versionCode}.apk"
}
}
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'com.google.android.material:material:1.5.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
androidTestImplementation 'androidx.test:core:1.4.0'
@@ -114,7 +130,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.mockito:mockito-android:3.12.4'
androidTestImplementation 'org.mockito:mockito-android:4.3.1'
androidTestUtil 'androidx.test:orchestrator:1.4.0'
androidTestUtil 'androidx.test:orchestrator:1.4.1'
}
+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="22"
width="22"
fill="#000000"
version="1.0"
x="0px"
y="0px"
viewBox="0 0 21.84952 21.99934"
enable-background="new 0 0 99.316 99.997"
xml:space="preserve"
id="svg10"
sodipodi:docname="ic_activity_swimming.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
inkscape:export-filename="/home/peter/StudioProjects/OpenTracks/drawable-svg/ic_activity_swimming.png"
inkscape:export-xdpi="1309.09"
inkscape:export-ydpi="1309.09"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata16"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs14" /><sodipodi:namedview
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:window-width="1831"
inkscape:window-height="1039"
id="namedview12"
showgrid="false"
inkscape:pagecheckerboard="false"
inkscape:zoom="31.780381"
inkscape:cx="10.368032"
inkscape:cy="10.069105"
inkscape:window-x="33"
inkscape:window-y="3"
inkscape:window-maximized="0"
inkscape:current-layer="svg10"
inkscape:document-rotation="0" /><path
id="path2"
style="fill:#ffffff;stroke-width:0.20758"
d="m 18.327285,15.516652 c -1.017125,-0.01824 -1.903328,0.770666 -2.945647,0.500454 -0.952298,-0.271819 -1.950516,-0.765534 -2.949277,-0.338881 -0.938402,0.492101 -2.078868,0.493759 -3.0170251,0 C 8.4157577,15.251421 7.4164572,15.744755 6.4634103,16.017106 5.261142,16.31195 4.2317908,15.256338 3.0296629,15.558611 2.2388959,15.806745 1.0534821,16.11293 1.0534821,16.11293 v 1.764949 c 1.0226762,0.01947 1.9128198,-0.776355 2.9601398,-0.498166 1.4912769,0.589492 1.7842151,0.623743 2.7777974,0.375279 0.9553792,-0.44042 2.0942104,-0.606317 3.0559286,-0.07975 0.9566571,0.429062 1.9815791,0.09313 2.8934291,-0.263824 1.038124,-0.339466 1.95525,0.382078 2.955963,0.461915 1.087184,0.105691 2.019821,-0.784126 3.121615,-0.493622 0.55673,-0.01509 1.943197,1.034046 1.977683,0.148315 v -1.471252 c -0.878739,0.05089 -1.592095,-0.588897 -2.468753,-0.540124 z"
sodipodi:nodetypes="scccccccccccccccss" /><path
id="path2-1"
style="fill:#ffffff;stroke-width:0.20758"
d="m 18.327285,12.592022 c -1.017125,-0.01824 -1.903328,0.770666 -2.945647,0.500454 -0.952298,-0.271819 -1.950516,-0.765534 -2.949277,-0.338881 -0.938402,0.492101 -2.078868,0.493759 -3.0170249,0 C 8.4157578,12.326791 7.4164573,12.820125 6.4634104,13.092476 5.2611421,13.38732 4.2317909,12.331708 3.029663,12.633981 2.238896,12.882115 1.0534822,13.1883 1.0534822,13.1883 v 1.764949 c 1.0226762,0.01947 1.9128198,-0.776355 2.9601398,-0.498166 1.4912769,0.589492 1.7842151,0.623743 2.7777974,0.375279 0.9553792,-0.44042 2.0942104,-0.606317 3.0559286,-0.07975 0.956657,0.429062 1.981579,0.09313 2.893429,-0.263824 1.038124,-0.339466 1.95525,0.382078 2.955963,0.461915 1.087184,0.105691 2.019821,-0.784126 3.121615,-0.493622 0.55673,-0.01509 1.943197,1.034046 1.977683,0.148315 v -1.471252 c -0.878739,0.05089 -1.592095,-0.588897 -2.468753,-0.540124 z"
sodipodi:nodetypes="scccccccccccccccss" /><path
id="path851"
style="fill:#ffffff;stroke-width:1.16861"
d="m 15.771645,8.3604958 c 0.05505,1.1807818 -1.406378,2.0132882 -2.394197,1.3656158 -1.044532,-0.5537235 -1.017922,-2.2353035 0.04332,-2.7559408 1.018447,-0.5919723 2.378758,0.20782 2.350877,1.390325 z" /><path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.18448px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 4.3145569,12.014368 9.3210561,8.9478742 6.786081,6.2628496 11.908511,3.5970428 c 0.936726,-0.3746046 1.601464,0.5528762 1.096915,1.1662478 L 9.4152098,6.6598661 15.504839,12.51419 c -1.334475,-0.390685 -1.595745,-1.008768 -2.663055,-0.656789 -2.818488,0.929488 -2.219707,0.0047 -4.240291,0.02086 -2.2836952,0.01821 -1.8989609,1.154049 -4.2869361,0.136107 z"
id="path2791"
sodipodi:nodetypes="cccccccssc" /></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="22"
width="22"
fill="#000000"
version="1.0"
x="0px"
y="0px"
viewBox="0 0 21.84952 21.99934"
enable-background="new 0 0 99.316 99.997"
xml:space="preserve"
id="svg10"
sodipodi:docname="ic_activity_swimming_open.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
inkscape:export-filename="/home/peter/StudioProjects/OpenTracks/drawable-svg/ic_activity_swimming_open.png"
inkscape:export-xdpi="1309.09"
inkscape:export-ydpi="1309.09"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata16"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs14" /><sodipodi:namedview
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1047"
id="namedview12"
showgrid="false"
inkscape:pagecheckerboard="false"
inkscape:zoom="15.890191"
inkscape:cx="10.006173"
inkscape:cy="13.719156"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg10"
inkscape:document-rotation="0" /><path
id="path2"
style="fill:#ffffff;stroke-width:0.150857"
d="m 2.8183594,17.572266 c -0.1057708,4.65e-4 -0.2130618,0.01356 -0.3222656,0.04102 -0.5746803,0.180329 -1.4355469,0.402344 -1.4355469,0.402344 v 1.28125 c 0.7432174,0.01415 1.3912167,-0.563498 2.1523437,-0.361328 1.0837672,0.428407 1.295504,0.454005 2.0175782,0.273437 0.6943098,-0.32007 1.5217861,-0.441266 2.2207031,-0.05859 0.6952388,0.311815 1.4408397,0.06801 2.1035156,-0.191407 0.7544435,-0.246702 1.4211805,0.277917 2.1484375,0.335938 0.621051,-0.0088 1.351954,-0.399393 1.818402,-0.396552 0.536698,0.02254 1.051376,0.234242 1.498081,0.382763 0.357762,0.07663 0.717881,0.02104 0.970698,-0.07223 0.717277,-0.264623 1.521787,-0.441266 2.220704,-0.05859 0.695237,0.311815 1.440839,0.06801 2.103515,-0.191407 0.229629,-0.07509 0.450663,-0.07784 0.667969,-0.04492 v -1.314453 c -0.295925,-0.04096 -0.594072,-0.02791 -0.892578,0.09961 -0.681973,0.357628 -1.511565,0.358833 -2.19336,0 -0.726431,-0.310175 -1.451915,0.04817 -2.144531,0.246093 -0.116646,0.02861 -0.230986,0.0388 -0.34375,0.03711 v -0.0078 c -0.304634,0.02335 -0.610512,-0.122847 -0.878909,-0.189453 -1.061726,-0.598921 -2.285933,0.148857 -3.056646,0.16011 -0.510364,-0.111475 -1.41674,-0.556159 -2.1425779,-0.246093 -0.6819724,0.357628 -1.5115649,0.358833 -2.1933593,0 C 6.410287,17.389044 5.6848033,17.747388 4.9921875,17.945312 4.2276707,18.132803 3.558755,17.569011 2.8183594,17.572266 Z"
transform="matrix(0.99997,0,0,0.99997,-0.07491,0)"
sodipodi:nodetypes="sccccccccccsccccccccccccccs" /><path
id="path2-1-1"
style="fill:#ffffff;stroke-width:0.150857"
d="m 10.229667,15.445213 c -0.2340521,-0.01177 -0.6643302,0.02851 -0.8995889,0.129006 -0.6819724,0.357628 -1.5115649,0.358834 -2.1933593,0 C 6.410287,15.264044 5.6848033,15.622386 4.9921875,15.820312 4.1184541,16.034604 3.3697256,15.266655 2.4960938,15.486328 1.9214135,15.666657 1.0605469,15.890625 1.0605469,15.890625 v 1.28125 c 0.7432175,0.01415 1.3912167,-0.563498 2.1523437,-0.361328 1.0837672,0.428406 1.2955041,0.454006 2.0175782,0.273437 0.6943098,-0.320069 1.5217861,-0.441275 2.2207031,-0.05859 0.3325161,0.149133 0.6767832,0.169105 1.0175781,0.123047 1.1763193,-0.377141 1.78456,-0.510947 3.130219,0.05666 0.64547,0.05184 1.34431,-0.445591 2.223297,-0.423842 0.264657,-0.01332 1.092167,0.246652 1.478515,0.365234 h 0.002 c 0.560757,0.08029 1.135035,-0.115511 1.658204,-0.320312 0.754443,-0.246704 1.42118,0.277917 2.148437,0.335937 0.679991,0.06611 1.277834,-0.402757 1.941406,-0.40039 v -1.3125 c -0.0096,3.1e-4 -0.01967,-5.36e-4 -0.0293,0 -0.739183,-0.01326 -1.383132,0.559654 -2.140625,0.363281 -0.69207,-0.197542 -1.41674,-0.556158 -2.142578,-0.246094 -1.655465,0.798879 -2.131536,-0.136064 -3.123047,-0.109375 -0.566651,-0.01017 -1.078155,0.324314 -1.626953,0.392578 -0.768304,0.02005 -1.124822,-0.242268 -1.758614,-0.404396 z"
transform="matrix(0.99997,0,0,0.99997,-0.07491,0)"
sodipodi:nodetypes="cccccccccccccccccccccccccc" /><path
id="path851"
style="fill:#ffffff;stroke-width:0.849248"
d="m 11.681761,12.380821 c 0.04001,0.858093 -1.022037,1.463089 -1.7399012,0.992415 -0.759078,-0.4024 -0.73974,-1.624431 0.03148,-2.002787 0.7401222,-0.430195 1.7286822,0.151027 1.7084202,1.010372 z" /><path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.860781px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 3.355712,15.036148 3.6383028,-2.22847 -1.842207,-1.95125 3.722552,-1.9372846 c 0.680734,-0.2722312 1.1638102,0.4017839 0.797146,0.8475309 l -2.609068,1.3782717 4.4254312,4.254431 c -0.969784,-0.283917 -1.159653,-0.733088 -1.9352852,-0.477299 -2.04824,0.675474 -1.613096,0.0034 -3.081487,0.01516 -1.659598,0.01323 -1.380005,0.838666 -3.1153848,0.09891 z"
id="path2791"
sodipodi:nodetypes="cccccccssc" /><path
id="rect4320"
style="fill:#ffffff;stroke-width:1.00154;stroke-linecap:square;paint-order:markers fill stroke"
d="m 21.108524,13.306347 c -0.260342,0.07218 -0.504886,-0.363252 -0.886457,-0.333843 -0.682913,0.05263 -0.688525,0.273741 -1.170401,0.333103 -0.56376,-0.01179 -0.705141,-0.39106 -1.323093,-0.385461 -0.797244,0.05005 -0.816589,0.344784 -1.506419,0.327831 -0.635156,-0.006 -0.584758,-0.400757 -1.137589,-0.37363 -0.445887,0 -0.423469,0.363096 -1.030167,0.33682 0.804394,-1.992062 7.313411,-2.472785 7.054126,0.09518 z"
sodipodi:nodetypes="cscccccc" /><path
id="rect4573"
style="fill:#ffffff;stroke-width:1.00154;stroke-linecap:square;paint-order:markers fill stroke"
d="m 15.952342,6.3416747 0.804782,0.00553 c 0.791609,0.9558102 1.568049,2.3417023 2.288114,5.3258424 l -1.791332,0.368162 c 0.0085,-1.595974 -0.327662,-3.5171502 -1.301564,-5.6995303 z"
sodipodi:nodetypes="ccccc" /><path
id="rect5178"
style="fill:#ffffff;stroke-width:1.00154;stroke-linecap:square;paint-order:markers fill stroke"
d="M 15.714534,5.7993213 16.460602,5.694421 C 17.892566,5.5299173 18.930384,6.2178215 19.826278,7.1622728 18.315795,6.5191329 17.758674,6.2722082 16.194793,6.4666796 14.820535,6.8297473 13.804944,7.2159692 13.25106,7.9257197 13.396619,7.3313592 13.938011,6.4826988 15.714534,5.7993213 Z"
sodipodi:nodetypes="cccccc" /><path
id="rect5178-8"
style="fill:#ffffff;stroke-width:0.961457;stroke-linecap:square;paint-order:markers fill stroke"
d="m 16.165622,5.4722594 c -0.08973,0.080465 -0.17851,0.1681115 -0.26532,0.2648771 l -0.0964,0.1131325 0.390891,0.6164106 C 17.150623,5.4034465 17.686156,5.2561052 19.060064,4.8593949 18.032481,4.6520987 17.033002,4.6944293 16.165622,5.4722594 Z" /><path
id="rect5178-5"
style="fill:#ffffff;stroke-width:0.928756;stroke-linecap:square;paint-order:markers fill stroke"
d="M 16.314771,5.73079 C 16.294181,5.717361 16.273991,5.703711 16.252891,5.690635 L 15.674258,5.3521611 c -1.591186,-0.4463567 -2.388606,-0.054483 -2.781886,0.3520817 0.738652,-0.2696642 1.645743,-0.011014 2.795776,0.4705547 0.09419,0.057052 0.180726,0.1118664 0.264194,0.1668772 z" /></svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

+469
View File
@@ -0,0 +1,469 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
sodipodi:docname="ic_activity_workout.01.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22"
inkscape:export-filename="/home/peter/StudioProjects/OpenTracks/drawable-svg/ic_activity_workout.01.png"
inkscape:export-xdpi="1309.09"
inkscape:export-ydpi="1309.09"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect3162"
effect="spiro" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
lpeversion="1"
is_visible="true"
id="path-effect3149"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="0"
inkscape:window-x="0"
inkscape:window-height="1047"
inkscape:window-width="1920"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="10.326952"
inkscape:cx="15.790447"
inkscape:zoom="31.664715"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base"
inkscape:pagecheckerboard="0"
inkscape:snap-grids="false">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<path
id="path17"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.730205;stroke-opacity:1"
d="m 12.521484,5.0957031 c -0.795342,0 -1.447265,0.6634257 -1.447265,1.4746094 0,0.811183 0.651923,1.4746094 1.447265,1.4746094 0.795342,0 1.445313,-0.663426 1.445313,-1.4746094 0,-0.8111837 -0.649971,-1.4746094 -1.445313,-1.4746094 z m 5.697266,2.2871094 c -1.005293,9.03e-5 -1.820222,0.8150191 -1.820312,1.8203125 0.001,0.2581612 0.02839,0.6283924 0.303378,0.93547 L 15.712713,10.886411 13.488221,8.9477292 10.263672,8.5957031 7.5078513,10.338038 5.7695312,12.576172 c -0.3754732,0.661347 0.1816517,1.249724 0.7773438,0.966797 l 1.7128929,-2.075723 1.9003931,-1.220112 -0.5761766,4.110288 -2.5878906,5.929687 1.484375,-0.0293 c 0.4801983,-0.907334 2.5996092,-5.441406 2.5996092,-5.441406 l 2.054688,0.941406 v 4.423829 h 1.445312 v -5.53125 L 12.75,13.876953 l 0.650391,-3.398437 2.285156,2.103515 1.747452,-1.734477 c 0.31097,0.238627 0.542791,0.17537 0.785751,0.175884 1.005294,-9e-5 1.820222,-0.815019 1.820312,-1.820313 -9e-5,-1.0052934 -0.815019,-1.8202222 -1.820312,-1.8203125 z m 0.0059,1.3535156 c 0.274357,9.402e-4 0.496232,0.2236881 0.496094,0.4980469 1.38e-4,0.2743588 -0.221737,0.4971067 -0.496094,0.4980469 C 17.94953,9.7325585 17.726466,9.5094953 17.726603,9.234375 17.726466,8.9592547 17.94953,8.7361915 18.22465,8.7363281 Z"
transform="scale(0.26458333)"
sodipodi:nodetypes="sssssccccccccccccccccccccccccccccccc" />
<path
id="path9403"
style="fill:#ffffff;stroke:none;stroke-width:0.264999;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;paint-order:markers fill stroke"
d="M 1.0131059,0.53415337 C 0.73729957,0.54114168 0.64785326,0.76400686 0.64775351,1.04575 c 0.001003,0.049428 0.006811,0.095893 0.0170532,0.1405599 H 1.1996578 L 1.3303992,1.4400412 1.6714636,0.85558076 1.9071081,1.1801088 2.1840938,1.1826926 C 2.1968962,1.1377378 2.2034247,1.0922518 2.2032141,1.04575 2.2031128,0.75953474 2.1266608,0.53477144 1.8404456,0.53467013 1.540136,0.53761574 1.5284374,0.67900891 1.4327185,0.76204643 1.3421819,0.69057893 1.2614053,0.53055962 1.0265417,0.53415337 c -0.00447,1.59e-6 -0.00906,-1.1091e-4 -0.013436,0 z M 2.1840938,1.1826926 c -0.016723,0.058723 -0.044375,0.116296 -0.079582,0.173116 l 0.4873088,-0.00258 V 1.1863099 Z M 2.1045121,1.3558086 1.7965206,1.3573589 1.6740474,1.16874 1.3009436,1.7361472 1.0828691,1.3532248 H 0.7273352 C 0.8490025,1.5865985 1.0981629,1.7800149 1.3903438,2.0580913 1.669028,1.8167708 1.9577231,1.5927143 2.1045121,1.3558086 Z M 0.7273352,1.3532248 C 0.69965645,1.3001334 0.67827264,1.2450376 0.66480673,1.1863099 H 0.25914674 v 0.1669149 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 24 24"
id="vector"
version="1.1"
sodipodi:docname="ic_clear_filter.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1011"
id="namedview4"
showgrid="false"
inkscape:zoom="16.541667"
inkscape:cx="12.392292"
inkscape:cy="24.710689"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="vector" />
<path
id="path"
style="fill:#707070;fill-opacity:1"
d="M 5.0390625 4 C 4.2090625 4 3.74 4.949375 4.25 5.609375 C 6.0862696 7.9637998 8.9006606 11.587089 9.4765625 12.328125 L 17.804688 4 L 5.0390625 4 z M 14 13.958984 L 10 17.958984 L 10 19 C 10 19.55 10.45 20 11 20 L 13 20 C 13.55 20 14 19.55 14 19 L 14 13.958984 z " />
<rect
style="fill:#707070;fill-opacity:1;stroke:none;stroke-width:1.118;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect833"
width="21.37405"
height="1.7606405"
x="-10.108474"
y="16.886091"
ry="0.88032025"
transform="rotate(-45)" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,11 @@
v3.20.2: OpenTracks
Changes:
- Elevation gain and loss are integers instead of floats
- Ask for permission before using Bluetooth by a Toast message
Bugfix:
- Set FLAG_IMMUTABLE for PendingIntents to avoid crash on Android 12
- Cadence sensor returns 0 instead of None when user is not pedaling
- Speed sensor returns 0 instead of the last speed when it's stopped
- With sensors, moving time stops when is not moving
@@ -0,0 +1,19 @@
v3.21.0: OpenTracks
Changes:
- While recording: remove idle/non-idle creation of TrackPoints
- Settings are now in separate folders
- Opening OpenTracks from notification (while recording) and pressing back opens track list
- GPX compatibilty: support <speed> for import
Bugfixes:
- Altitude and distance (from BlE sensor): only reset counters if data was actually stored
- Import without speed, requires computing speed
- BLE running sensor not connected, should not be reported as present
- BLE running sensor: show data while recording
- Activity type icon was not updated while changing activity
Developer:
- SharedPrefences via static reference
- Tests are executed against in-memory database
- TrackRecordingManager keeps last TrackPoint in memory
@@ -0,0 +1,11 @@
v3.22.0: OpenTracks
Changes:
- Locations are only stored if traveled distance > recording distance interval
(removed left over functionality from idle/non-idle detection)
- UI can be configured to show a clock
- Idle speed is now configurable
Bugfix:
- Intervals: crash when gain but not loss was stored
- While recording: prevent crash when disabling voice announcements
@@ -0,0 +1,10 @@
v3.23.0: OpenTracks
Changes:
- Added UI profiles
- Aggregated statistics for selected tracks
- Re-implemented idle/non-idle TrackPoints as moving time stopped working (introduced in v3.22.0)
Bugfixes:
- BLE sensors: clear data on disconnect
- Automatic export needs to persist permission
@@ -0,0 +1,17 @@
v3.24.0: OpenTracks
Changes:
- GPS is optional (also for indoor use): if using a BLE sensors that provide distance are used
- Settings: option to disable BLE filtering of devices
- Charts: altitude starts at minimal value instead of 0m.
- UI supports showing stored timezone information
Bugfix:
- Re-added workaround for crash due to clock traveling back in time (workaround was removed in v3.23.0)
- Removed unecessary workaround for BLE cycling sensor (RTFM)
- GPX importer did not handle missing data properly
Developer:
- BLE sensors report data event-driven than pull-based
- Reworked and added integration tests for recording
@@ -0,0 +1,16 @@
v3.24.1: OpenTracks
Changes:
- Added API for starting recordings from other applications (disabled by default)
- Shortened ISO8601 track names
- BLE distance sensors: store GPS-locations based upon previous GPS-locations
Bugfix:
- While recording: fixed crash when opening charts
- TrackDataHub should end processing after stop()
- Prevent rare crash while deleting tracks
Developer:
- Refactored package structure
- Monotonic clock
- Value objects for heartrate, cadence, and power
@@ -0,0 +1,11 @@
v3.25.0: OpenTracks
Changes:
- GPS is now optional incl. GPS disabled warning
- PublicAPI: other applications can now receive recorded data if they start a recording (implemented for Gadgetbridge)
- New activities: swimming, swimming open, and workout
- Removed share pic functionality
Bugfix:
- UI: speed/elevation where jumping between 0.0 and the actual value
- Tracks with emojis can be exported
@@ -1,11 +1,22 @@
OpenTracks is a sport tracking application that completely respects your privacy.
A sport tracking buddy that respects your privacy.
Features:
* Sport tracking: track your sport and outdoor activities
* Photos and markers: mark interesting locations while tracking
* Altitude gain and loss: via barometric sensor
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84
* Voice announcements
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling)
* Export data: export tracks either as KMZ (incl. photos), KML, or GPX
* No Internet access: Internet is not used
Sport and outdoor activities grants happiness, skill and confidence.
Value your health by keeping track on your training.
Records as you go running or walking, and gives you a bike computer with a bigger screen for cycling.
Mark interesting locations along your way with pictures.
Keep recorded statistics in great detail for analysis.
Share only the data you want others to have.
* Voice announcements.
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling).
* Altitude gain and loss: via barometric sensor.
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84.
* Export data as tracks either as KMZ (incl. photos), KML, or GPX.
* No Internet access or extra permissions.
* Dark and light theme, respecting system settings.
* No ads.
Libre software / Free Software / Open Source
meaning you can use, study, change and share the source code.
Licensed Apache 2.0
@@ -1 +1 @@
A sport tracker that completely respects your privacy.
A sport tracker buddy that respects your privacy.
@@ -1,6 +1,6 @@
OpenTracks estas aplikaĵo por registri vian sportan aktivecon, kiu plene respektas vian privatecon.
Eblaĵoj:
Ebloj:
• registri vian sportan kaj alian aktivecon eksterdome
• fotoj kaj markoj: marki interesajn lokojn dum registri aktivecon
• registri ŝanĝon de altitudo uzante barometron
@@ -0,0 +1,8 @@
v3.19.1: OpenTracks
Cambios:
- Traducciones actualizadas.
Corrección de errores:
- Corregido el error de exportar todas las pistas.
- Corregido el error de reanudar la pista.
@@ -0,0 +1,7 @@
v3.20.0: OpenTracks
Cambios:
- Durante la grabación: la interfaz de usuario de las estadísticas es ahora configurable
Corrección de errores:
- Se ignoraba el cambio de nombre de pista/categoría durante la grabación (introducido en la v3.19.0)
@@ -0,0 +1,6 @@
v3.20.1: OpenTracks
Cambios:
- Los anuncios ahora se pueden utilizar tanto por distancia como por tiempo
- Los anuncios emiten un pitido si no se ha instalado un software de texto a voz
- Se ha eliminado la frecuencia de localización GPS adaptativa
@@ -0,0 +1,10 @@
v3.23.0: OpenTracks
Cambios:
- Añadido perfiles UI
- Estadísticas agregadas para las pistas seleccionadas
- Reimplementación de TrackPoints inactivos/no inactivos ya que el tiempo de movimiento dejó de funcionar (introducido en la v3.22.0)
Corrección de errores:
- Sensores BLE: borrado de datos al desconectarse
- La exportación automática necesita persistir el permiso
@@ -1,11 +1,22 @@
OpenTracks on spordi jälgimise rakendus, mis austab täielikult teie privaatsust.
Spordijälgija, mis austab teie privaatsust.
Omadused:
* Spordi jälgimine: jälgige oma spordi- ja vabaõhutegevust.
* Fotod ja markerid: märkige jälgimise ajal huvipakkuvad kohad.
* Kõrguse suurenemine ja vähenemine: baromeetrilise anduri abil
* Kõrgus näitab EGM2008 (üle keskmise merepinna); eksporditakse WGS84 kujul.
* Häälteated
* Toetab Bluetooth LE andureid: südame löögisagedus, kiirus ja distants (jalgrattasõit), takistus (jalgrattasõit) ja jõumõõtja (jalgrattasõit).
* Andmete eksport: eksportida rajad kas KMZ (sh fotod), KML või GPX kujul.
* Interneti-ühendus puudub: Internetti ei kasutata
Sport ja välitegevused garanteerivad õnnelikkuse, vilumuse ja kindlustunde.
Väärtusta oma tervist, jälgides on treeninguid.
Salvestab kui lähed jooksma või jalutama ja annab teile jalgrattasõiduks suurema ekraani.
Märgi huvitavad asukohad oma teel, koos piltidega.
Hoia salvestatud statistika analüüsimiseks.
Jaga ainult neid andmeid, mida soovid, et teised näeksid.
* Häälteated.
* Toetab Bluetooth LE andureid: pulss, kiirus, distants (jalgrattasõit), rütm (jalgrattasõit) ja jõumõõtja (jalgrattasõit).
* Kõrguse tõus ja langus: baromeetrilise anduri abil.
* Kõrgus näitab EGM2008 (üle keskmise merepinna); eksporditakse WGS84-na.
* Ekspordit teekond kas KMZ- (koos fotodega), KML- või GPX-vormingus.
* Ei vaja internetile ligipääsu ega lisalubasid.
* Tume ja hele teema, austab süsteemi sätteid.
* Reklaamivaba.
Vabavara / Avatud Lähtekoodiga Tarkvara
mis tähendab, et võite lähtekoodi kasutada, uurida, muuta ja jagada.
Litsentsitud Apache 2.0
@@ -1 +1 @@
Spordijälgija, mis austab täielikult teie privaatsust.
Spordijälgija, mis austab teie privaatsust.
@@ -0,0 +1,8 @@
v3.19.1 : OpenTracks
Changements :
- Traductions mises à jour.
Correction de bogue :
- Correction du bug de l'exportation de toutes les pistes.
- Correction du bogue "Reprendre une piste".
@@ -0,0 +1,7 @@
v3.20.0 : OpenTracks
Changements :
- Pendant l'enregistrement : l'interface des statistiques est maintenant configurable
Correction d'un bug :
- Le changement de nom de piste/catégorie pendant l'enregistrement était ignoré (introduit dans la v3.19.0)
@@ -0,0 +1,6 @@
v3.20.1 : OpenTracks
Changements :
- Les annonces peuvent maintenant être utilisées à la fois par distance et par temps.
- Les annonces émettent un bip si aucun logiciel de synthèse vocale n'est installé.
- Suppression de la fréquence de localisation GPS adaptative
@@ -1,4 +1,4 @@
OpenTracks er en sportsporingsapp som respekterer ditt personvern.
OpenTracks er en app for opptak av sportsaktivitet som respekterer ditt personvern.
Funksjoner:
* Sportsporing: Registrer dine sports- og utendørsaktiviteter
@@ -1 +1 @@
En sportsregistrerer som respekterer ditt personvern.
En opptaker av sportsaktivitet som respekterer ditt personvern.
@@ -0,0 +1,16 @@
v3.18.6: OpenTracks
Zmiany:
- "Pokaż na mapie"- Funkcjonalość wraz z KML zamiast KMZ
- Komunikaty głosowe nie podają nazwy aplikacji
- Podczas śledzenia: Zmiana wysokości aktualizowana co sekundę
- Przetłumaczone opisy w sklepach aplikacji
Naprawione błędy:
- Naprawiono nieprawidłowe tłumaczenia wiadomości głosowych
Developer:
- Uprawnienia Android12 (BLUETOOTH_SCAN)
- Uprawnienia Android12 (ACCESS_COARSE_LOCATION)
- Zastosowanie TrackPoints (testowane)
- Usunięcie niepotrzebnych tłumaczeń
@@ -0,0 +1,11 @@
v3.22.0: OpenTracks
Zmiany:
- Lokalizacja zapisywana tylko wtedy gdy pokonany dystans jest większy od minimalnego przedziału śledzenia dystansu
(usunięto funkcjonalność z wykrywania bezczynności)
- UI może zostać skonfigurowane do wyświetlania zegara
- Prędkość ruchu bez oporu (prędkość jałowa) jest teraz konfigurowalna
Poprawki:
- Przedziały: awaria, gdy zapisano zwiększenie wysokości, ale nie spadek
- Podczas nagrywania: zapobieganie awariom podczas wyłączania komunikatów głosowych
@@ -3,9 +3,9 @@ OpenTracks jest aplikacją do śledzenia sportu, która całkowicie szanuje Twoj
Cechy:
* Śledzenie sportu: śledź swoją aktywność sportową i outdoorową.
* Zdjęcia i markery: zaznacz interesujące lokalizacje podczas śledzenia.
* Przyrost i utrata wysokości: poprzez czujnik barometryczny
* Zmiana wysokości: poprzez czujnik barometryczny
* Wysokość pokazana w EGM2008 (powyżej średniego poziomu morza); wyeksportowana jako WGS84
* Komunikaty głosowe
* Obsługa czujników Bluetooth LE: tętno, prędkość i dystans (kolarstwo), kadencja (kolarstwo) i miernik mocy (kolarstwo)
* Eksport danych: eksport tras jako KMZ (w tym zdjęcia), KML lub GPX
* Brak dostępu do Internetu: Internet nie jest używany
* Dostępu do Internetu: Internet nie jest używany
+1 -1
View File
@@ -1,6 +1,6 @@
#Wed Jul 28 23:10:13 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
+2
View File
@@ -6,6 +6,8 @@
<issue id="ExtraTranslation" severity="error" />
<issue id="GifUsage" severity="error" />
<issue id="InconsistentArrays" severity="error" />
<issue id="MissingQuantity"
severity="warning" />
<issue id="MissingTranslation" severity="warning" />
<issue id="Proguard" severity="warning" />
<issue id="ScrollViewCount" severity="error" />
@@ -1,6 +1,19 @@
package de.dennisguse.opentracks;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
@@ -35,19 +48,6 @@ import org.junit.runner.RunWith;
import de.dennisguse.opentracks.services.TrackDeleteService;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoDeleteTrackTest {
@@ -236,7 +236,6 @@ public class EspressoDeleteTrackTest {
@Override
public void describeTo(Description description) {
}
}));
@@ -29,10 +29,10 @@ import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.espresso.util.HumanReadables;
import androidx.test.espresso.util.TreeIterables;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -49,13 +49,10 @@ import java.util.concurrent.TimeoutException;
public class EspressoEditTrackRecordingTest {
@Rule
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule =
GrantPermissionRule.grant(
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION");
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@LargeTest
@Test
@@ -1,22 +1,15 @@
package de.dennisguse.opentracks;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class PreferenceMetricUnitRule implements TestRule {
private final Context context;
private final boolean metricUnits;
public PreferenceMetricUnitRule(@NonNull Context context, boolean metricUnits) {
this.context = context;
public PreferenceMetricUnitRule(boolean metricUnits) {
this.metricUnits = metricUnits;
}
@@ -25,14 +18,13 @@ public class PreferenceMetricUnitRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits();
try {
PreferencesUtils.setMetricUnits(sharedPreferences, context, metricUnits);
PreferencesUtils.setMetricUnits(metricUnits);
base.evaluate();
} finally {
PreferencesUtils.setMetricUnits(sharedPreferences, context, previousMetricUnits);
PreferencesUtils.setMetricUnits(previousMetricUnits);
}
}
};
@@ -0,0 +1,9 @@
package de.dennisguse.opentracks;
public class TestApplication extends Startup {
@Override
public String getDatabaseName() {
// null will make SQLiteOpenHelper create an in-memory database
return null;
}
}
@@ -0,0 +1,13 @@
package de.dennisguse.opentracks;
import android.app.Application;
import android.content.Context;
import androidx.test.runner.AndroidJUnitRunner;
public class TestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.newApplication(cl, TestApplication.class.getName(), context);
}
}
@@ -1,5 +1,8 @@
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;
@@ -7,15 +10,12 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.fragments.TrackStubUtils;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(AndroidJUnit4.class)
public class ChartPointTest {
@@ -26,7 +26,7 @@ public class ChartPointTest {
statistics.setTotalTime(Duration.ofSeconds(1000));
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, false, false);
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), false, false);
// then
assertEquals(1000000, (long) point.getTimeOrDistance());
@@ -39,7 +39,7 @@ public class ChartPointTest {
statistics.setTotalDistance(Distance.of(1000));
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, true, true);
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), true, true);
// then
assertEquals(1, (long) point.getTimeOrDistance());
@@ -51,7 +51,7 @@ public class ChartPointTest {
TrackStatistics statistics = new TrackStatistics();
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, true);
// then
assertEquals(50, point.getAltitude(), 0.01);
@@ -65,7 +65,7 @@ public class ChartPointTest {
TrackStatistics statistics = new TrackStatistics();
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, true);
// then
assertNull(point.getHeartRate());
@@ -77,14 +77,14 @@ public class ChartPointTest {
public void create_sensorAvailable() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setHeartRate_bpm(100f);
trackPoint.setCyclingCadence_rpm(101f);
trackPoint.setHeartRate(100f);
trackPoint.setCadence(101f);
trackPoint.setPower(102f);
TrackStatistics statistics = new TrackStatistics();
// when
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), 50, false, true);
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), Altitude.EGM2008.of(50), false, true);
// then
assertEquals(100.0, point.getHeartRate(), 0.01);
@@ -14,20 +14,20 @@
* the License.
*/
package de.dennisguse.opentracks.fragments;
package de.dennisguse.opentracks.chart;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.data.models.Distance;
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
*/
public class TrackStubUtils {
class TrackStubUtils {
public static final double INITIAL_ALTITUDE = 22;
public static final long INITIAL_TIME = 1000L;
@@ -11,10 +11,16 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.util.FileUtils;
public class TestDataUtil {
@@ -119,8 +125,8 @@ public class TestDataUtil {
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
trackPoint.setHeartRate_bpm(100f + i);
trackPoint.setCyclingCadence_rpm(300f + i);
trackPoint.setHeartRate(100f + i);
trackPoint.setCadence(300f + i);
trackPoint.setPower(400f + i);
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
trackPoint.setAltitudeLoss(ALTITUDE_LOSS);
@@ -145,7 +151,7 @@ public class TestDataUtil {
}
public static Marker createMarkerWithPhoto(Context context, Track.Id trackId, TrackPoint trackPoint) throws IOException {
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
File dstFile = new File(MarkerUtils.getImageUrl(context, trackId));
dstFile.createNewFile();
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
String photoUrl = photoUri.toString();
@@ -177,7 +183,7 @@ public class TestDataUtil {
for (int i = 0; i < numberOfPoints; i++) {
TrackPoint tp = TestDataUtil.createTrackPoint(i);
contentProviderUtils.insertTrackPoint(tp, dummyTrack.getId());
trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0));
trackStatisticsUpdater.addTrackPoint(tp);
}
dummyTrack.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(dummyTrack);
@@ -5,6 +5,10 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
public class TestSensorDataUtil {
private final List<TrackPoint> trackPointList = new ArrayList<>();
@@ -16,9 +20,15 @@ public class TestSensorDataUtil {
int i = trackPointList.size() + 1;
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
tp.setHeartRate_bpm(hr);
tp.setCyclingCadence_rpm(cadence);
tp.setPower(power);
if (hr != null) {
tp.setHeartRate(hr);
}
if (cadence != null) {
tp.setCadence(cadence);
}
if (power != null) {
tp.setPower(power);
}
tp.setHorizontalAccuracy(Distance.of(1f));
tp.setAltitude(1f);
tp.setSpeed(Speed.of(5f + (i / 10f)));
@@ -1,152 +0,0 @@
package de.dennisguse.opentracks.content.sensor;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.util.UintUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@RunWith(AndroidJUnit4.class)
public class SensorDataCyclingTest {
@Test
public void compute_cadence_1() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024); // 1s
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 2048); // 2s
// when
current.compute(previous);
// then
assertEquals(60, current.getValue(), 0.01);
}
@Test
public void compute_cadence_2() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 8016);
// when
current.compute(previous);
// then
assertEquals(33.53, current.getValue(), 0.01);
}
@Test
public void compute_cadence_sameCount() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 2048);
// when
current.compute(previous);
// then
assertEquals(0, current.getValue(), 0.01);
}
@Test
public void compute_cadence_sameTime() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 1024);
// when
current.compute(previous);
// then
assertFalse(current.hasValue());
}
@Test
public void compute_cadence_rollOverTime() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, UintUtils.UINT16_MAX - 1024);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 0);
// when
current.compute(previous);
// then
assertEquals(60, current.getValue(), 0.01);
}
@Test
public void compute_cadence_rollOverCount() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous);
// then
assertEquals(60, current.getValue(), 0.01);
}
@Test
public void compute_speed() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
// when
current.compute(previous, Distance.ofMM(2150));
// then
assertEquals(2.15, current.getValue().getDistance().toM(), 0.01);
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
}
@Test
public void compute_speed_rollOverCount() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, Distance.ofMM(2000));
// then
assertEquals(2, current.getValue().getDistance().toM(), 0.01);
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
}
@Test
public void equals_speed_with_no_data() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
@Test
public void equals_cadence_with_no_data() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress");
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
}
@@ -14,7 +14,10 @@
* the License.
*/
package de.dennisguse.opentracks.content.provider;
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;
@@ -25,12 +28,9 @@ import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Test;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns;
/**
* Tests {@link CustomContentProvider}.
@@ -13,7 +13,14 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -42,27 +49,20 @@ import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.TestSensorDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
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.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.UUIDUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
/**
* A unit test for {@link ContentProviderUtils}.
@@ -192,6 +192,19 @@ public class CustomContentProviderUtilsTest {
assertEquals(name, track.getName());
}
private void assertCount(int trackCount, int trackPointCount, int markerCount) {
ContentResolver contentResolver = context.getContentResolver();
try (Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID)) {
assertEquals(trackCount, tracksCursor.getCount());
}
try (Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID)) {
assertEquals(trackPointCount, tracksPointsCursor.getCount());
}
try (Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID)) {
assertEquals(markerCount, markerCursor.getCount());
}
}
/**
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
*/
@@ -204,22 +217,13 @@ public class CustomContentProviderUtilsTest {
Marker marker = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
contentProviderUtils.insertMarker(marker);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(1, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(10, tracksPointsCursor.getCount());
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(1, markerCursor.getCount());
// Delete all.
assertCount(1, 10, 1);
// when
contentProviderUtils.deleteAllTracks(context);
// Check whether all have been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(0, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(0, tracksPointsCursor.getCount());
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(0, markerCursor.getCount());
// then
assertCount(0, 0, 0);
}
/**
@@ -235,28 +239,18 @@ public class CustomContentProviderUtilsTest {
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint);
contentProviderUtils.insertMarker(marker);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(1, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(10, tracksPointsCursor.getCount());
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(1, markerCursor.getCount());
// Check marker has photo and it's in the external storage.
assertCount(1, 10, 1);
assertTrue(marker.hasPhoto());
File dir = FileUtils.getPhotoDir(context, trackId);
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
assertTrue(dir.exists());
// Delete all.
// when
contentProviderUtils.deleteAllTracks(context);
// Check whether all have been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(0, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(0, tracksPointsCursor.getCount());
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(0, markerCursor.getCount());
// then
assertCount(0, 0, 0);
assertFalse(dir.exists());
}
@@ -279,22 +273,13 @@ public class CustomContentProviderUtilsTest {
Marker marker = new Marker(trackId1, contentProviderUtils.getLastValidTrackPoint(trackId2));
contentProviderUtils.insertMarker(marker);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(3, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(30, tracksPointsCursor.getCount());
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(1, markerCursor.getCount());
// Delete one track.
assertCount(3, 30, 1);
// when
contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(2, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(20, tracksPointsCursor.getCount());
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(0, markerCursor.getCount());
// then
assertCount(2, 20, 0);
}
/**
@@ -324,13 +309,7 @@ public class CustomContentProviderUtilsTest {
File dir2 = FileUtils.getPhotoDir(context, trackId2);
// Check.
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(3, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(30, tracksPointsCursor.getCount());
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(2, markerCursor.getCount());
assertCount(3, 30, 2);
assertTrue(marker1.hasPhoto());
assertTrue(dir1.isDirectory());
assertEquals(1, dir1.list().length);
@@ -338,15 +317,12 @@ public class CustomContentProviderUtilsTest {
assertTrue(dir2.isDirectory());
assertEquals(1, dir2.list().length);
assertTrue(dir2.exists());
// Delete one track.
// when
contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(2, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(20, tracksPointsCursor.getCount());
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(1, markerCursor.getCount());
// then
assertCount(2, 20, 1);
assertFalse(dir1.exists());
assertTrue(dir2.exists());
}
@@ -763,11 +739,19 @@ public class CustomContentProviderUtilsTest {
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
// when / then
// when
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, null)) {
// then
assertEquals(20, cursor.getCount());
}
// when
contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId);
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, null)) {
// then
assertEquals(28, cursor.getCount());
}
}
/**
@@ -801,6 +785,7 @@ public class CustomContentProviderUtilsTest {
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);
@@ -810,7 +795,7 @@ public class CustomContentProviderUtilsTest {
assertEquals(latitude, trackPoint.getLatitude(), 0.01);
assertEquals(time, trackPoint.getTime().toEpochMilli());
assertEquals(speed, trackPoint.getSpeed().toMPS(), 0.01);
assertFalse(trackPoint.hasHeartRate());
assertEquals(HeartRate.of(75f), trackPoint.getHeartRate());
}
/**
@@ -824,7 +809,11 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
// when
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, null)) {
// then
assertEquals(11, cursor.getCount());
}
}
@Test
@@ -834,8 +823,8 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
trackPoint.setHeartRate_bpm(1F);
trackPoint.setCyclingCadence_rpm(2F);
trackPoint.setHeartRate(1F);
trackPoint.setCadence(2F);
trackPoint.setPower(3F);
// when
@@ -844,9 +833,9 @@ public class CustomContentProviderUtilsTest {
// then
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrue(trackPoints.get(10).hasHeartRate());
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
assertEquals(trackPoint.getHeartRate(), trackPoints.get(10).getHeartRate());
assertEquals(trackPoint.getCadence(), trackPoints.get(10).getCadence());
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower());
}
/**
@@ -874,10 +863,10 @@ public class CustomContentProviderUtilsTest {
.map(TrackPoint.Id::new).collect(Collectors.toList());
// when
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8));
// then
assertEquals(2, cursor.getCount());
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8))) {
// then
assertEquals(2, cursor.getCount());
}
}
@Test
@@ -934,8 +923,8 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
trackPoint.setPower(null);
trackPoint.setCyclingCadence_rpm(null);
trackPoint.setHeartRate_bpm(null);
trackPoint.setCadence(null);
trackPoint.setHeartRate(null);
trackPointList.add(trackPoint);
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId);
@@ -997,13 +986,13 @@ public class CustomContentProviderUtilsTest {
// then
assertTrue(sensorStatistics.hasHeartRate());
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgHeartRate().getBPM(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate().getBPM(), stats.maxHr, 0f);
assertTrue(sensorStatistics.hasCadence());
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgCadence().getRPM(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence().getRPM(), stats.maxCadence, 0f);
assertTrue(sensorStatistics.hasPower());
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
assertEquals(sensorStatistics.getAvgPower().getW(), stats.avgPower, 0f);
}
@Test
@@ -1029,8 +1018,8 @@ public class CustomContentProviderUtilsTest {
// then
assertTrue(sensorStatistics.hasHeartRate());
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgHeartRate().getBPM(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate().getBPM(), stats.maxHr, 0f);
assertFalse(sensorStatistics.hasCadence());
assertFalse(sensorStatistics.hasPower());
}
@@ -1059,8 +1048,8 @@ public class CustomContentProviderUtilsTest {
// then
assertFalse(sensorStatistics.hasHeartRate());
assertTrue(sensorStatistics.hasCadence());
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgCadence().getRPM(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence().getRPM(), stats.maxCadence, 0f);
assertFalse(sensorStatistics.hasPower());
}
@@ -1089,7 +1078,7 @@ public class CustomContentProviderUtilsTest {
assertFalse(sensorStatistics.hasHeartRate());
assertFalse(sensorStatistics.hasCadence());
assertTrue(sensorStatistics.hasPower());
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
assertEquals(sensorStatistics.getAvgPower().getW(), stats.avgPower, 0f);
}
@Test
@@ -1122,11 +1111,11 @@ public class CustomContentProviderUtilsTest {
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
// then
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
assertEquals(sensorStatistics.getAvgHeartRate().getBPM(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate().getBPM(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgCadence().getRPM(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence().getRPM(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgPower().getW(), stats.avgPower, 0f);
}
@Test
@@ -1167,11 +1156,11 @@ public class CustomContentProviderUtilsTest {
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
// then
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
assertEquals(sensorStatistics.getAvgHeartRate().getBPM(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate().getBPM(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgCadence().getRPM(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence().getRPM(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgPower().getW(), stats.avgPower, 0f);
}
@Test
@@ -1212,11 +1201,11 @@ public class CustomContentProviderUtilsTest {
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
// then
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0f);
assertEquals(sensorStatistics.getAvgHeartRate().getBPM(), stats.avgHr, 0f);
assertEquals(sensorStatistics.getMaxHeartRate().getBPM(), stats.maxHr, 0f);
assertEquals(sensorStatistics.getAvgCadence().getRPM(), stats.avgCadence, 0f);
assertEquals(sensorStatistics.getMaxCadence().getRPM(), stats.maxCadence, 0f);
assertEquals(sensorStatistics.getAvgPower().getW(), stats.avgPower, 0f);
}
private void testGetSensorStats_randomData(int totalPoints, boolean withStartSegments) {
@@ -1243,11 +1232,11 @@ public class CustomContentProviderUtilsTest {
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
// then
assertEquals(sensorStatistics.getAvgHeartRate(), stats.avgHr, 0.01f);
assertEquals(sensorStatistics.getMaxHeartRate(), stats.maxHr, 0.01f);
assertEquals(sensorStatistics.getAvgCadence(), stats.avgCadence, 0.01f);
assertEquals(sensorStatistics.getMaxCadence(), stats.maxCadence, 0.01f);
assertEquals(sensorStatistics.getAvgPower(), stats.avgPower, 0.01f);
assertEquals(sensorStatistics.getAvgHeartRate().getBPM(), stats.avgHr, 0.01f);
assertEquals(sensorStatistics.getMaxHeartRate().getBPM(), stats.maxHr, 0.01f);
assertEquals(sensorStatistics.getAvgCadence().getRPM(), stats.avgCadence, 0.01f);
assertEquals(sensorStatistics.getMaxCadence().getRPM(), stats.maxCadence, 0.01f);
assertEquals(sensorStatistics.getAvgPower().getW(), stats.avgPower, 0.01f);
}
@Test
@@ -1,4 +1,8 @@
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.database.Cursor;
@@ -20,13 +24,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns;
@RunWith(AndroidJUnit4.class)
public class CustomSQLiteOpenHelperTest {
@@ -1,4 +1,6 @@
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import static org.junit.Assert.assertEquals;
import android.net.Uri;
import android.util.Pair;
@@ -8,11 +10,9 @@ import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
public class ShareContentProviderTest {
@Test
@@ -0,0 +1,205 @@
package de.dennisguse.opentracks.data;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Track;
@RunWith(AndroidJUnit4.class)
public class TrackSelectionTest extends TestCase {
@Test
public void testFilterBuildSelection_empty() {
// given
TrackSelection filter = new TrackSelection();
// when
SelectionData selection = filter.buildSelection();
// Then
assertNull(selection.getSelection());
assertNull(selection.getSelectionArgs());
}
@Test
public void testFilterBuildSelection_onlyOneTrackId() {
// given
Track.Id trackId = new Track.Id(1);
TrackSelection filter = new TrackSelection().addTrackId(trackId);
// when
SelectionData selection = filter.buildSelection();
// Then
assertEquals(selection.getSelection(), "_id IN (?)");
assertEquals(selection.getSelectionArgs().length, 1);
assertEquals(selection.getSelectionArgs()[0], "1");
}
@Test
public void testFilterBuildSelection_severalTracksId() {
// given
Track.Id trackId1 = new Track.Id(1);
Track.Id trackId2 = new Track.Id(2);
Track.Id trackId3 = new Track.Id(3);
TrackSelection filter = new TrackSelection()
.addTrackId(trackId1)
.addTrackId(trackId2)
.addTrackId(trackId3);
// when
SelectionData selection = filter.buildSelection();
// Then
assertEquals(selection.getSelection(), "_id IN (?,?,?)");
assertEquals(selection.getSelectionArgs().length, 3);
assertEquals(selection.getSelectionArgs()[0], "1");
assertEquals(selection.getSelectionArgs()[1], "2");
assertEquals(selection.getSelectionArgs()[2], "3");
}
@Test
public void testFilterBuildSelection_onlyOneCategory() {
// given
TrackSelection filter = new TrackSelection().addCategory("running");
// when
SelectionData selection = filter.buildSelection();
// Then
assertEquals(selection.getSelection(), "category IN (?)");
assertEquals(selection.getSelectionArgs().length, 1);
assertEquals(selection.getSelectionArgs()[0], "running");
}
@Test
public void testFilterBuildSelection_severalCategories() {
// given
TrackSelection filter = new TrackSelection()
.addCategory("running")
.addCategory("road biking")
.addCategory("mountain biking")
.addCategory("trail walking");
// when
SelectionData selection = filter.buildSelection();
// Then
assertEquals(selection.getSelection(), "category IN (?,?,?,?)");
assertEquals(selection.getSelectionArgs().length, 4);
assertEquals(selection.getSelectionArgs()[0], "running");
assertEquals(selection.getSelectionArgs()[1], "road biking");
assertEquals(selection.getSelectionArgs()[2], "mountain biking");
assertEquals(selection.getSelectionArgs()[3], "trail walking");
}
@Test
public void testFilterBuildSelection_onlyDateRange() {
// given
Instant instant = Instant.now();
long oneDay = 24 * 60 * 60 * 1000;
TrackSelection filterWrong1 = new TrackSelection().addDateRange(instant, null);
TrackSelection filterWrong2 = new TrackSelection().addDateRange(null, instant);
TrackSelection filterOk = new TrackSelection().addDateRange(instant, instant.plusMillis(oneDay));
// when
SelectionData selectionWrong1 = filterWrong1.buildSelection();
SelectionData selectionWrong2 = filterWrong2.buildSelection();
SelectionData selectionOk = filterOk.buildSelection();
// Then
assertNull(selectionWrong1.getSelection());
assertNull(selectionWrong1.getSelectionArgs());
assertNull(selectionWrong2.getSelection());
assertNull(selectionWrong2.getSelectionArgs());
assertEquals(selectionOk.getSelection(), "starttime BETWEEN ? AND ?");
assertEquals(selectionOk.getSelectionArgs().length, 2);
assertEquals(selectionOk.getSelectionArgs()[0], Long.toString(instant.toEpochMilli()));
assertEquals(selectionOk.getSelectionArgs()[1], Long.toString(instant.toEpochMilli() + oneDay));
}
@Test
public void testFilterBuildSelection_tracksId_and_categories() {
// given
Track.Id trackId1 = new Track.Id(1);
Track.Id trackId2 = new Track.Id(2);
Track.Id trackId3 = new Track.Id(3);
TrackSelection filter = new TrackSelection()
.addTrackId(trackId1)
.addTrackId(trackId2)
.addTrackId(trackId3)
.addCategory("running")
.addCategory("road biking");
// when
SelectionData selection = filter.buildSelection();
// Then
assertEquals(selection.getSelection(), "_id IN (?,?,?) AND category IN (?,?)");
assertEquals(selection.getSelectionArgs().length, 5);
assertEquals(selection.getSelectionArgs()[0], "1");
assertEquals(selection.getSelectionArgs()[1], "2");
assertEquals(selection.getSelectionArgs()[2], "3");
assertEquals(selection.getSelectionArgs()[3], "running");
assertEquals(selection.getSelectionArgs()[4], "road biking");
}
@Test
public void testFilterBuildSelection_tracksId_and_dateRange() {
// given
Instant instant = Instant.now();
long oneDay = 24 * 60 * 60 * 1000;
Track.Id trackId1 = new Track.Id(1);
Track.Id trackId2 = new Track.Id(2);
Track.Id trackId3 = new Track.Id(3);
TrackSelection filter = new TrackSelection()
.addTrackId(trackId1)
.addTrackId(trackId2)
.addTrackId(trackId3)
.addDateRange(instant, instant.plusMillis(oneDay));
// when
SelectionData selection = filter.buildSelection();
// Then
assertEquals(selection.getSelection(), "_id IN (?,?,?) AND starttime BETWEEN ? AND ?");
assertEquals(selection.getSelectionArgs().length, 5);
assertEquals(selection.getSelectionArgs()[0], "1");
assertEquals(selection.getSelectionArgs()[1], "2");
assertEquals(selection.getSelectionArgs()[2], "3");
assertEquals(selection.getSelectionArgs()[3], Long.toString(instant.toEpochMilli()));
assertEquals(selection.getSelectionArgs()[4], Long.toString(instant.toEpochMilli() + oneDay));
}
@Test
public void testFilterBuildSelection_categories_and_dateRange() {
// given
Instant instant = Instant.now();
long oneDay = 24 * 60 * 60 * 1000;
TrackSelection filter = new TrackSelection()
.addCategory("running")
.addCategory("road biking")
.addDateRange(instant, instant.plusMillis(oneDay));
// when
SelectionData selection = filter.buildSelection();
// Then
assertEquals(selection.getSelection(), "category IN (?,?) AND starttime BETWEEN ? AND ?");
assertEquals(selection.getSelectionArgs().length, 4);
assertEquals(selection.getSelectionArgs()[0], "running");
assertEquals(selection.getSelectionArgs()[1], "road biking");
assertEquals(selection.getSelectionArgs()[2], Long.toString(instant.toEpochMilli()));
assertEquals(selection.getSelectionArgs()[3], Long.toString(instant.toEpochMilli() + oneDay));
}
}
@@ -1,4 +1,6 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.data;
import static org.junit.Assert.assertEquals;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -7,8 +9,6 @@ import org.junit.runner.RunWith;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class UUIDUtilsTest {
@@ -1,13 +1,16 @@
package de.dennisguse.opentracks.content.data;
package de.dennisguse.opentracks.data.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
public class TrackPointTest {
@@ -1,5 +1,7 @@
package de.dennisguse.opentracks.io.file.exporter;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -10,12 +12,11 @@ import org.junit.runners.JUnit4;
import java.io.ByteArrayOutputStream;
import java.time.Instant;
import java.time.ZoneOffset;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
@RunWith(JUnit4.class)
public class KmlTrackExporterTest {
@@ -27,6 +28,8 @@ public class KmlTrackExporterTest {
@Test
public void writeCloseSegment_only_write_sensordata_if_present() {
String expected = "<when>1970-01-01T00:00:00Z</when>\n" +
"<gx:coord/>\n" +
"<when>1970-01-01T01:00:00+01:00</when>\n" +
"<gx:coord/>\n" +
"<ExtendedData>\n" +
"<SchemaData schemaUrl=\"#schema\">\n" +
@@ -41,7 +44,8 @@ public class KmlTrackExporterTest {
KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
kmlTrackWriter.prepare(outputStream);
kmlTrackWriter.writeTrackPoint(trackPoint);
kmlTrackWriter.writeTrackPoint(ZoneOffset.UTC, trackPoint);
kmlTrackWriter.writeTrackPoint(ZoneOffset.ofTotalSeconds(3600), trackPoint);
// when
kmlTrackWriter.writeCloseSegment();
@@ -30,34 +30,35 @@ import org.mockito.Mockito;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Cadence;
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.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceTestUtils;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
@@ -98,55 +99,68 @@ public class ExportImportTest {
private TrackImporter trackImporter;
@Before
public void fileSetup() throws IOException {
public void fileSetup() throws IOException, TimeoutException {
TrackRecordingServiceTestUtils.resetService(mServiceRule, context);
tmpFile = File.createTempFile("test", "test", context.getFilesDir());
tmpFileUri = Uri.fromFile(tmpFile);
trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(10), Distance.of(200), true);
trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(200), true);
TrackRecordingServiceTestUtils.resetService(mServiceRule, context);
}
@After
public void FileTearDown() throws TimeoutException {
public void tearDown() throws TimeoutException {
tmpFile.deleteOnExit();
tmpFileUri = null;
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
service.getTrackPointCreator().setClock(Clock.systemUTC());
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
TrackRecordingServiceTestUtils.resetService(mServiceRule, context);
}
public void setUp(boolean hasSensorDistance) throws TimeoutException {
public void setUp() throws TimeoutException {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackPointCreator.setClock("2020-02-02T02:02:02Z");
trackId = service.startNewTrack();
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
Distance sensorDistance = Distance.of(10); // recording distance interval
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 15, 10, 1);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:04Z"), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
// A sensor-only TrackPoint
trackPointCreator.setClock("2020-02-02T02:02:04Z");
mockAltitudeChange(trackPointCreator, 1);
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
trackPointCreator.setClock("2020-02-02T02:02:05Z");
mockBLESensorData(trackPointCreator, 5f, Distance.of(2), 66f, 3f, 50f); // Distance will be added to next TrackPoint
sendLocation(trackPointCreator, "2020-02-02T02:02:05Z", 3, 14.001, 10, 15, 10, 0);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET")));
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
trackPointCreator.setClock("2020-02-02T02:02:06Z");
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator));
service.pauseCurrentTrack();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET")));
trackPointCreator.setClock("2020-02-02T02:02:20Z");
service.resumeCurrentTrack();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:21Z"), ZoneId.of("CET")));
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, "2020-02-02T02:02:21Z", 3, 14.002, 10, 15, 10, 0);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
sendLocation(trackPointCreator, "2020-02-02T02:02:22Z", 3, 16, 10, 15, 10, 0);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET")));
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
sendLocation(trackPointCreator, "2020-02-02T02:02:23Z", 3, 16.001, 10, 15, 10, 0);
trackPointCreator.setClock("2020-02-02T02:02:24Z");
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator));
service.endCurrentTrack();
Track track = contentProviderUtils.getTrack(trackId);
@@ -158,25 +172,15 @@ public class ExportImportTest {
track = contentProviderUtils.getTrack(trackId);
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
markers = contentProviderUtils.getMarkers(trackId);
assertEquals(10, trackPoints.size());
assertEquals(11, trackPoints.size());
assertEquals(2, markers.size());
}
@After
public void tearDown() {
if (trackId != null) {
contentProviderUtils.deleteTrack(context, trackId);
}
if (importTrackId != null) {
contentProviderUtils.deleteTrack(context, importTrackId);
}
}
//TODO Does not test images
@LargeTest
@Test
public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException {
setUp(true);
setUp();
// given
Track track = contentProviderUtils.getTrack(trackId);
@@ -204,32 +208,51 @@ public class ExportImportTest {
// 2. trackpoints
TrackPointAssert a = new TrackPointAssert()
.noAccuracy();
a.assertEquals(trackPoints, TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId));
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
a.assertEquals(trackPoints, actual);
// 3. trackstatistics
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:24Z"), importedTrackStatistics.getStopTime());
assertEquals(track.getTrackStatistics().getTotalTime(), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(8), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
TrackStatistics originalTrackStatistics = track.getTrackStatistics();
assertEquals(originalTrackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(7), importedTrackStatistics.getTotalTime());
assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance());
assertEquals(123.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
assertEquals(Speed.of(3.75), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed());
assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed());
assertEquals(17.59, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
// Altitude
assertEquals(originalTrackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(originalTrackStatistics.getMaxAltitude(), importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
assertEquals(originalTrackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(originalTrackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
// 4. markers
assertMarkers();
@@ -238,7 +261,7 @@ public class ExportImportTest {
@LargeTest
@Test(expected = ImportAlreadyExistsException.class)
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException, IOException {
setUp(false);
setUp();
// given
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
@@ -264,7 +287,7 @@ public class ExportImportTest {
@LargeTest
@Test
public void gpx() throws TimeoutException, IOException {
setUp(true);
setUp();
// given
Track track = contentProviderUtils.getTrack(trackId);
@@ -295,39 +318,78 @@ public class ExportImportTest {
// 2. trackpoints
// The GPX exporter does not support exporting TrackPoints without lat/lng.
// Therefore, the track segmentation is changes.
List<TrackPoint> trackPointsWithCoordinates = trackPoints.stream().filter(it -> TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(it.getType()) || TrackPoint.Type.TRACKPOINT.equals(it.getType())).collect(Collectors.toList());
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
TrackPointAssert a = new TrackPointAssert()
.setDelta(0.05)
.noAccuracy(); // speed is not fully
a.assertEquals(trackPointsWithCoordinates, TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId));
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:03Z"))
.setLatitude(3)
.setLongitude(14)
.setAltitude(10)
.setSpeed(Speed.of(15))
.setAltitudeLoss(1f)
.setAltitudeGain(1f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z"))
.setLatitude(3)
.setLongitude(14.001)
.setAltitude(10)
.setSpeed(Speed.of(5))
.setAltitudeLoss(1f)
.setAltitudeGain(1f)
.setSensorDistance(Distance.of(12))
.setHeartRate(66f)
.setPower(50f)
.setCadence(3f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:21Z"))
.setLatitude(3)
.setLongitude(14.002)
.setAltitude(10)
.setAltitudeLoss(0f)
.setAltitudeGain(0f)
.setSpeed(Speed.of(15)),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:22Z"))
.setLatitude(3)
.setLongitude(16)
.setAltitude(10)
.setAltitudeLoss(0f)
.setAltitudeGain(0f)
.setSpeed(Speed.of(15)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:23Z"))
.setLatitude(3)
.setLongitude(16.001)
.setAltitude(10)
.setAltitudeLoss(0f)
.setAltitudeGain(0f)
.setSpeed(Speed.of(15))
), actual);
// 3. trackstatistics
TrackStatistics trackStatistics = track.getTrackStatistics();
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(19), importedTrackStatistics.getMovingTime());
assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
assertEquals(123.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
assertEquals(Speed.of(1.5), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(1.5789473684210527), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(41.05, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(41.05, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
// 4. markers
assertMarkers();
@@ -336,7 +398,7 @@ public class ExportImportTest {
@LargeTest
@Test(expected = ImportAlreadyExistsException.class)
public void gpx_duplicate_trackUUID() throws TimeoutException, IOException {
setUp(false);
setUp();
// given
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
@@ -380,7 +442,43 @@ public class ExportImportTest {
}
}
private void sendLocation(TrackPointCreator trackPointCreator, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator) {
@Override
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
SensorDataSet sensorDataSet = new SensorDataSet();
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
SensorDataCycling.CyclingCadence cyclingCadence = Mockito.mock(SensorDataCycling.CyclingCadence.class);
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
sensorDataSet.set(cyclingCadence);
if (distance != null && speed != null) {
SensorDataCycling.DistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCycling.DistanceSpeed.Data.class);
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
SensorDataCycling.DistanceSpeed distanceSpeed = Mockito.mock(SensorDataCycling.DistanceSpeed.class);
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
sensorDataSet.set(distanceSpeed);
}
sensorDataSet.fillTrackPoint(trackPoint);
return sensorDataSet;
}
});
trackPointCreator.onChange(null);
}
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager();
altitudeSumManager.setAltitudeGain_m(altitudeGain);
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
}
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain) {
Location location = new Location("mock");
location.setLatitude(latitude);
location.setLongitude(longitude);
@@ -388,41 +486,9 @@ public class ExportImportTest {
location.setSpeed(speed);
location.setAltitude(altitude);
trackPointCreator.setAltitudeSumManager(new AltitudeSumManager() {
@Override
public void fill(@NonNull TrackPoint trackPoint) {
trackPoint.setAltitudeGain(altitudeGain);
trackPoint.setAltitudeLoss(altitudeGain);
}
});
mockAltitudeChange(trackPointCreator, altitudeGain);
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
@Override
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
SensorDataSet sensorDataSet = new SensorDataSet();
sensorDataSet.set(new SensorDataCyclingPower("power", "power", power));
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", heartRate));
SensorDataCycling.Cadence cadence = Mockito.mock(SensorDataCycling.Cadence.class);
Mockito.when(cadence.hasValue()).thenReturn(true);
Mockito.when(cadence.getValue()).thenReturn(cyclingCadence);
sensorDataSet.set(cadence);
SensorDataCycling.DistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCycling.DistanceSpeed.Data.class);
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
SensorDataCycling.DistanceSpeed distanceSpeed = Mockito.mock(SensorDataCycling.DistanceSpeed.class);
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
sensorDataSet.set(distanceSpeed);
sensorDataSet.fillTrackPoint(trackPoint);
return sensorDataSet;
}
});
trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET")));
trackPointCreator.getLocationHandler().onLocationChanged(location);
trackPointCreator.setClock(time);
trackPointCreator.getGpsHandler().onLocationChanged(location);
}
}
@@ -1,5 +1,8 @@
package de.dennisguse.opentracks.io.file.importer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -14,18 +17,17 @@ import org.junit.runners.JUnit4;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Test that legacy KML/GPX formats can still be imported.
@@ -42,7 +44,7 @@ public class GPXImportTest {
@Before
public void setUp() {
trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(10), Distance.of(200), true);
trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(200), true);
}
@After
@@ -67,7 +69,7 @@ public class GPXImportTest {
importTrackId = importer.importFile(inputStream).get(0);
// then
// 1. track
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("the category", importedTrack.getCategory());
@@ -75,6 +77,8 @@ public class GPXImportTest {
assertEquals("2021-01-07 22:51", importedTrack.getName());
assertEquals("UNKNOWN", importedTrack.getIcon());
//TODO Check trackstatistics
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
@@ -106,4 +110,102 @@ public class GPXImportTest {
.setAltitude(10)
), importedTrackPoints);
}
@LargeTest
@Test
public void gpx_without_speed() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_without_speed);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getCategory());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals("", importedTrack.getIcon());
// 3. trackstatistics
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
assertEquals(1.44, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(53), trackStatistics.getMovingTime());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(3, importedTrackPoints.size());
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
.setLatitude(30.14184657)
.setLongitude(-40.38670089)
.setAltitude(-5)
.setSpeed(Speed.of(0.7976524233818054)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(0.7224021553993225))
), importedTrackPoints);
}
@LargeTest
@Test
public void gpx_speed_no_namespace() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_with_speed_no_namespace);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getCategory());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals("", importedTrack.getIcon());
// 3. trackstatistics
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
assertEquals(4, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(3, importedTrackPoints.size());
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(5)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
.setLatitude(30.14184657)
.setLongitude(-40.38670089)
.setAltitude(-5)
.setSpeed(Speed.of(0.7976524233818054))
.setSpeed(Speed.of(4)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(3))
), importedTrackPoints);
}
}
@@ -1,5 +1,8 @@
package de.dennisguse.opentracks.io.file.importer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -17,15 +20,12 @@ import java.io.InputStream;
import java.time.Instant;
import java.util.List;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
/**
* Test that legacy KML/GPX formats can still be imported.
@@ -42,7 +42,7 @@ public class KMLImportTest {
@Before
public void setUp() {
trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(10), Distance.of(200), true);
trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(200), true);
}
@After
@@ -4,11 +4,10 @@ import org.junit.Assert;
import java.util.List;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.data.models.TrackPoint;
public class TrackPointAssert {
private boolean assertTime = true;
private boolean assertAccuracy = true;
private double delta = 0.001;
@@ -17,10 +16,7 @@ public class TrackPointAssert {
}
public void assertEquals(TrackPoint expected, TrackPoint actual) {
Assert.assertNotNull(actual.getTime());
if (assertTime) {
Assert.assertEquals(expected.getTime(), actual.getTime());
}
Assert.assertEquals(expected.getTime(), actual.getTime());
Assert.assertEquals(expected.getType(), actual.getType());
@@ -65,17 +61,17 @@ public class TrackPointAssert {
Assert.assertEquals(expected.hasHeartRate(), actual.hasHeartRate());
if (expected.hasHeartRate()) {
Assert.assertEquals(expected.getHeartRate_bpm(), actual.getHeartRate_bpm(), delta);
Assert.assertEquals(expected.getHeartRate(), actual.getHeartRate());
}
Assert.assertEquals(expected.hasPower(), actual.hasPower());
if (expected.hasPower()) {
Assert.assertEquals(expected.getPower(), actual.getPower(), delta);
Assert.assertEquals(expected.getPower(), actual.getPower());
}
Assert.assertEquals(expected.hasCyclingCadence(), actual.hasCyclingCadence());
if (expected.hasCyclingCadence()) {
Assert.assertEquals(expected.getCyclingCadence_rpm(), actual.getCyclingCadence_rpm(), delta);
Assert.assertEquals(expected.hasCadence(), actual.hasCadence());
if (expected.hasCadence()) {
Assert.assertEquals(expected.getCadence(), actual.getCadence());
}
}
@@ -83,14 +79,14 @@ public class TrackPointAssert {
try {
Assert.assertEquals(expected.size(), actual.size());
} catch (AssertionError e) {
throw new AssertionError("Expected: " + expected + " actual: " + actual);
throw new AssertionError("Size difference; expected: " + expected.size() + "; actual: " + actual.size() + "\nExpected: " + expected + "\n actual: " + actual);
}
for (int i = 0; i < expected.size(); i++) {
try {
assertEquals(expected.get(i), actual.get(i));
} catch (AssertionError e) {
throw new AssertionError("Expected: " + expected.get(i) + " actual: " + actual.get(i), e);
throw new AssertionError("Expected: " + i + " to be " + expected.get(i) + "\n actual: " + actual.get(i), e);
}
}
Assert.assertEquals(expected.size(), actual.size());
@@ -100,11 +96,6 @@ public class TrackPointAssert {
}
}
public TrackPointAssert ignoreTime() {
this.assertTime = false;
return this;
}
public TrackPointAssert noAccuracy() {
this.assertAccuracy = false;
return this;
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.services.sensors;
package de.dennisguse.opentracks.sensors;
import org.junit.Assert;
import org.junit.Before;
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.sensors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -7,10 +7,11 @@ import android.bluetooth.BluetoothGattCharacteristic;
import org.junit.Test;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
public class BluetoothUtilsTest {
@@ -43,40 +44,40 @@ public class BluetoothUtilsTest {
@Test
public void parseCyclingSpeedCadence_crankOnly() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
characteristic.setValue(new byte[]{0x01, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
// when
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertNull(sensor.getDistanceSpeed());
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
}
@Test
public void parseCyclingSpeedCadence_wheelOnly() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
characteristic.setValue(new byte[]{0x02, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
// when
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(65535 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertNull(sensor.getCadence());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
}
@Test
public void parseCyclingSpeedCadence_crankWheel() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
// when
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertEquals(200 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertEquals(225, sensor.getCadence().getCrankRevolutionsCount());
}
@Test
@@ -93,15 +94,15 @@ public class BluetoothUtilsTest {
@Test
public void parseRunningSpeedAndCadence_with_distance() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, 0, 0);
characteristic.setValue(new byte[]{2, 0, 5, 80, 12, 1});
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID, 0, 0);
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
// when
SensorDataRunning sensor = BluetoothUtils.parseRunningSpeedAndCadence("address", "sensorName", characteristic);
// then
assertEquals(Speed.of(5), sensor.getSpeed());
assertEquals(80, sensor.getCadence(), 0.01);
assertEquals(Distance.of(26.8), sensor.getTotalDistance());
assertEquals(Cadence.of(80), sensor.getCadence());
assertEquals(Distance.of(6553.5 + 1677721.6), sensor.getTotalDistance());
}
}
@@ -1,10 +1,10 @@
package de.dennisguse.opentracks.util;
import org.junit.Test;
package de.dennisguse.opentracks.sensors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
public class PressureSensorUtilsTest {
// real data at 500Hz stationary
@@ -1,9 +1,9 @@
package de.dennisguse.opentracks.util;
import org.junit.Test;
package de.dennisguse.opentracks.sensors;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class UintUtilsTest {
@Test
@@ -0,0 +1,185 @@
package de.dennisguse.opentracks.sensors.sensorData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.sensors.UintUtils;
@RunWith(AndroidJUnit4.class)
public class SensorDataCyclingTest {
@Test
public void compute_cadence_1() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 1024); // 1s
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 2048); // 2s
// when
current.compute(previous);
// then
assertEquals(60, current.getValue().getRPM(), 0.01);
}
@Test
public void compute_cadence_2() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 8016);
// when
current.compute(previous);
// then
assertEquals(33.53, current.getValue().getRPM(), 0.01);
}
@Test
public void compute_cadence_sameCount() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 1024);
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 2048);
// when
current.compute(previous);
// then
assertEquals(Cadence.of(0), current.getValue());
}
@Test
public void compute_cadence_sameTime() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 1024);
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 1024);
// when
current.compute(previous);
// then
assertFalse(current.hasValue());
}
@Test
public void compute_cadence_rollOverTime() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, UintUtils.UINT16_MAX - 1024);
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 0);
// when
current.compute(previous);
// then
assertEquals(60, current.getValue().getRPM(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_cadence_rollOverCount() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous);
// then
assertEquals(60, current.getValue().getRPM(), 0.01);
}
@Test
public void compute_cadence_overflow() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous);
// then
assertNull(current.getValue());
}
@Test
public void compute_speed() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
// when
current.compute(previous, Distance.ofMM(2150));
// then
assertEquals(2.15, current.getValue().getDistance().toM(), 0.01);
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_speed_rollOverCount() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, Distance.ofMM(2000));
// then
assertEquals(2, current.getValue().getDistance().toM(), 0.01);
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
}
@Test
public void compute_speed_overflow() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, Distance.ofMM(2000));
// then
assertNull(current.getValue());
}
@Test
public void equals_speed_with_no_data() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
@Test
public void equals_cadence_with_no_data() {
// given
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress");
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
}
@@ -1,5 +1,10 @@
package de.dennisguse.opentracks.services;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.NotificationManager;
import android.content.Context;
@@ -11,16 +16,11 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TrackRecordingServiceNotificationManagerTest {
@@ -1,472 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Tests for the track recording service.
*
* @author Bartlomiej Niechwiej
* <p>
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
static Intent createStartIntent(Context context) {
return new Intent(context, TrackRecordingService.class);
}
private final AltitudeSumManager altitudeSumManager = new AltitudeSumManager() {
@Override
public void fill(@NonNull TrackPoint trackPoint) {
trackPoint.setAltitudeGain(0f);
trackPoint.setAltitudeLoss(0f);
}
};
@Before
public void setUp() {
// Set up the mock content resolver
ContentProvider customContentProvider = new CustomContentProvider() {
};
customContentProvider.attachInfo(context, null);
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
sharedPreferences.edit().clear().commit();
// Ensure that the database is empty before every test
contentProviderUtils.deleteAllTracks(context);
}
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
service.getTrackPointCreator().setClock(Clock.systemUTC());
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@SmallTest
@Test
public void testStartable() throws TimeoutException {
mServiceRule.startService(createStartIntent(context));
assertNotNull(mServiceRule.bindService(createStartIntent(context)));
}
@MediumTest
@Test
public void testBindable() throws TimeoutException {
IBinder service = mServiceRule.bindService(createStartIntent(context));
assertNotNull(service);
}
@MediumTest
@Test
public void testRecording_noTracks() throws TimeoutException {
// given
List<Track> tracks = contentProviderUtils.getTracks();
assertTrue(tracks.isEmpty());
// when
Intent startIntent = createStartIntent(context);
mServiceRule.startService(startIntent);
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
.getService();
// then
// Test if we start in no-recording mode by default.
assertFalse(service.isRecording());
}
@MediumTest
@Test
public void testRecording_oldTracks() throws TimeoutException {
// given
createDummyTrack(trackId);
// when
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
// then
assertFalse(service.isRecording());
}
@MediumTest
@Test
public void testRecording_serviceRestart_whileRecording() throws TimeoutException {
// given
createDummyTrack(trackId);
//when
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
service.resumeTrack(trackId);
// then
assertTrue(service.isRecording());
}
@MediumTest
@Test
public void testRecording_stop() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.endCurrentTrack();
// then
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testRecording_pauseAndResume() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.pauseCurrentTrack();
// then
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
//when
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
service.resumeTrack(trackId);
// then
assertTrue(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:04Z"))
), trackPoints);
}
@MediumTest
@Test
public void testRecording_resumeStoppedTrack() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.endCurrentTrack();
// when
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
service.resumeTrack(trackId);
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z")), Distance.of(50));
// then
assertTrue(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:04Z")),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z"))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testRecording_stopPausedTrack() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
service.pauseCurrentTrack();
// when
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testStartNewTrack_alreadyRecording() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
// when
Track.Id newTrackId = service.startNewTrack();
// then
assertNotNull(trackId);
assertNull(newTrackId);
}
@MediumTest
@Test
public void testEndCurrentTrack_noRecording() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
assertFalse(service.isRecording());
// when
// Ending the current track when there is no recording should not result in any error.
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
}
@MediumTest
@Test
public void testInsertWaypointMarker_noRecordingTrack() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
assertFalse(service.isRecording());
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNull(markerId);
}
@MediumTest
@Test
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.stopGPS();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
trackPointCreator.onNewTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
.setLatitude(10)
.setLongitude(10)
, Distance.of(50));
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNotEquals(new Marker.Id(-1L), markerId);
Marker wpt = contentProviderUtils.getMarker(markerId);
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
assertEquals(trackId, wpt.getTrackId());
assertEquals(0.0, wpt.getLength().toM(), 0.01);
assertNotNull(wpt.getLocation());
service.endCurrentTrack();
}
private void addTrack(Track track) {
assertNotNull(track.getId());
contentProviderUtils.insertTrack(track);
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
}
// NOTE: Do not use to create a track that is currently recording.
private void createDummyTrack(Track.Id id) {
Track dummyTrack = new Track();
dummyTrack.setId(id);
dummyTrack.setName("Dummy Track");
TrackStatistics trackStatistics = new TrackStatistics();
Instant now = Instant.now();
trackStatistics.setStartTime(now.minusSeconds(5L));
trackStatistics.setStopTime(now.minusSeconds(1L));
dummyTrack.setTrackStatistics(trackStatistics);
addTrack(dummyTrack);
}
static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed) {
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
}
/**
* Inserts a location and waits for 200ms.
*/
private static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(time))
.setLongitude(longitude)
.setLatitude(latitude)
.setHorizontalAccuracy(Distance.of(accuracy))
.setSpeed(Speed.of(speed))
.setBearing(3.0f);
trackRecordingService.getTrackPointCreator().onNewTrackPoint(trackPoint, Distance.of(50));
}
}
@@ -1,500 +0,0 @@
package de.dennisguse.opentracks.services;
import android.content.ContentProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertFalse;
/**
* Tests insert location.
* <p>
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
//TODO Implement as mock test; no need to store data in database
public class TrackRecordingServiceTestLocation {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingService service;
private final AltitudeSumManager altitudeSumManager = new AltitudeSumManager() {
@Override
public void fill(@NonNull TrackPoint trackPoint) {
trackPoint.setAltitudeGain(0f);
trackPoint.setAltitudeLoss(0f);
}
};
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
@Before
public void setUp() throws TimeoutException {
// Set up the mock content resolver
ContentProvider customContentProvider = new CustomContentProvider() {
};
customContentProvider.attachInfo(context, null);
contentProviderUtils = new ContentProviderUtils(context);
tearDown();
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
service.getTrackPointCreator().stopGPS();
}
@After
public void tearDown() {
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_movingAccurate() {
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 2, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 3, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 4, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 6, 15);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.0001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.0001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(3))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.0002)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(4))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.0003)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(5))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.0004)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(6))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_slowMovingAccurate() {
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000001, 35.0, 2, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 3, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 6, 15);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.0005)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(6))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_idle() {
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 0);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Why is this added? Systems is idle and not moving at all.
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(6))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withMovement() {
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().stopGPS();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 15);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(5))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(6))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withSensorData() {
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
@Override
public boolean isEnabled() {
return true;
}
@Override
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
SensorDataSet sensorDataSet = new SensorDataSet();
sensorDataSet.set(new SensorDataHeartRate("sensorName", "sensorAddress", 5f));
sensorDataSet.fillTrackPoint(trackPoint);
return sensorDataSet;
}
});
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 0);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(3))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(4))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(5))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(6))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f)
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_segment() {
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.1, 35.0, 2, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.1, 35.0, 3, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.2, 35.0, 4, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.2, 35.0, 5, 0);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
.setLatitude(45.1)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.1)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(3))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
.setLatitude(45.2)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(4))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45.2)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(5))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
}
@@ -1,237 +0,0 @@
package de.dennisguse.opentracks.services;
import android.content.ContentProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Tests for the track recording service, which require a {@link Looper}.
*
* @author Bartlomiej Niechwiej
* <p>
* ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestLooper {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
@Before
public void setUp() {
// Set up the mock content resolver
ContentProvider customContentProvider = new CustomContentProvider() {
};
customContentProvider.attachInfo(context, null);
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
sharedPreferences.edit().clear().apply();
// Ensure that the database is empty before every test
contentProviderUtils.deleteAllTracks(context);
}
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_gps_accuracy_key), 500);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.max_recording_distance_key), Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.max_recording_distance_key), 50);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_distance_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistance() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_distance_interval_key), 2);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.min_recording_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingInterval() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.min_recording_interval_key), 2);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_gps_accuracy_key), Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testIntegration_completeRecordingSession() throws TimeoutException {
List<Track> tracks = contentProviderUtils.getTracks();
assertTrue(tracks.isEmpty());
fullRecordingSession();
}
private void fullRecordingSession() throws TimeoutException {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
assertFalse(service.isRecording());
// Start a track.
Track.Id trackId = service.startNewTrack();
assertNotNull(trackId);
assertTrue(service.isRecording());
Track track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
assertEquals(trackId, track.getId());
// Insert a few points, markers and statistics.
for (int i = 0; i < 30; i++) {
//TODO Should send locations to LocationHandler instead of TrackPoints to TrackPointCreator?
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, service.getTrackPointCreator().createNow())
.setLongitude(35.0f + i / 10.0f)
.setLatitude(45.0f - i / 5.0f)
.setHorizontalAccuracy(Distance.of(5))
.setSpeed(Speed.of(10))
.setBearing(3.0f);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
service.getTrackPointCreator().onNewTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) {
service.insertMarker(null, null, null, null);
}
}
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
assertFalse(service.isRecording());
track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
assertEquals(trackId, track.getId());
TrackStatistics trackStatistics = track.getTrackStatistics();
assertNotNull(trackStatistics);
assertTrue(trackStatistics.getStartTime().isAfter(Instant.ofEpochMilli(0)));
assertTrue(trackStatistics.getStopTime().isAfter(trackStatistics.getStartTime()));
}
}
@@ -0,0 +1,158 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestMarker {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingService service;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
private TrackRecordingService startService() throws TimeoutException {
Intent startIntent = new Intent(context, TrackRecordingService.class);
return ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
.getService();
}
@Before
public void setUp() throws TimeoutException {
contentProviderUtils = new ContentProviderUtils(context);
service = startService();
tearDown();
}
@After
public void tearDown() throws TimeoutException {
TrackRecordingServiceTestUtils.resetService(mServiceRule, context);
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@MediumTest
@Test
public void notRecording_testInsertMarker() {
// given
assertFalse(service.isRecording());
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNull(markerId);
}
@MediumTest
@Test
public void recording_noGPSfix_cannotCreateMarker() {
// given
service.startNewTrack();
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNull(markerId);
}
@MediumTest
@Test
public void recording_GPSfix_createsMarker() {
// given
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.stopGPS();
trackPointCreator.setClock("2020-02-02T02:02:02Z");
Track.Id trackId = service.startNewTrack();
service.stopUpdateRecordingData();
assertTrue(service.isRecording());
trackPointCreator.onNewTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
.setLatitude(10)
.setLongitude(10)
);
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNotEquals(new Marker.Id(-1L), markerId);
Marker wpt = contentProviderUtils.getMarker(markerId);
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
assertEquals(trackId, wpt.getTrackId());
assertEquals(0.0, wpt.getLength().toM(), 0.01);
assertNotNull(wpt.getLocation());
service.endCurrentTrack();
}
}
@@ -0,0 +1,820 @@
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Tests insert location.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestRecording {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingService service;
private final AltitudeSumManager altitudeSumManager = new AltitudeSumManager() {
@Override
public void fill(@NonNull TrackPoint trackPoint) {
trackPoint.setAltitudeGain(0f);
trackPoint.setAltitudeLoss(0f);
}
};
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
private TrackRecordingService startService() throws TimeoutException {
Intent startIntent = new Intent(context, TrackRecordingService.class);
return ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
.getService();
}
@Before
public void setUp() throws TimeoutException {
contentProviderUtils = new ContentProviderUtils(context);
tearDown();
PreferencesUtils.setString(R.string.recording_distance_interval_key, R.string.recording_distance_interval_default);
PreferencesUtils.setString(R.string.idle_speed_key, R.string.idle_speed_default);
service = startService();
service.getTrackPointCreator().stopGPS();
}
@After
public void tearDown() throws TimeoutException {
TrackRecordingServiceTestUtils.resetService(mServiceRule, context);
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@MediumTest
@Test
public void recording_startStop() {
// given
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
// when
String startTime = "2020-02-02T02:02:02Z";
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// then
assertEquals(new TrackStatistics(startTime, startTime, 0, 0, 0, 0, null, null)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
// when
String stopTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 0, 1, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_startPauseResume() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String pauseTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(pauseTime);
service.pauseCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, pauseTime, 0, 1, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(pauseTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
//when
String resumeTime = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(resumeTime);
service.resumeTrack(trackId);
// then
assertEquals(new TrackStatistics(startTime, resumeTime, 0, 1, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(pauseTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_startPauseStop() {
// given
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
String starTime = "2020-02-02T02:02:02Z";
trackPointCreator.setClock(starTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
String pauseTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(pauseTime);
service.pauseCurrentTrack();
// when
trackPointCreator.setClock("2020-02-02T02:02:04Z");
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(starTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(pauseTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), trackPoints);
}
@MediumTest
@Test
public void testRecording_startStopResumeStop() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
String stopTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// when
String resumeTime = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(resumeTime);
service.resumeTrack(trackId);
trackPointCreator.stopGPS();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// then
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_recordingDistance_above() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// then
assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0001, 35.0, 1, 15);
// then
assertEquals(new TrackStatistics(startTime, gps2, 11.113178253173828f, 4, 3, 15, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps3 = "2020-02-02T02:02:08Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0002, 35.0, 1, 15);
// then
assertEquals(new TrackStatistics(startTime, gps3, 22.226356506347656, 6, 5, 15, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 22.226356506347656, 10, 5, 15, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps2))
.setLatitude(45.0001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
.setLatitude(45.0001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_recordingDistance_above_speed_0() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 0);
// then
TrackStatistics gps1statistics = new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f);
assertEquals(gps1statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0001, 35.0, 1, 0);
// then
assertEquals(gps1statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps3 = "2020-02-02T02:02:08Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0002, 35.0, 1, 0);
// then
assertEquals(gps1statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
.setLatitude(45.0001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_recordingDistance_below() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// then
TrackStatistics gps1Statistics = new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f);
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.00001, 35.0, 1, 15);
// then
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps3 = "2020-02-02T02:02:08Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.00002, 35.0, 1, 15);
// then
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 2.222635507583618, 10, 5, 15, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
.setLatitude(45.00002)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_recordingDistance_idle() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 0);
// then
TrackStatistics gps1Statistics = new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f);
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 1, 0);
// then
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps3 = "2020-02-02T02:02:08Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0, 35.0, 1, 0);
// then
assertEquals(gps1Statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
.setLatitude(45.00002)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_recordingDistance_idle_movement() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// then
assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 1, 0);
// then
final TrackStatistics gps2statistics = new TrackStatistics(startTime, gps2, 0, 4, 0, 0, 0f, 0f);
assertEquals(gps2statistics
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps3 = "2020-02-02T02:02:08Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0, 35.0, 1, 0);
// then
assertEquals(gps2statistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps4 = "2020-02-02T02:02:10Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps4, 45.0, 35.0, 1, 15);
// then
assertEquals(new TrackStatistics(startTime, gps4, 0, 8, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps2))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps4))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_ignore_inaccurate() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 100, 15);
// then
TrackStatistics startStatistics = new TrackStatistics(startTime, startTime, 0, 0, 0, 0, null, null);
assertEquals(startStatistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.1, 35.0, 100, 15);
// then
assertEquals(startStatistics, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 0, 10, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// then
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_gpsOnly_segment() {
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// then
assertEquals(new TrackStatistics(startTime, gps1, 0, 1, 0, 0, 0f, 0f), contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String gps2 = "2020-02-02T02:02:06Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.1, 35.0, 1, 15);
// then
assertEquals(new TrackStatistics(startTime, gps2, 0, 1, 0, 0, 0f, 0f), contentProviderUtils.getTrack(trackId).getTrackStatistics());
// when
String stopTime = "2020-02-02T02:02:12Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
assertEquals(new TrackStatistics(startTime, stopTime, 0, 7, 0, 0, 0f, 0f)
, contentProviderUtils.getTrack(trackId).getTrackStatistics());
// then
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse(gps2))
.setLatitude(45.1)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
/**
* Make sure that GPS-based TrackPoints are stored, if the distance to the previous GPS-based TrackPoint is greater than recordingDistanceInterval.
*/
@MediumTest
@Test
public void testRecording_gpsAndSensor_gpsIdleMoving_sensorMoving() {
// TODO Check TrackStatistics
AltitudeSumManager altitudeSumManager = new AltitudeSumManager();
// given
String startTime = "2020-02-02T02:02:02Z";
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
// when
String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(0))); //Should be ignored
// when
String sensor2 = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(sensor2);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2)));
// when
String gps1 = "2020-02-02T02:02:05Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
// when
String sensor3 = "2020-02-02T02:02:06Z";
trackPointCreator.setClock(sensor3);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(12)));
// when
String sensor4 = "2020-02-02T02:02:07Z";
trackPointCreator.setClock(sensor4);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(14))); //Should be ignored
// when
String gps2 = "2020-02-02T02:02:08Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 4, 15); //Should be ignored
// when
String sensor5 = "2020-02-02T02:02:10Z";
trackPointCreator.setClock(sensor5);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16))); //Should be ignored
// when
String gps3 = "2020-02-02T02:02:12Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.001, 35.0, 1, 15);
// when
String gps4 = "2020-02-02T02:02:14Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps4, 45.001, 35.0, 1, 15);
// when
String stopTime = "2020-02-02T02:02:16Z";
trackPointCreator.setClock(stopTime);
service.endCurrentTrack();
// then
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor2)) //First moving TrackPoint: store as the time might be interesting.
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(2)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(0)),
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor3))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(10)),
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor5)) //TODO No need to store this TrackPoint, data could be merged into the next one
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(4)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
.setLatitude(45.001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(0)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps4))
.setLatitude(45.001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(0)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setSensorDistance(Distance.of(11))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(0))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
}
@@ -0,0 +1,282 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
/**
* Testing the states of TrackRecordingService.
* As states are checked against MutableLiveData that are updated via `postValue()` (is asynchronous to be inform other Threads), Thread.sleep() is required.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestStateMachine {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingService service;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
private TrackRecordingService startService() throws TimeoutException {
Intent startIntent = new Intent(context, TrackRecordingService.class);
return ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
.getService();
}
@Before
public void setUp() throws TimeoutException {
contentProviderUtils = new ContentProviderUtils(context);
service = startService();
tearDown();
}
@After
public void tearDown() throws TimeoutException {
TrackRecordingServiceTestUtils.resetService(mServiceRule, context);
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@MediumTest
@Test
public void initialState() {
// given
List<Track> tracks = contentProviderUtils.getTracks();
assertTrue(tracks.isEmpty());
// when
// noop
// then
assertFalse(service.isRecording());
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
}
@MediumTest
@Test
public void gps_startStop() throws InterruptedException {
// given
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// when
service.tryStartSensors();
Thread.sleep(1000);
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
// when
service.stopSensorsAndShutdown();
Thread.sleep(1000);
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
// assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue()); TODO BUG: GPS can only be stopped if a service is recording.
}
@MediumTest
@Test
public void recording_startPauseStopResume_no_data() throws InterruptedException {
// given
assertFalse(service.isRecording());
// when
Track.Id trackId = service.startNewTrack();
Thread.sleep(1000);
// then
assertTrue(service.isRecording());
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
// when
service.pauseCurrentTrack();
Thread.sleep(1000);
// then
assertTrue(service.isRecording());
assertTrue(service.isPaused());
assertEquals(new RecordingStatus(trackId, true), service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue()); //TODO The GPS is kept in pause, right? Might be a bug.
// when
service.resumeCurrentTrack();
Thread.sleep(1000);
// then
assertTrue(service.isRecording());
assertFalse(service.isPaused());
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
// when
service.endCurrentTrack();
Thread.sleep(1000);
// then
assertFalse(service.isRecording());
assertFalse(service.isPaused());
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// when
service.resumeTrack(trackId);
Thread.sleep(1000);
// then
assertTrue(service.isRecording());
assertFalse(service.isPaused());
assertEquals(new RecordingStatus(trackId, false), 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());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
}
@MediumTest
@Test
public void invalidStateChanges() throws InterruptedException {
// given
assertFalse(service.isRecording());
// when
service.pauseCurrentTrack();
Thread.sleep(1000);
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// when
service.resumeCurrentTrack();
Thread.sleep(1000);
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// when
service.endCurrentTrack();
Thread.sleep(1000);
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
}
@Ignore("TODO Bug: GPS can be stopped although the current track is recording")
@MediumTest
@Test
public void recording_stopGPS_noop() throws InterruptedException {
// given
Track.Id trackId = service.startNewTrack();
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
// when
service.stopSensors(); //TODO Should be ignored as service is recording
Thread.sleep(1000);
// then
assertTrue(service.isRecording());
assertFalse(service.isPaused());
assertEquals(new RecordingStatus(trackId, false), 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());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
}
@MediumTest
@Test
public void recording_startRecording_alreadyRecording() throws InterruptedException {
// given
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
// when
Track.Id newTrackId = service.startNewTrack();
Thread.sleep(1000);
// then
assertNotNull(trackId);
assertNull(newTrackId);
}
}
@@ -0,0 +1,46 @@
package de.dennisguse.opentracks.services;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import androidx.test.rule.ServiceTestRule;
import java.time.Clock;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class TrackRecordingServiceTestUtils {
//TODO Workaround as service is not stopped on API23; thus sharedpreferences are not reset between tests.
//TODO Anyhow, the service should re-create all it's resources if a recording starts and makes sure that there is no leftovers from previous recordings.
@Deprecated
public static void resetService(ServiceTestRule mServiceRule, Context context) throws TimeoutException {
// Let's use default values.
PreferencesUtils.clear();
// Reset service (if some previous test failed)
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, service.getTrackPointCreator()));
service.getTrackPointCreator().setClock(Clock.systemUTC());
service.endCurrentTrack();
}
static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
Location location = new Location("mock");
location.setTime(1L); // Should be ignored anyhow.
location.setLatitude(latitude);
location.setLongitude(longitude);
location.setAccuracy(accuracy);
location.setSpeed(speed);
trackPointCreator.setClock(time);
trackPointCreator.getGpsHandler().onLocationChanged(location);
}
}
@@ -1,4 +1,6 @@
package de.dennisguse.opentracks.services.tasks;
package de.dennisguse.opentracks.services.announcement;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.content.Intent;
@@ -19,12 +21,10 @@ import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class VoiceAnnouncementManagerTest {
@@ -60,11 +60,11 @@ public class VoiceAnnouncementManagerTest {
// when
TrackStatistics statistics = new TrackStatistics();
statistics.setTotalDistance(Distance.ofKilometer(13));
voiceAnnouncementManager.restore(statistics);
voiceAnnouncementManager.start(statistics);
assertEquals(Distance.of(15000), voiceAnnouncementManager.getNextTotalDistance());
statistics.setTotalDistance(Distance.of(15100));
voiceAnnouncementManager.restore(statistics);
voiceAnnouncementManager.start(statistics);
assertEquals(Distance.of(20000), voiceAnnouncementManager.getNextTotalDistance());
}
@@ -80,11 +80,11 @@ public class VoiceAnnouncementManagerTest {
// when
TrackStatistics statistics = new TrackStatistics();
statistics.setTotalTime(Duration.ofSeconds(91));
voiceAnnouncementManager.restore(statistics);
voiceAnnouncementManager.start(statistics);
assertEquals(Duration.ofSeconds(95), voiceAnnouncementManager.getNextTotalTime());
statistics.setTotalTime(Duration.ofSeconds(95));
voiceAnnouncementManager.restore(statistics);
voiceAnnouncementManager.start(statistics);
assertEquals(Duration.ofSeconds(100), voiceAnnouncementManager.getNextTotalTime());
}
@@ -1,4 +1,6 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.services.announcement;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.util.Pair;
@@ -6,25 +8,25 @@ import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import static org.junit.Assert.assertEquals;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
import de.dennisguse.opentracks.util.StringUtils;
@RunWith(AndroidJUnit4.class)
public class AnnouncementUtilsTest {
public class VoiceAnnouncementUtilsTest {
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
@@ -44,7 +46,7 @@ public class AnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, null);
// then
assertEquals("total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
@@ -60,15 +62,15 @@ public class AnnouncementUtilsTest {
IntervalStatistics.Interval lastInterval;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
// then
assertEquals(
Assert.assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
@@ -89,7 +91,7 @@ public class AnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, null);
// then
assertEquals("total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer", announcement);
@@ -105,12 +107,12 @@ public class AnnouncementUtilsTest {
IntervalStatistics.Interval lastInterval;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
// then
assertEquals(
@@ -134,7 +136,7 @@ public class AnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, null);
// then
assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour", announcement);
@@ -150,12 +152,12 @@ public class AnnouncementUtilsTest {
IntervalStatistics.Interval lastInterval;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
// then
assertEquals(
@@ -179,7 +181,7 @@ public class AnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, null);
// then
assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile", announcement);
@@ -195,12 +197,12 @@ public class AnnouncementUtilsTest {
IntervalStatistics.Interval lastInterval;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
// then
//assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
@@ -5,7 +5,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Looper;
@@ -22,21 +21,20 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(MockitoJUnitRunner.class)
public class LocationHandlerTest {
public class GPSHandlerTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@Mock
private TrackPointCreator trackPointCreator;
@InjectMocks
private LocationHandler locationHandler;
private GPSHandler locationHandler;
@BeforeClass
public static void preSetUp() {
@@ -47,15 +45,12 @@ public class LocationHandlerTest {
@Before
public void setUp() {
// Let's use default values.
sharedPreferences.edit().clear().commit();
PreferencesUtils.clear();
Mockito.when(trackPointCreator.createNow())
.thenReturn(Instant.now());
//TODO REMOVE
// locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
// locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
locationHandler.onStart(context, sharedPreferences);
locationHandler.onStart(context);
}
/**
@@ -67,7 +62,7 @@ public class LocationHandlerTest {
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
// then
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class));
}
/**
@@ -83,7 +78,7 @@ public class LocationHandlerTest {
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
// then
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class));
}
/**
@@ -92,14 +87,14 @@ public class LocationHandlerTest {
@Test
public void testOnLocationChanged_poorAccuracy() {
// given
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
// when
locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis()));
// then
// no newTrackPoint called
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class));
}
@Test
@@ -112,7 +107,7 @@ public class LocationHandlerTest {
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
// then
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class));
}
/**
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.services.handlers;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.SharedPreferences;
import org.junit.After;
import org.junit.Before;
@@ -13,8 +12,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
@RunWith(MockitoJUnitRunner.class)
public class TrackPointCreatorTest {
@@ -26,10 +25,7 @@ public class TrackPointCreatorTest {
private TrackPointCreator.Callback server;
@Mock
private LocationHandler locationHandler;
@Mock
private SharedPreferences sharedPreferences;
private GPSHandler locationHandler;
private TrackPointCreator subject;
@@ -52,7 +48,7 @@ public class TrackPointCreatorTest {
Distance horizontalAccuracyThreshold = Distance.of(50);
// when
subject.onNewTrackPoint(trackPoint, horizontalAccuracyThreshold);
subject.onNewTrackPoint(trackPoint);
// then
Thread.sleep(10); // Wait for executor service
@@ -0,0 +1,118 @@
package de.dennisguse.opentracks.settings;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
@RunWith(AndroidJUnit4.class)
public class PreferencesOpenHelperTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
@Test
public void test_upgradeFrom0To1_withoutStatsCustomLayouts() {
// given the version 0
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(context.getString(R.string.stats_custom_layouts_key));
editor.putInt(context.getString(R.string.prefs_last_version_key), 0);
editor.commit();
// when update to version 1
PreferencesOpenHelper.newInstance(1).check();
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
}
@Test
public void test_upgradeFrom1To2_withOldVersion() {
// given the version 1
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
String oldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";distance,1,1,0;speed,1,1,0;";
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
editor.commit();
// when update to version 2
PreferencesOpenHelper.newInstance(2).check();
String updatedOldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";"
+ PreferencesUtils.getLayoutColumnsByDefault() + ";distance,1,1,0;speed,1,1,0;";
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
}
@Test
public void test_upgradeFrom1To2_withNewVersion() {
// given the version 1
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
String oldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
editor.commit();
// when update to version 2
PreferencesOpenHelper.newInstance(2).check();
String updatedOldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
}
@Test
public void test_downgrade() {
// given version 2
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
String customLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
editor.putString(context.getString(R.string.stats_custom_layouts_key), customLayoutCsv);
editor.putInt(context.getString(R.string.prefs_last_version_key), 2);
editor.commit();
// when downgrade to version 1
PreferencesOpenHelper.newInstance(1).check();
// then custom layout should be equals to default layout.
Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
List<Layout> customLayout = PreferencesUtils.getAllCustomLayouts();
assertEquals(customLayout.size(), 1);
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
}
}
@@ -0,0 +1,345 @@
package de.dennisguse.opentracks.settings;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
@RunWith(AndroidJUnit4.class)
public class PreferencesUtilsTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
@Test
public void ExportTrackFileFormat_ok() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.name());
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
public void ExportTrackFileFormat_invalid() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
public void ExportTrackFileFormat_noValue() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
public void testGetAllCustomLayouts_default() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
// then
assertEquals(layouts.size(), 1);
assertTrue(layouts.get(0).getFields().size() > 0);
assertEquals(layouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(layouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
}
@Test
public void testGetCustomLayout_default() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertTrue(layout.getFields().size() > 0);
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
}
@Test
public void testGetCustomLayout_1() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layouts_key),
"run;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getName(), "run");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
assertTrue(layout.getFields().get(0).isPrimary());
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(layout.getFields().get(1).isVisible());
assertFalse(layout.getFields().get(1).isPrimary());
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
assertFalse(layout.getFields().get(2).isVisible());
assertTrue(layout.getFields().get(2).isPrimary());
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(layout.getFields().get(3).isVisible());
assertFalse(layout.getFields().get(3).isPrimary());
}
@Test
public void testGetCustomLayout_coordinatesIsWide() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layouts_key),
"walking;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getName(), "walking");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
assertTrue(layout.getFields().get(0).isPrimary());
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(layout.getFields().get(1).isVisible());
assertFalse(layout.getFields().get(1).isPrimary());
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
assertFalse(layout.getFields().get(2).isVisible());
assertTrue(layout.getFields().get(2).isPrimary());
assertTrue(layout.getFields().get(2).isWide());
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(layout.getFields().get(3).isVisible());
assertFalse(layout.getFields().get(3).isPrimary());
}
@Test
public void testSetCustomLayout() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
Layout layoutSrc = new Layout("road cycling");
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
// when
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
editor.commit();
// then
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layouts_key), null);
assertNotNull(csv);
assertEquals(csv,
"road cycling;" + PreferencesUtils.getLayoutColumnsByDefault() + ";"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1,0;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
Layout layoutDst = PreferencesUtils.getCustomLayout();
assertEquals(layoutSrc.getName(), layoutDst.getName());
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
}
}
@Test
public void testEditCustomLayouts() {
// update all custom layouts
// given a custom layout with two profiles
String cyclingProfile = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
String runningProfile = "running;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.apply();
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
// when cyling profile is updated
String cyclingProfileUpdated = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
List<Layout> layoutsToBeUpdated = new ArrayList<>();
layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources));
layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources));
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
// then only updated profile is modified in the custom layouts
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
assertEquals(layoutsBefore.size(), 2);
assertEquals(layoutsAfter.size(), 2);
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
}
@Test
public void testEditCustomLayout() {
// Update only one custom layout
// given a custom layout with two profiles
String cyclingProfile = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
String runningProfile = "running;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.apply();
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
// when cyling profile is updated
String cyclingProfileUpdated = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
Layout layoutToBeUpdated = Layout.fromCsv(cyclingProfileUpdated, resources);
PreferencesUtils.updateCustomLayout(layoutToBeUpdated);
// then only updated profile is modified in the custom layouts
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
assertEquals(layoutsBefore.size(), 2);
assertEquals(layoutsAfter.size(), 2);
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
}
@Test
public void testGetCustomLayout_whenSelectedOneNotExists() {
// given a custom layout with two profiles and not existing custom layout selected
String cyclingProfile = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
String runningProfile = "running;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.putString(context.getString(R.string.stats_custom_layout_selected_layout_key), "Not Exists");
editor.apply();
// when it gets the custom layout
Layout layout = PreferencesUtils.getCustomLayout();
// then the first one was returned
assertEquals(layout.getName(), "cycling");
}
}
@@ -14,7 +14,9 @@
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.share;
import static org.junit.Assert.assertEquals;
import android.content.Context;
@@ -27,16 +29,16 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link DescriptionGenerator}.
*
@@ -80,18 +82,18 @@ public class DescriptionGeneratorTest {
+ "Description: -<br>"
+ "Total distance: 20.00 km (12.4 mi)<br>"
+ "Total time: 10:00<br>"
+ "Moving time: 05:00<br>"
+ "Average speed: 120.00 km/h (74.6 mi/h)<br>"
+ "Average moving speed: 240.00 km/h (149.1 mi/h)<br>"
+ "Max speed: 360.00 km/h (223.7 mi/h)<br>"
+ "Average pace: 0:30 min/km (0:48 min/mi)<br>"
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
+ "Max elevation: 550 m (1804 ft)<br>"
+ "Min elevation: -500 m (-1640 ft)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Elevation loss: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
+ "Moving time: 05:00<br>"
+ "Average speed: 120.00 km/h (74.6 mi/h)<br>"
+ "Average moving speed: 240.00 km/h (149.1 mi/h)<br>"
+ "Max speed: 360.00 km/h (223.7 mi/h)<br>"
+ "Average pace: 0:30 min/km (0:48 min/mi)<br>"
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
+ "Max elevation: 550 m (1804 ft)<br>"
+ "Min elevation: -500 m (-1640 ft)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Elevation loss: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
@@ -127,7 +129,7 @@ public class DescriptionGeneratorTest {
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Elevation loss: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
+ "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
@@ -15,6 +15,9 @@
*/
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;
@@ -24,11 +27,8 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
/**
* Tests for {@link TrackStatistics}.
@@ -1,5 +1,8 @@
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.Ignore;
@@ -9,21 +12,16 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.Altitude;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
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.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
@RunWith(AndroidJUnit4.class)
public class TrackStatisticsUpdaterTest {
private static final Distance GPS_DISTANCE = Distance.of(50);
@Test
public void empty() {
// when
@@ -53,7 +51,7 @@ public class TrackStatisticsUpdaterTest {
// when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
subject.addTrackPoint(tp, GPS_DISTANCE);
subject.addTrackPoint(tp);
// then
TrackStatistics statistics = subject.getTrackStatistics();
@@ -77,22 +75,22 @@ public class TrackStatisticsUpdaterTest {
// when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE));
data.trackPoints.forEach(it -> subject.addTrackPoint(it));
// then
TrackStatistics statistics = subject.getTrackStatistics();
assertEquals(99.58, statistics.getTotalDistance().toM(), 0.01);
assertEquals(Duration.ofSeconds(14), statistics.getTotalTime());
assertEquals(Duration.ofSeconds(7), statistics.getMovingTime());
assertEquals(128.03, statistics.getTotalDistance().toM(), 0.01);
assertEquals(Duration.ofSeconds(13), statistics.getTotalTime());
assertEquals(Duration.ofSeconds(9), statistics.getMovingTime());
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
assertEquals(27.5, statistics.getMaxAltitude(), 0.01);
assertEquals(28.75, statistics.getMaxAltitude(), 0.01);
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(7.11, statistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(9.84, statistics.getAverageSpeed().toMPS(), 0.01);
}
@Test
@@ -105,9 +103,9 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
// then
assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
@@ -124,9 +122,9 @@ public class TrackStatisticsUpdaterTest {
tp3.setSpeed(Speed.of(5f));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
// then
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
@@ -141,24 +139,24 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
tp2.setSpeed(Speed.of(5f));
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp2.setSpeed(Speed.of(5f));
tp4.setSpeed(Speed.of(5f));
tp4.setSensorDistance(Distance.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
tp5.setSensorDistance(Distance.of(10f));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
// then
assertEquals(110.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
// when
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp5, GPS_DISTANCE);
subject.addTrackPoint(tp4);
subject.addTrackPoint(tp5);
// then
assertEquals(125.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
@@ -178,16 +176,16 @@ public class TrackStatisticsUpdaterTest {
tp5.setSensorDistance(Distance.of(10f));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
// then
assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
// when
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp5, GPS_DISTANCE);
subject.addTrackPoint(tp4);
subject.addTrackPoint(tp5);
// then
assertEquals(15, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
@@ -200,22 +198,25 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
tp3.setSensorDistance(Distance.of(5f));
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp4.setSpeed(Speed.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
// then
assertEquals(5, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
// when
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp5, GPS_DISTANCE);
subject.addTrackPoint(tp4);
subject.addTrackPoint(tp5);
// then
assertEquals(59.18, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
@@ -248,19 +249,22 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp4.setSpeed(Speed.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp1);
subject.addTrackPoint(tp2);
subject.addTrackPoint(tp3);
subject.addTrackPoint(tp4);
// when
TrackStatisticsUpdater copy = new TrackStatisticsUpdater(subject);
subject.addTrackPoint(tp5, GPS_DISTANCE);
copy.addTrackPoint(tp5, GPS_DISTANCE);
subject.addTrackPoint(tp5);
copy.addTrackPoint(tp5);
// then
@@ -1,4 +1,7 @@
package de.dennisguse.opentracks.viewmodels;
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
@@ -13,15 +16,12 @@ import java.time.Instant;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.TrackIconUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(JUnit4.class)
public class AggregatedStatisticsTest {
@@ -0,0 +1,84 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import android.util.Pair;
import android.view.View;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import org.hamcrest.core.IsInstanceOf;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
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.TrackPoint;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoAggregatedFilterTest {
private final String CATEGORY = "category";
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@Before
public void setUp() {
Pair<Track, List<TrackPoint>> pair = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 20);
pair.first.setCategory(CATEGORY);
TestDataUtil.insertTrackWithLocations(new ContentProviderUtils(ApplicationProvider.getApplicationContext()), pair.first, pair.second);
}
@Test
public void espressoAggregatedFilterTest() {
// open AggregatedStatisticsActivity through toolbar's menu item
onView(ViewMatchers.withId(R.id.toolbar)).check(matches(isDisplayed()));
onView(withId(R.id.toolbar)).check(matches(hasDescendant(withId(R.id.track_list_aggregated_stats))));
onView(withId(R.id.track_list_aggregated_stats)).perform(click());
// open FilterDialogFragment through toolbar's menu item
onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
onView(withId(R.id.toolbar)).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
ViewInteraction checkBox = onView(
allOf(withId(R.id.filter_dialog_check_button), withText(CATEGORY),
withParent(allOf(withId(R.id.filter_items),
withParent(IsInstanceOf.<View>instanceOf(android.view.ViewGroup.class)))),
isDisplayed()));
checkBox.check(matches(isDisplayed()));
// check there's an edit text for "from date"
onView(withId(R.id.filter_date_edit_text_from)).check(matches(isDisplayed()));
// check there's an edit text for "to date"
onView(withId(R.id.filter_date_edit_text_to)).check(matches(isDisplayed()));
}
}
@@ -0,0 +1,93 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class CsvLayoutUtilsTest extends TestCase {
@Test
public void testGetCsvLineParts() {
// given
String csvLineOk = "Layout Name;2;distance,1,1;speed,1,1;";
String csvLineWrong1 = "Layout Name;distance,1,1;speed,1,1;";
String csvLineWrong2 = "Layout Name;1;";
// when
List<String> partsOk = CsvLayoutUtils.getCsvLineParts(csvLineOk);
List<String> partsWrong1 = CsvLayoutUtils.getCsvLineParts(csvLineWrong1);
List<String> partsWrong2 = CsvLayoutUtils.getCsvLineParts(csvLineWrong2);
// then
assertNotNull(partsOk);
assertEquals(partsOk.size(), 4);
assertEquals(partsOk.get(0), "Layout Name");
assertEquals(partsOk.get(1), "2");
assertEquals(partsOk.get(2), "distance,1,1");
assertEquals(partsOk.get(3), "speed,1,1");
assertNull(partsWrong1);
assertNull(partsWrong2);
}
@Test
public void testGetCsvFieldParts() {
// given
String csvFieldOk1 = "distance,0,0";
String csvFieldOk2 = "speed,0,1";
String csvFieldOk3 = "time,1,0";
String csvFieldOk4 = "moving time,1,1";
String csvFieldOk5 = ";moving time,1,1";
String csvLineWrong1 = "";
String csvLineWrong2 = "distance,speed,time";
String csvLineWrong3 = "distance,0,1o";
String csvLineWrong4 = "distance,0z,1";
String csvLineWrong5 = "distance";
String csvLineWrong6 = "distance,0";
String csvLineWrong7 = "distance,1,1;";
// when
String[] ok1 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk1);
String[] ok2 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk2);
String[] ok3 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk3);
String[] ok4 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk4);
String[] ok5 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk5);
String[] wrong1 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong1);
String[] wrong2 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong2);
String[] wrong3 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong3);
String[] wrong4 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong4);
String[] wrong5 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong5);
String[] wrong6 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong6);
String[] wrong7 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong7);
// then
assertNotNull(ok1);
assertNotNull(ok2);
assertNotNull(ok3);
assertNotNull(ok4);
assertNotNull(ok5);
assertFieldOk(ok1, "distance", "0", "0");
assertFieldOk(ok2, "speed", "0", "1");
assertFieldOk(ok3, "time", "1", "0");
assertFieldOk(ok4, "moving time", "1", "1");
assertFieldOk(ok5, ";moving time", "1", "1");
assertNull(wrong1);
assertNull(wrong2);
assertNull(wrong3);
assertNull(wrong4);
assertNull(wrong5);
assertNull(wrong6);
assertNull(wrong7);
}
private void assertFieldOk(String[] ok, String name, String val1, String val2) {
assertEquals(ok[0], name);
assertEquals(ok[1], val1);
assertEquals(ok[2], val2);
}
}
@@ -1,34 +1,31 @@
package de.dennisguse.opentracks;
package de.dennisguse.opentracks.ui.customRecordingLayout;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.is;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewAssertion;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.*;
import static androidx.test.espresso.matcher.ViewMatchers.*;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.util.PreferencesUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -36,31 +33,28 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.CoreMatchers.is;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoCustomLayoutTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule =
GrantPermissionRule.grant(
"android.permission.ACCESS_FINE_LOCATION");
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@Test
public void customLayoutTest() {
// TrackListActivity: start recording
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.controller_record));
trackControllerRecordButton.perform(click());
// Get custom layout preferences and check all data fields are showed.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
@@ -91,7 +85,7 @@ public class EspressoCustomLayoutTest {
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
return new TypeSafeMatcher<>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
@@ -0,0 +1,154 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import android.content.Context;
import android.content.res.Resources;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(AndroidJUnit4.class)
public class LayoutTest extends TestCase {
private final Context context = ApplicationProvider.getApplicationContext();
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
@Test
public void testFromCsv() {
// given a csv line
String csv = "running;2;" + context.getString(R.string.stats_custom_layout_speed_key) + ",1,1,0;" + context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;" + context.getString(R.string.stats_custom_layout_altitude_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_gain_key) + ",0,0,0;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "running");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(dataFieldList.size(), 4);
assertEquals(dataFieldList.get(0).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertEquals(dataFieldList.get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertEquals(dataFieldList.get(2).getKey(), context.getString(R.string.stats_custom_layout_altitude_key));
assertEquals(dataFieldList.get(3).getKey(), context.getString(R.string.stats_custom_layout_gain_key));
assertEquals(dataFieldList.get(0).getTitle(), context.getString(R.string.stats_speed));
assertEquals(dataFieldList.get(1).getTitle(), context.getString(R.string.stats_distance));
assertEquals(dataFieldList.get(2).getTitle(), context.getString(R.string.stats_altitude));
assertEquals(dataFieldList.get(3).getTitle(), context.getString(R.string.stats_gain));
assertTrue(dataFieldList.get(0).isVisible());
assertTrue(dataFieldList.get(0).isPrimary());
assertFalse(dataFieldList.get(0).isWide());
assertTrue(dataFieldList.get(1).isVisible());
assertFalse(dataFieldList.get(1).isPrimary());
assertFalse(dataFieldList.get(1).isWide());
assertFalse(dataFieldList.get(2).isVisible());
assertTrue(dataFieldList.get(2).isPrimary());
assertFalse(dataFieldList.get(2).isWide());
assertFalse(dataFieldList.get(3).isVisible());
assertFalse(dataFieldList.get(3).isPrimary());
assertFalse(dataFieldList.get(3).isWide());
}
@Test
public void testFromCsv_Wrong1() {
// given a csv line without number of columns
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(layout.getFields().size(), 0);
}
@Test
public void testFromCsv_Wrong2() {
// given a csv line without any field
String csv1 = "Layout Name;2;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(layout.getFields().size(), 0);
}
@Test
public void testFromCsv_Wrong3() {
// given a csv line with wrong fields description
String csv1 = "Layout Name;2;speed,distance,total time;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "Layout Name");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().size(), 0);
}
@Test
public void testFromCsv_Wrong4() {
// given a csv line with the first field ok but not the others
String csv1 = "Layout Name;2;speed,1,0;distance;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "Layout Name");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().size(), 1);
}
@Test
public void testToCsv() {
// given a layout's object
Layout layout = new Layout("Test Layout", 2);
layout.addField("key1", "Title 1", false, false, false);
layout.addField("key2", "Title 2", false, true, false);
layout.addField("key3", "Title 3", true, false, false);
layout.addField("key4", "Title 4", true, true, false);
layout.addField("key5", "Title 5", true, true, true);
// when converts it to CSV
String csv = layout.toCsv();
// then csv is well built
assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
}
@Test
public void testToCsv_columnsByDefault() {
// given a layout's object
Layout layout = new Layout("Test Layout");
layout.addField("key1", "Title 1", false, false, false);
layout.addField("key2", "Title 2", false, true, false);
layout.addField("key3", "Title 3", true, false, false);
layout.addField("key4", "Title 4", true, true, false);
layout.addField("key5", "Title 5", true, true, true);
// when converts it to CSV
String csv = layout.toCsv();
// then csv is well built
assertEquals(csv, "Test Layout;" + PreferencesUtils.getLayoutColumnsByDefault() + ";key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
}
}
@@ -1,4 +1,7 @@
package de.dennisguse.opentracks.viewmodels;
package de.dennisguse.opentracks.ui.intervals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.util.Pair;
@@ -12,17 +15,15 @@ import org.junit.runners.JUnit4;
import java.util.List;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.UnitConversions;
import static org.junit.Assert.assertEquals;
@RunWith(JUnit4.class)
public class IntervalStatisticsTest {
@@ -40,7 +41,7 @@ public class IntervalStatisticsTest {
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
for (TrackPoint tp : trackPoints) {
trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0));
trackStatisticsUpdater.addTrackPoint(tp);
}
return trackStatisticsUpdater.getTrackStatistics();
}
@@ -115,9 +116,40 @@ public class IntervalStatisticsTest {
whenAndThen(10000, distanceInterval);
}
@Test
public void testWithNoLossTrackPoints() {
// TrackPoints with elevation gain but without elevation loss.
// 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");
contentProviderUtils.insertTrack(dummyTrack);
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
for (int i = 0; i < numberOfPoints; i++) {
TrackPoint tp = TestDataUtil.createTrackPoint(i);
tp.setAltitudeLoss(null);
contentProviderUtils.insertTrackPoint(tp, dummyTrack.getId());
trackStatisticsUpdater.addTrackPoint(tp);
}
dummyTrack.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(dummyTrack);
Pair<Track.Id, TrackStatistics> trackWithStats = new Pair<>(dummyTrack.getId(), trackStatisticsUpdater.getTrackStatistics());
// when and then
whenAndThen(trackWithStats, numberOfPoints, distanceInterval);
}
private void whenAndThen(int numberOfPoints, float distanceInterval) {
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0));
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
whenAndThen(trackWithStats, numberOfPoints, distanceInterval);
}
private void whenAndThen(Pair<Track.Id, TrackStatistics> trackWithStats, int numberOfPoints, float distanceInterval) {
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval));
Track.Id trackId = trackWithStats.first;
TrackStatistics trackStatistics = trackWithStats.second;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
@@ -127,18 +159,39 @@ public class IntervalStatisticsTest {
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
Distance totalDistance = Distance.of(0);
float totalTime = 0L;
float totalGain = 0f;
Float totalGain = null;
Float totalLoss = null;
for (IntervalStatistics.Interval i : intervalList) {
totalDistance = totalDistance.plus(i.getDistance());
totalTime += i.getDistance().toM() / i.getSpeed().toMPS();
totalGain += i.getGain_m();
if (totalGain == null) {
totalGain = i.getGain_m();
} else if (i.getGain_m() != null) {
totalGain += i.getGain_m();
}
if (totalLoss == null) {
totalLoss = i.getLoss_m();
} else if (i.getLoss_m() != null) {
totalLoss += i.getLoss_m();
}
}
// then
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
if (totalGain != null) {
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
} else {
assertTrue(intervalStatistics.getIntervalList().stream().noneMatch(IntervalStatistics.Interval::hasGain));
}
if (totalLoss != null) {
assertEquals(totalLoss, numberOfPoints * TestDataUtil.ALTITUDE_LOSS, 0.1);
} else {
assertTrue(intervalStatistics.getIntervalList().stream().noneMatch(IntervalStatistics.Interval::hasLoss));
}
for (int i = 0; i < intervalList.size() - 1; i++) {
assertEquals(intervalList.get(i).getDistance().toM(), distanceInterval, 0.001);
@@ -1,5 +1,8 @@
package de.dennisguse.opentracks.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -16,10 +19,7 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import de.dennisguse.opentracks.data.models.TrackPoint;
@RunWith(JUnit4.class)
public class EGM2008UtilsTest {
@@ -15,6 +15,8 @@
*/
package de.dennisguse.opentracks.util;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -22,8 +24,6 @@ import org.junit.runners.JUnit4;
import java.io.File;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link FileUtils}.
*
@@ -101,6 +101,13 @@ public class FileUtilsTest {
assertEquals(expected, FileUtils.sanitizeFileName(name));
}
@Test
public void testSanitizeFileName_emoji() {
String name = "\uD83C\uDF5B-Food";
String expected = "_-Food";
assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#truncateFileName(File, String, String)}.
* Verifies the a long file name is truncated.
@@ -1,199 +0,0 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
public class PreferencesUtilsTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Test
public void ExportTrackFileFormat_ok() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.name());
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
public void ExportTrackFileFormat_invalid() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
public void ExportTrackFileFormat_noValue() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
public void testGetCustomLayout_default() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
// then
assertTrue(layout.getFields().size() > 0);
assertEquals(layout.getProfile(), context.getString(R.string.default_activity_default));
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
}
@Test
public void testGetCustomLayout_1() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
"run;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getProfile(), "run");
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
assertTrue(layout.getFields().get(0).isPrimary());
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(layout.getFields().get(1).isVisible());
assertFalse(layout.getFields().get(1).isPrimary());
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
assertFalse(layout.getFields().get(2).isVisible());
assertTrue(layout.getFields().get(2).isPrimary());
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(layout.getFields().get(3).isVisible());
assertFalse(layout.getFields().get(3).isPrimary());
}
@Test
public void testGetCustomLayout_coordinatesIsWide() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
"walking;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getProfile(), "walking");
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
assertTrue(layout.getFields().get(0).isPrimary());
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(layout.getFields().get(1).isVisible());
assertFalse(layout.getFields().get(1).isPrimary());
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
assertFalse(layout.getFields().get(2).isVisible());
assertTrue(layout.getFields().get(2).isPrimary());
assertTrue(layout.getFields().get(2).isWide());
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(layout.getFields().get(3).isVisible());
assertFalse(layout.getFields().get(3).isPrimary());
}
@Test
public void testSetCustomLayout() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Layout layoutSrc = new Layout("road cycling");
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
// when
PreferencesUtils.setCustomLayout(sharedPreferences, context, layoutSrc);
// then
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
assertNotNull(csv);
assertEquals(csv,
"road cycling;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
Layout layoutDst = PreferencesUtils.getCustomLayout(sharedPreferences, context);
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
}
}
}
@@ -11,8 +11,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
@RunWith(AndroidJUnit4.class)
public class StatisticsUtilsTest extends TestCase {
@@ -16,6 +16,9 @@
package de.dennisguse.opentracks.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -24,15 +27,26 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
/**
* Tests for {@link StringUtils}.
@@ -141,7 +155,7 @@ public class StringUtilsTest {
// This comparision tends to be flaky (difference of 1ms)
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime).toEpochMilli()) <= 1);
assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime).toInstant().toEpochMilli()) <= 1);
}
@Test
@@ -184,4 +198,43 @@ public class StringUtilsTest {
assertEquals("12.0 km/h", StringUtils.formatSpeed(context, Speed.of(3.34), true, true));
assertEquals("7.5 mph", StringUtils.formatSpeed(context, Speed.of(3.34), false, true));
}
@Test
public void testFormatDateTodayRelative() {
// given
ArrayList<String> shortDays = Arrays.stream(DayOfWeek.values()).map(d -> d.getDisplayName(TextStyle.FULL, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
ArrayList<String> shortMonths = Arrays.stream(Month.values()).map(m -> m.getDisplayName(TextStyle.SHORT, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
LocalDate today = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).toLocalDate();
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
LocalDate dayName = today.minus(2, ChronoUnit.DAYS);
LocalDate thisYear = today.minus(15, ChronoUnit.DAYS);
LocalDate aYearAgo = today.minus(400, ChronoUnit.DAYS);
int offsetFromLocale = OffsetDateTime.now().getOffset().getTotalSeconds() - 28800; // -08:00
OffsetDateTime todayRecordedOdt = OffsetDateTime.of(LocalDateTime.of(today.getYear(), today.getMonth(), today.getDayOfMonth(), 20, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
OffsetDateTime yesterdayRecordedOdt = OffsetDateTime.of(LocalDateTime.of(yesterday.getYear(), yesterday.getMonth(), yesterday.getDayOfMonth(), 1, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
OffsetDateTime dayNameRecordedOdt = OffsetDateTime.of(LocalDateTime.of(dayName.getYear(), dayName.getMonth(), dayName.getDayOfMonth(), 7, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
OffsetDateTime thisYearRecordedOdt = OffsetDateTime.of(LocalDateTime.of(thisYear.getYear(), thisYear.getMonth(), thisYear.getDayOfMonth(), 12, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
OffsetDateTime aYearAgoRecordedOdt = OffsetDateTime.of(LocalDateTime.of(aYearAgo.getYear(), aYearAgo.getMonth(), aYearAgo.getDayOfMonth(), 17, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
// when
String formatToday = StringUtils.formatDateTodayRelative(context, todayRecordedOdt);
String formatYesterday = StringUtils.formatDateTodayRelative(context, yesterdayRecordedOdt);
String formatDayName = StringUtils.formatDateTodayRelative(context, dayNameRecordedOdt);
String formatThisYear = StringUtils.formatDateTodayRelative(context, thisYearRecordedOdt);
String formatAYearAgo = StringUtils.formatDateTodayRelative(context, aYearAgoRecordedOdt);
// then
assertEquals("Today", formatToday);
assertEquals("Yesterday", formatYesterday);
assertTrue(shortDays.contains(formatDayName)); // Something like Friday
if (today.getYear() != thisYear.getYear()) {
assertTrue(shortMonths.stream().anyMatch(fty -> formatThisYear.matches("\\d+ " + fty + " \\d{4}"))); // Something like 14 Dec 2021
} else {
assertTrue(shortMonths.stream().anyMatch(fty -> formatThisYear.matches("\\d+ " + fty))); // Something like 14 Dec
}
assertTrue(shortMonths.stream().anyMatch(fty -> formatAYearAgo.matches("\\d+ " + fty + " \\d{4}"))); // Something like 14 Dec 2021
}
}
@@ -16,8 +16,9 @@
package de.dennisguse.opentracks.util;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -25,14 +26,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Locale;
import java.time.OffsetDateTime;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import static org.junit.Assert.assertEquals;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Tests {@link TrackNameUtils}.
@@ -43,19 +41,17 @@ import static org.junit.Assert.assertEquals;
public class TrackNameUtilsTest {
private static final Track.Id TRACK_ID = new Track.Id(1L);
private static final Instant START_TIME = Instant.ofEpochMilli(1288213406000L);
private static final OffsetDateTime START_TIME = OffsetDateTime.parse("2022-01-02T10:15:30.1234+01:00");
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
/**
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
*/
@Test
public void testTrackName_date_local() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
assertEquals(StringUtils.formatDateTimeWithOffset(START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
@@ -63,9 +59,8 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
assertEquals("2022-01-02T10:15+01", TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
@@ -73,7 +68,7 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_number() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
}
@@ -1,5 +1,7 @@
package de.dennisguse.opentracks.viewmodels;
import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -15,14 +17,15 @@ import java.time.Duration;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.mockito.Mockito.when;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
@RunWith(MockitoJUnitRunner.class)
public class StatisticDataBuilderTest extends TestCase {
@@ -36,7 +39,7 @@ public class StatisticDataBuilderTest extends TestCase {
private TrackStatistics trackStatisticsMock;
@Mock
private TrackRecordingService.RecordingData recordingDataMock;
private RecordingData recordingDataMock;
@Mock
private SensorStatistics sensorStatisticsMock;
@@ -124,8 +127,8 @@ public class StatisticDataBuilderTest extends TestCase {
@Test
public void testFromSensorStatistics_onlyHeartRate() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
when(sensorStatisticsMock.hasPower()).thenReturn(false);
@@ -142,8 +145,8 @@ public class StatisticDataBuilderTest extends TestCase {
public void testFromSensorStatistics_onlyCadence() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
when(sensorStatisticsMock.hasPower()).thenReturn(false);
// when
@@ -160,7 +163,7 @@ public class StatisticDataBuilderTest extends TestCase {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
when(sensorStatisticsMock.hasPower()).thenReturn(true);
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
@@ -173,13 +176,13 @@ public class StatisticDataBuilderTest extends TestCase {
@Test
public void testFromSensorStatistics() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
when(sensorStatisticsMock.hasPower()).thenReturn(true);
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
@@ -0,0 +1,26 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<name>20210907_213924.gpx</name>
<trkseg>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:10:19Z</time>
<hdop>4</hdop>
<speed>5</speed>
</trkpt>
<trkpt lat="30.14184657" lon="-40.38670089">
<ele>-5</ele>
<time>2021-09-07T22:11:07Z</time>
<hdop>4</hdop>
<speed>4</speed>
</trkpt>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:12:00Z</time>
<hdop>4</hdop>
<speed>3</speed>
</trkpt>
</trkseg>
</trk>
</gpx>
@@ -0,0 +1,23 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<name>20210907_213924.gpx</name>
<trkseg>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:10:19Z</time>
<hdop>4</hdop>
</trkpt>
<trkpt lat="30.14184657" lon="-40.38670089">
<ele>-5</ele>
<time>2021-09-07T22:11:07Z</time>
<hdop>4</hdop>
</trkpt>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:12:00Z</time>
<hdop>4</hdop>
</trkpt>
</trkseg>
</trk>
</gpx>

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