Compare commits

...

109 Commits

Author SHA1 Message Date
Dennis Guse 4df0e2a65c Release: v3.25.1 2022-03-02 03:36:43 +01:00
Peter Storch b5acfa022d try appCtx to persist NextCloud URI permission
for #1080
Also fixing a missing break in a switch statement.
2022-02-26 23:42:50 +01:00
dependabot[bot] 6fe38453b3 Bump gradle from 7.1.1 to 7.1.2
Bumps gradle from 7.1.1 to 7.1.2.

---
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-25 12:54:54 +01:00
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
294 changed files with 4562 additions and 3243 deletions
+3 -1
View File
@@ -1,6 +1,8 @@
name: Linting
on: [ push ]
on:
push:
pull_request_target:
jobs:
analyse:
+5 -2
View File
@@ -1,6 +1,9 @@
#https://github.com/marketplace/actions/android-emulator-runner
name: Test
on: [ push ]
on:
push:
pull_request_target:
jobs:
test:
timeout-minutes: 45
@@ -20,7 +23,7 @@ jobs:
java-version: '11'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.22.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
+7
View File
@@ -3,7 +3,14 @@
<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>
+22 -4
View File
@@ -117,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/)).
+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).
+7 -11
View File
@@ -57,18 +57,13 @@ android {
viewBinding true
}
aaptOptions {
cruncherEnabled = false
}
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4646
versionName "v3.24.0"
versionCode 4755
versionName "v3.25.1"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdkVersion 21
targetSdkVersion 31
@@ -98,6 +93,7 @@ android {
}
release {
crunchPngs false
minifyEnabled false
}
@@ -123,10 +119,10 @@ android {
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.appcompat:appcompat:1.4.0'
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'
@@ -134,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:4.2.0'
androidTestImplementation 'org.mockito:mockito-android:4.3.1'
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

@@ -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
@@ -0,0 +1,6 @@
v3.25.1: OpenTracks
Bugfix:
- Automatic export: hopefully prevent silent failures
- Automatic export: prevent crash
@@ -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.
+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" />
@@ -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
@@ -10,11 +10,10 @@ import org.junit.runner.RunWith;
import java.time.Duration;
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.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;
@RunWith(AndroidJUnit4.class)
@@ -78,8 +77,8 @@ public class ChartPointTest {
public void create_sensorAvailable() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setHeartRate_bpm(100f);
trackPoint.setCadence_rpm(101f);
trackPoint.setHeartRate(100f);
trackPoint.setCadence(101f);
trackPoint.setPower(102f);
TrackStatistics statistics = new TrackStatistics();
@@ -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.setCadence_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.setCadence_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)));
@@ -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,7 @@
* 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;
@@ -49,20 +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;
/**
* A unit test for {@link ContentProviderUtils}.
@@ -739,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());
}
}
/**
@@ -777,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);
@@ -786,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());
}
/**
@@ -800,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
@@ -810,8 +823,8 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
trackPoint.setHeartRate_bpm(1F);
trackPoint.setCadence_rpm(2F);
trackPoint.setHeartRate(1F);
trackPoint.setCadence(2F);
trackPoint.setPower(3F);
// when
@@ -820,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.getCadence_rpm(), trackPoints.get(10).getCadence_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());
}
/**
@@ -850,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
@@ -910,8 +923,8 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
trackPoint.setPower(null);
trackPoint.setCadence_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);
@@ -973,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
@@ -1005,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());
}
@@ -1035,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());
}
@@ -1065,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
@@ -1098,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
@@ -1143,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
@@ -1188,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) {
@@ -1219,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
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.content.data;
package de.dennisguse.opentracks.data;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -9,7 +9,7 @@ import org.junit.runner.RunWith;
import java.time.Instant;
import de.dennisguse.opentracks.content.provider.SelectionData;
import de.dennisguse.opentracks.data.models.Track;
@RunWith(AndroidJUnit4.class)
public class TrackSelectionTest extends TestCase {
@@ -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 {
@@ -14,7 +14,7 @@ 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;
@RunWith(JUnit4.class)
@@ -38,24 +38,27 @@ 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.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;
/**
@@ -102,7 +105,7 @@ public class ExportImportTest {
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);
}
@@ -169,7 +172,7 @@ 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());
}
@@ -336,9 +339,9 @@ public class ExportImportTest {
.setAltitudeLoss(1f)
.setAltitudeGain(1f)
.setSensorDistance(Distance.of(12))
.setHeartRate_bpm(66f)
.setHeartRate(66f)
.setPower(50f)
.setCadence_rpm(3f),
.setCadence(3f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:21Z"))
.setLatitude(3)
.setLongitude(14.002)
@@ -439,18 +442,18 @@ public class ExportImportTest {
}
}
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, Float heartRate, Float cyclingCadence, Float power) {
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));
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", heartRate));
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(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.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);
@@ -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;
@@ -18,17 +21,14 @@ 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 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Test that legacy KML/GPX formats can still be imported.
*/
@@ -44,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
@@ -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,7 +4,7 @@ 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 {
@@ -61,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.hasCadence(), actual.hasCadence());
if (expected.hasCadence()) {
Assert.assertEquals(expected.getCadence_rpm(), actual.getCadence_rpm(), delta);
Assert.assertEquals(expected.getCadence(), actual.getCadence());
}
}
@@ -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 {
@@ -101,7 +102,7 @@ public class BluetoothUtilsTest {
// then
assertEquals(Speed.of(5), sensor.getSpeed());
assertEquals(80, sensor.getCadence(), 0.01);
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
@@ -1,4 +1,9 @@
package de.dennisguse.opentracks.content.sensor;
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;
@@ -6,13 +11,9 @@ import org.junit.Ignore;
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;
import static org.junit.Assert.assertNull;
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 {
@@ -20,48 +21,48 @@ 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
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(), 0.01);
assertEquals(60, current.getValue().getRPM(), 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);
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(), 0.01);
assertEquals(33.53, current.getValue().getRPM(), 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);
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(0, current.getValue(), 0.01);
assertEquals(Cadence.of(0), current.getValue());
}
@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);
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 1024);
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 1024);
// when
current.compute(previous);
@@ -73,14 +74,14 @@ public class SensorDataCyclingTest {
@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);
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(), 0.01);
assertEquals(60, current.getValue().getRPM(), 0.01);
}
@Ignore("Disabled from #953")
@@ -88,21 +89,21 @@ public class SensorDataCyclingTest {
@Deprecated
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);
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(), 0.01);
assertEquals(60, current.getValue().getRPM(), 0.01);
}
@Test
public void compute_cadence_overflow() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
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);
@@ -171,8 +172,8 @@ public class SensorDataCyclingTest {
@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);
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress");
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
@@ -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 {
@@ -45,10 +45,10 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
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.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)
@@ -28,17 +28,17 @@ 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.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.SensorDataRunning;
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.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -742,7 +742,7 @@ public class TrackRecordingServiceTestRecording {
// when
String sensor2 = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(sensor2);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2))); //TODO Should be ignored; distance should be added to the next TrackPoint
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2)));
// when
String gps1 = "2020-02-02T02:02:05Z";
@@ -765,7 +765,7 @@ public class TrackRecordingServiceTestRecording {
// when
String sensor5 = "2020-02-02T02:02:10Z";
trackPointCreator.setClock(sensor5);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16)));
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16))); //Should be ignored
// when
String gps3 = "2020-02-02T02:02:12Z";
@@ -784,7 +784,7 @@ public class TrackRecordingServiceTestRecording {
// then
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor2)) // TODO Should be ignored; is stored as it assumed to be first in current segment.
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))
@@ -796,27 +796,21 @@ public class TrackRecordingServiceTestRecording {
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor3))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(10)),
/*
//TODO BUG Should be stored, but sensorDistance is used instead of distance to previous TRACKPOINT.
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(2)),
.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(2)),
*/
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps4))
.setLatitude(45.001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(4)),
.setSensorDistance(Distance.of(0)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setSensorDistance(Distance.of(11))
.setSpeed(Speed.of(5))
@@ -45,8 +45,8 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
/**
@@ -123,7 +123,7 @@ public class TrackRecordingServiceTestStateMachine {
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
// when
service.tryStartGps();
service.tryStartSensors();
Thread.sleep(1000);
// then
@@ -132,7 +132,7 @@ public class TrackRecordingServiceTestStateMachine {
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
// when
service.stopGps(true);
service.stopSensorsAndShutdown();
Thread.sleep(1000);
// then
@@ -153,7 +153,7 @@ public class TrackRecordingServiceTestStateMachine {
// then
assertTrue(service.isRecording());
assertEquals(new TrackRecordingService.RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
@@ -165,7 +165,7 @@ public class TrackRecordingServiceTestStateMachine {
// then
assertTrue(service.isRecording());
assertTrue(service.isPaused());
assertEquals(new TrackRecordingService.RecordingStatus(trackId, true), service.getRecordingStatusObservable().getValue());
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.
@@ -177,7 +177,7 @@ public class TrackRecordingServiceTestStateMachine {
// then
assertTrue(service.isRecording());
assertFalse(service.isPaused());
assertEquals(new TrackRecordingService.RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
@@ -201,7 +201,7 @@ public class TrackRecordingServiceTestStateMachine {
// then
assertTrue(service.isRecording());
assertFalse(service.isPaused());
assertEquals(new TrackRecordingService.RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
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());
@@ -252,13 +252,13 @@ public class TrackRecordingServiceTestStateMachine {
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
// when
service.stopGps(false); //TODO Should be ignored as service is recording
service.stopSensors(); //TODO Should be ignored as service is recording
Thread.sleep(1000);
// then
assertTrue(service.isRecording());
assertFalse(service.isPaused());
assertEquals(new TrackRecordingService.RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
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());
@@ -9,8 +9,8 @@ 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.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class TrackRecordingServiceTestUtils {
@@ -30,7 +30,6 @@ public class TrackRecordingServiceTestUtils {
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, service.getTrackPointCreator()));
service.getTrackPointCreator().setClock(Clock.systemUTC());
service.endCurrentTrack();
service.sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
}
static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
@@ -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.services.tasks;
package de.dennisguse.opentracks.services.announcement;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.util.Pair;
@@ -13,21 +15,18 @@ 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.services.tasks.AnnouncementUtils;
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.ui.intervals.IntervalStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class AnnouncementUtilsTest {
public class VoiceAnnouncementUtilsTest {
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
@@ -47,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);
@@ -63,12 +62,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, true, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
// then
Assert.assertEquals(
@@ -92,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);
@@ -108,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(
@@ -137,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);
@@ -153,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(
@@ -182,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);
@@ -198,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);
@@ -21,8 +21,8 @@ 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.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(MockitoJUnitRunner.class)
@@ -12,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 {
@@ -17,7 +17,7 @@ import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
@RunWith(AndroidJUnit4.class)
public class PreferencesOpenHelperTest {
@@ -1,4 +1,9 @@
package de.dennisguse.opentracks.util;
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;
@@ -11,20 +16,14 @@ 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 de.dennisguse.opentracks.settings.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;
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 {
@@ -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;
@@ -31,14 +33,12 @@ 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}.
*
@@ -82,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(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<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));
}
@@ -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}.
@@ -12,18 +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 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,7 +75,7 @@ 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();
@@ -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);
@@ -149,16 +147,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(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);
@@ -209,16 +207,16 @@ public class TrackStatisticsUpdaterTest {
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);
@@ -258,15 +256,15 @@ public class TrackStatisticsUpdaterTest {
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 {
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks;
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
@@ -15,10 +15,11 @@ 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 androidx.test.runner.AndroidJUnit4;
import org.hamcrest.core.IsInstanceOf;
import org.junit.Before;
@@ -28,10 +29,12 @@ 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.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
@LargeTest
@RunWith(AndroidJUnit4.class)
@@ -55,7 +58,7 @@ public class EspressoAggregatedFilterTest {
@Test
public void espressoAggregatedFilterTest() {
// open AggregatedStatisticsActivity through toolbar's menu item
onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
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());
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.ui.customRecordingLayout;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks;
package de.dennisguse.opentracks.ui.customRecordingLayout;
import static androidx.test.espresso.Espresso.onView;
@@ -8,23 +8,22 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.is;
import android.content.Context;
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 org.hamcrest.Description;
@@ -34,28 +33,24 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
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.
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.content.data;
package de.dennisguse.opentracks.ui.customRecordingLayout;
import android.content.Context;
import android.content.res.Resources;
@@ -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,18 +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;
import static org.junit.Assert.assertTrue;
@RunWith(JUnit4.class)
public class IntervalStatisticsTest {
@@ -41,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();
}
@@ -132,7 +132,7 @@ public class IntervalStatisticsTest {
TrackPoint tp = TestDataUtil.createTrackPoint(i);
tp.setAltitudeLoss(null);
contentProviderUtils.insertTrackPoint(tp, dummyTrack.getId());
trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0));
trackStatisticsUpdater.addTrackPoint(tp);
}
dummyTrack.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(dummyTrack);
@@ -149,7 +149,7 @@ public class IntervalStatisticsTest {
}
private void whenAndThen(Pair<Track.Id, TrackStatistics> trackWithStats, int numberOfPoints, float distanceInterval) {
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0));
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval));
Track.Id trackId = trackWithStats.first;
TrackStatistics trackStatistics = trackWithStats.second;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
@@ -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.
@@ -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 {
@@ -45,8 +45,8 @@ 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 de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
/**
* Tests for {@link StringUtils}.
@@ -26,11 +26,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
@@ -42,7 +41,7 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
public class TrackNameUtilsTest {
private static final Track.Id TRACK_ID = new Track.Id(1L);
private static final OffsetDateTime START_TIME = OffsetDateTime.parse("2022-01-02T10:15:30+01:00");
private static final OffsetDateTime START_TIME = OffsetDateTime.parse("2022-01-02T10:15:30.1234+01:00");
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
@@ -61,7 +60,7 @@ public class TrackNameUtilsTest {
@Test
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
assertEquals(StringUtils.formatDateTimeIso8601(START_TIME.toInstant(), START_TIME.getOffset()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
assertEquals("2022-01-02T10:15+01", 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);
+27 -8
View File
@@ -69,6 +69,23 @@ limitations under the License.
android:supportsRtl="true"
android:theme="@style/ThemeCustom">
<activity
android:name=".publicapi.StartRecording"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- <activity-->
<!-- android:name=".publicapi.StopRecording"-->
<!-- android:exported="true"-->
<!-- android:theme="@style/SplashTheme">-->
<!-- <intent-filter>-->
<!-- <category android:name="android.intent.category.DEFAULT" />-->
<!-- </intent-filter>-->
<!-- </activity>-->
<activity android:name=".AboutActivity" />
<activity
@@ -267,7 +284,7 @@ limitations under the License.
</activity>
<activity
android:name=".MarkerDetailActivity"
android:name=".ui.markers.MarkerDetailActivity"
android:exported="true">
<intent-filter>
<action android:name="de.dennisguse.opentracks.MarkerDetails" />
@@ -275,10 +292,10 @@ limitations under the License.
</intent-filter>
</activity>
<activity android:name=".MarkerEditActivity" />
<activity android:name=".ui.markers.MarkerEditActivity" />
<activity
android:name=".MarkerListActivity"
android:name=".ui.markers.MarkerListActivity"
android:exported="true"
android:label="@string/menu_markers"
android:launchMode="singleTop">
@@ -308,7 +325,9 @@ limitations under the License.
<activity android:name=".TrackRecordedActivity" />
<activity android:name=".TrackRecordingActivity" android:parentActivityName=".TrackListActivity">
<activity
android:name=".TrackRecordingActivity"
android:parentActivityName=".TrackListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".TrackListActivity" />
@@ -316,7 +335,7 @@ limitations under the License.
<activity android:name=".TrackEditActivity" />
<activity android:name=".AggregatedStatisticsActivity" />
<activity android:name=".ui.aggregatedStatistics.AggregatedStatisticsActivity" />
<activity android:name=".io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity" />
<activity android:name=".io.file.importer.DirectoryChooserActivity$DefaultTrackExportDirectoryChooserActivity" />
@@ -328,7 +347,7 @@ limitations under the License.
<activity android:name=".settings.SettingsCustomLayoutEditActivity" />
<activity
android:name=".ShowOnMapProxyActivity$KML"
android:name=".share.ShowOnMapProxyActivity$KML"
android:exported="true">
<intent-filter android:label="@string/open_track_as_KML">
<action android:name="Intent.OpenTracks-Dashboard" />
@@ -336,7 +355,7 @@ limitations under the License.
</intent-filter>
</activity>
<activity
android:name=".ShowOnMapProxyActivity$GPX"
android:name=".share.ShowOnMapProxyActivity$GPX"
android:exported="true">
<intent-filter android:label="@string/open_track_as_GPX">
<action android:name="Intent.OpenTracks-Dashboard" />
@@ -345,7 +364,7 @@ limitations under the License.
</activity>
<provider
android:name=".content.provider.ShareContentProvider"
android:name=".data.ShareContentProvider"
android:authorities="${applicationId}.content"
android:exported="false"
android:grantUriPermissions="true" />
@@ -6,8 +6,8 @@ import android.view.View;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.databinding.AboutBinding;
import de.dennisguse.opentracks.ui.util.ViewUtils;
import de.dennisguse.opentracks.util.SystemUtils;
import de.dennisguse.opentracks.util.ViewUtils;
public class AboutActivity extends AbstractActivity {
@@ -22,7 +22,8 @@ import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import de.dennisguse.opentracks.services.tasks.VoiceAnnouncement;
import de.dennisguse.opentracks.services.announcement.VoiceAnnouncement;
/**
* @author Jimmy Shih
@@ -22,11 +22,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.TrackDeleteService;
import de.dennisguse.opentracks.services.TrackDeleteServiceConnection;
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
/**
* An abstract class for the following common tasks across
@@ -96,11 +96,7 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
protected abstract Track.Id getRecordingTrackId();
@Override
public void connected() {
TrackDeleteService service = trackDeleteServiceConnection.getServiceIfBound();
if (service == null) {
return;
}
public void connected(TrackDeleteService service) {
service.getDeletingStatusObservable().observe(AbstractTrackDeleteActivity.this, this::onTrackDeleteStatus);
}
}
@@ -1,46 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.view.Menu;
/**
* Callback when items in the contextual action mode are selected.
*
* @author Jimmy Shih
*/
public interface ContextualActionModeCallback {
/**
* Invoked to prepare the menu for the selected items.
*
* @param menu the menu
* @param positions the selected items' positions
* @param ids the selected items' ids, if available
* @param showSelectAll true to show select all
*/
void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll);
/**
* Invoked when items are selected.
*
* @param itemId the context menu item id
* @param positions the selected items' positions
* @param ids the selected items' ids, if available
*/
boolean onClick(int itemId, int[] positions, long[] ids);
}
@@ -33,9 +33,11 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import de.dennisguse.opentracks.databinding.ControllerFragmentBinding;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.ui.util.ActivityUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
@@ -57,24 +59,14 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
private TransitionDrawable transition;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackRecordingService.RecordingStatus recordingStatus;
private TrackRecordingService.RecordingData recordingData;
private RecordingStatus recordingStatus;
private RecordingData recordingData;
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.w(TAG, "could not get TrackRecordingService");
return;
}
service.getRecordingStatusObservable()
.observe(ControllerFragment.this, status -> onRecordingStatusChanged(status));
service.getRecordingDataObservable()
.observe(ControllerFragment.this, recordingData -> onTotalTimeChanged(recordingData));
}
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
service.getRecordingStatusObservable()
.observe(ControllerFragment.this, this::onRecordingStatusChanged);
service.getRecordingDataObservable()
.observe(ControllerFragment.this, this::onTotalTimeChanged);
};
@Override
@@ -247,7 +239,7 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
void recordStop();
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
private void onRecordingStatusChanged(RecordingStatus status) {
recordingStatus = status;
viewBinding.controllerRecord.setImageResource(recordingStatus.isRecordingAndNotPaused() ? R.drawable.ic_button_pause : R.drawable.button_record);
@@ -264,7 +256,7 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
viewBinding.controllerTotalTime.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
}
private void onTotalTimeChanged(TrackRecordingService.RecordingData recordingData) {
private void onTotalTimeChanged(RecordingData recordingData) {
this.recordingData = recordingData;
viewBinding.controllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(this.recordingData.getTrackStatistics().getTotalTime()));
@@ -6,7 +6,7 @@ import android.view.View;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.databinding.HelpBinding;
import de.dennisguse.opentracks.util.ViewUtils;
import de.dennisguse.opentracks.ui.util.ViewUtils;
public class HelpActivity extends AbstractActivity {
@@ -1,10 +0,0 @@
package de.dennisguse.opentracks;
import de.dennisguse.opentracks.content.TrackDataHub;
/**
* Interface for communication between activities that use {@link de.dennisguse.opentracks.content.TrackDataHub} and their fragments that need thi data hub.
*/
public interface TrackActivityDataHubInterface {
TrackDataHub getTrackDataHub();
}
@@ -23,8 +23,8 @@ import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackEditBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -16,33 +16,26 @@
package de.dennisguse.opentracks;
import android.Manifest;
import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
@@ -55,20 +48,24 @@ import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.databinding.TrackListBinding;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.share.ShareUtils;
import de.dennisguse.opentracks.ui.aggregatedStatistics.AggregatedStatisticsActivity;
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
import de.dennisguse.opentracks.ui.util.ActivityUtils;
import de.dennisguse.opentracks.ui.util.ListItemUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -93,10 +90,10 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
private boolean metricUnits = true;
private GpsStatusValue gpsStatusValue = TrackRecordingService.STATUS_GPS_DEFAULT;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
// Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
private final ActivityUtils.ContextualActionModeCallback contextualActionModeCallback = new ActivityUtils.ContextualActionModeCallback() {
@Override
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
@@ -128,32 +125,21 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
private MenuItem searchMenuItem;
private MenuItem startGpsMenuItem;
// Callback when the trackRecordingServiceConnection binding changes.
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.e(TAG, "service not available to start gps or a new recording");
return;
}
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
service.getRecordingStatusObservable()
.observe(TrackListActivity.this, this::onRecordingStatusChanged);
service.getRecordingStatusObservable()
.observe(TrackListActivity.this, status -> onRecordingStatusChanged(status));
service.getGpsStatusObservable()
.observe(TrackListActivity.this, this::onGpsStatusChanged);
// Get GPS status and listen GPS status changes.
service.getGpsStatusObservable()
.observe(TrackListActivity.this, status -> onGpsStatusChanged(status));
updateGpsMenuItem(true, recordingStatus.isRecording());
updateGpsMenuItem(true, recordingStatus.isRecording());
if (service.getGpsStatusObservable().getValue().isGpsStarted()) {
return;
}
//TODO Not cool to do this in a callback that might be called more than once!
service.tryStartGps();
if (service.getGpsStatusObservable().getValue().isGpsStarted()) {
return;
}
//TODO Not cool to do this in a callback that might be called more than once!
service.tryStartSensors();
};
@Override
@@ -221,8 +207,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
ActivityUtils.configureListViewContextualMenu(viewBinding.trackList, contextualActionModeCallback);
loadData(getIntent());
requestGPSPermissions();
}
@Override
@@ -305,12 +289,11 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
} else {
// Invoke trackRecordingService
if (!gpsStatusValue.isGpsStarted()) {
trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run();
trackRecordingServiceConnection.startAndBindWithCallback(this);
} else {
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) {
trackRecordingService.stopGpsAndShutdown();
trackRecordingService.stopSensorsAndShutdown(); //TODO Handle this in TrackRecordingServiceConnection
}
trackRecordingServiceConnection.unbindAndStop(this);
}
@@ -401,26 +384,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
return recordingStatus.getTrackId();
}
private void requestGPSPermissions() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
return;
}
ActivityResultLauncher<String[]> locationPermissionRequest = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
Boolean fineLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
Boolean coarseLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false);
if (fineLocationGranted == null || !fineLocationGranted
|| coarseLocationGranted == null || !coarseLocationGranted) {
Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show();
finish();
}
}
);
String[] permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
locationPermissionRequest.launch(permissions);
}
/**
* Updates the menu items with the icon specified.
*
@@ -460,7 +423,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
}
if (itemId == R.id.list_context_menu_share) {
Intent intent = IntentUtils.newShareFileIntent(this, trackIds);
Intent intent = ShareUtils.newShareFileIntent(this, trackIds);
intent = Intent.createChooser(intent, null);
startActivity(intent);
return true;
@@ -579,7 +542,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
updateGpsMenuItem(true, recordingStatus.isRecording());
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
private void onRecordingStatusChanged(RecordingStatus status) {
recordingStatus = status;
}
}
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
@@ -33,21 +32,24 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.google.android.material.tabs.TabLayoutMediator;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.chart.ChartFragment;
import de.dennisguse.opentracks.chart.TrackDataHubInterface;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.IntervalsFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackDeleteService;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.share.ShareUtils;
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* An activity to show the track detail, record a new track or resumes an existing one.
@@ -56,7 +58,7 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
* @author Rodrigo Damazio
*/
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
public class TrackRecordedActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
public class TrackRecordedActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackDataHubInterface {
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
@@ -73,33 +75,12 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
private TrackRecordedBinding viewBinding;
private Track.Id trackId;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private final Runnable bindCallback = new Runnable() {
@Override
public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.w(TAG, "could not get TrackRecordingService");
return;
}
service.getRecordingStatusObservable()
.observe(TrackRecordedActivity.this, status -> onRecordingStatusChanged(status));
}
};
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
}
}
};
private final TrackRecordingServiceConnection.Callback bindCallback = service -> service.getRecordingStatusObservable()
.observe(TrackRecordedActivity.this, this::onRecordingStatusChanged);
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -128,8 +109,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
protected void onStart() {
super.onStart();
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackDataHub.start();
}
@@ -152,7 +131,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
super.onStop();
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -192,7 +170,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.track_detail_share) {
Intent intent = Intent.createChooser(IntentUtils.newShareFileIntent(this, trackId), null);
Intent intent = Intent.createChooser(ShareUtils.newShareFileIntent(this, trackId), null);
startActivity(intent);
return true;
}
@@ -322,7 +300,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
startPostponedEnterTransition();
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
private void onRecordingStatusChanged(RecordingStatus status) {
recordingStatus = status;
}
}
@@ -1,8 +1,8 @@
package de.dennisguse.opentracks;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Build;
import android.os.Bundle;
@@ -11,30 +11,40 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.List;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.chart.ChartFragment;
import de.dennisguse.opentracks.chart.TrackDataHubInterface;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackRecordingBinding;
import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.IntervalsFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
@@ -46,7 +56,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
*/
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
//This mode of communication should be removed anyhow.
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, ControllerFragment.Callback {
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackDataHubInterface, ControllerFragment.Callback {
public static final String EXTRA_TRACK_ID = "track_id";
@@ -54,6 +64,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
private Snackbar snackbar;
// The following are setFrequency in onCreate
private ContentProviderUtils contentProviderUtils;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -65,55 +77,43 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Deprecated
private Track.Id trackId;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.w(TAG, "could not get TrackRecordingService");
return;
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
service.getRecordingStatusObservable()
.observe(TrackRecordingActivity.this, this::onRecordingStatusChanged);
service.getGpsStatusObservable()
.observe(TrackRecordingActivity.this, this::onGpsStatusChanged);
if (!service.isRecording()) {
if (trackId == null) {
// trackId isn't initialized -> leads a new recording.
trackId = service.startNewTrack();
} else {
// trackId is initialized -> resumes the track.
service.resumeTrack(trackId);
}
service.getRecordingStatusObservable()
.observe(TrackRecordingActivity.this, status -> onRecordingStatusChanged(status));
if (!service.isRecording()) {
if (trackId == null) {
// trackId isn't initialized -> leads a new recording.
trackId = service.startNewTrack();
} else {
// trackId is initialized -> resumes the track.
service.resumeTrack(trackId);
}
// A recording track is on.
trackDataHub.loadTrack(trackId);
trackDataHub.setRecordingStatus(recordingStatus);
}
// A recording track is on.
trackDataHub.loadTrack(trackId);
trackDataHub.setRecordingStatus(recordingStatus);
}
};
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(R.string.stats_show_on_lockscreen_while_recording_key, key)) {
setLockscreenPolicy();
}
if (PreferencesUtils.isKey(R.string.stats_keep_screen_on_while_recording_key, key)) {
setScreenOnPolicy();
}
if (PreferencesUtils.isKey(R.string.stats_fullscreen_while_recording_key, key)) {
setFullscreenPolicy();
}
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
}
if (key == null) return;
runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu);
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(R.string.stats_show_on_lockscreen_while_recording_key, key)) {
setLockscreenPolicy();
}
if (PreferencesUtils.isKey(R.string.stats_keep_screen_on_while_recording_key, key)) {
setScreenOnPolicy();
}
if (PreferencesUtils.isKey(R.string.stats_fullscreen_while_recording_key, key)) {
setFullscreenPolicy();
}
if (key == null) return;
runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu); //TODO Should not be necessary
};
private MenuItem insertMarkerMenuItem;
@@ -146,6 +146,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
if (savedInstanceState != null) {
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
}
requestGPSPermissions();
}
@Override
@@ -211,13 +213,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
trackDataHub.setRecordingStatus(recordingStatus);
}
/*
* If the binding has happened, then invoke the callback to start a new recording.
* If the binding hasn't happened, then invoking the callback will have no effect.
* But when the binding occurs, the callback will get invoked.
*/
trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run();
trackRecordingServiceConnection.startAndBindWithCallback(this);
}
@Override
@@ -328,10 +324,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
setTitle(getString(isPaused ? R.string.generic_paused : R.string.generic_recording));
}
public void chooseActivityType(String category) {
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
}
@Override
public void onChooseActivityTypeDone(String iconValue) {
Track track = contentProviderUtils.getTrack(trackId);
@@ -406,7 +398,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
private void onRecordingStatusChanged(RecordingStatus status) {
recordingStatus = status;
trackDataHub.setRecordingStatus(recordingStatus);
@@ -414,4 +406,38 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
setLockscreenPolicy();
setScreenOnPolicy();
}
}
private void requestGPSPermissions() {
if (PermissionUtils.hasGPSPermission(this)) {
return;
}
ActivityResultLauncher<String[]> locationPermissionRequest = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
Boolean fineLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
Boolean coarseLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false);
if (fineLocationGranted == null || !fineLocationGranted
|| coarseLocationGranted == null || !coarseLocationGranted) {
Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show();
}
}
);
String[] permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
locationPermissionRequest.launch(permissions);
}
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
if (gpsStatusValue.isGpsStarted() && snackbar != null && snackbar.isShown()) {
snackbar.dismiss();
return;
}
if (gpsStatusValue != GpsStatusValue.GPS_DISABLED) {
return;
}
snackbar = Snackbar
.make(viewBinding.trackRecordingCoordinatorLayout,
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.generic_dismiss), v -> {});
snackbar.show();
}
}
@@ -14,7 +14,7 @@ import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.CustomLayoutFieldType;
import de.dennisguse.opentracks.ui.customRecordingLayout.CustomLayoutFieldType;
import de.dennisguse.opentracks.viewmodels.StatisticData;
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@@ -14,7 +14,7 @@
* the License.
*/
package de.dennisguse.opentracks.fragments;
package de.dennisguse.opentracks.chart;
import android.app.Activity;
import android.content.SharedPreferences;
@@ -31,15 +31,12 @@ import java.util.Collections;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackActivityDataHubInterface;
import de.dennisguse.opentracks.chart.ChartPoint;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.data.Altitude;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Altitude;
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.databinding.ChartBinding;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -52,7 +49,7 @@ import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class ChartFragment extends Fragment implements TrackDataListener {
public class ChartFragment extends Fragment implements TrackDataHub.Listener {
private static final String KEY_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
@@ -243,7 +240,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackActivityDataHubInterface) getActivity()).getTrackDataHub();
trackDataHub = ((TrackDataHubInterface) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this);
}
@@ -3,12 +3,12 @@ package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
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.TrackPoint;
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.data.models.UnitConversions;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.UnitConversions;
public class ChartPoint {
//X-axis
@@ -44,13 +44,13 @@ public class ChartPoint {
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
}
if (trackPoint.hasHeartRate()) {
heartRate = (double) trackPoint.getHeartRate_bpm();
heartRate = (double) trackPoint.getHeartRate().getBPM();
}
if (trackPoint.hasCadence()) {
cadence = (double) trackPoint.getCadence_rpm();
cadence = (double) trackPoint.getCadence().getRPM();
}
if (trackPoint.hasPower()) {
power = (double) trackPoint.getPower();
power = (double) trackPoint.getPower().getW();
}
}
@@ -41,16 +41,17 @@ import androidx.core.view.GestureDetectorCompat;
import java.text.NumberFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import de.dennisguse.opentracks.MarkerDetailActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.stats.ExtremityMonitor;
import de.dennisguse.opentracks.ui.markers.MarkerDetailActivity;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.ui.util.ThemeUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.ThemeUtils;
/**
* Visualization of the chart.
@@ -82,12 +83,12 @@ public class ChartView extends View {
X_FRACTION_FORMAT.setMinimumFractionDigits(1);
}
private final List<ChartValueSeries> seriesList = new ArrayList<>();
private final List<ChartValueSeries> seriesList = new LinkedList<>();
private final ChartValueSeries speedSeries;
private final ChartValueSeries paceSeries;
private final List<ChartPoint> chartPoints = new ArrayList<>();
private final List<Marker> markers = new ArrayList<>();
private final LinkedList<ChartPoint> chartPoints = new LinkedList<>();
private final List<Marker> markers = new LinkedList<>();
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
private final int backgroundColor;
private final Paint axisPaint;
@@ -834,22 +835,22 @@ public class ChartView extends View {
return paint.measureText(marker);
}
/**
* Draws the current pointer.
*
* @param canvas the canvas
*/
private void drawPointer(Canvas canvas) {
if (chartPoints.isEmpty()) {
return;
}
ChartPoint last = chartPoints.getLast();
ChartValueSeries firstChartValueSeries = null;
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() && chartValueSeries.isChartPointValid(last)) {
firstChartValueSeries = chartValueSeries;
break;
}
}
if (firstChartValueSeries != null && chartPoints.size() > 0) {
int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2;
double value = firstChartValueSeries.extractDataFromChartPoint(chartPoints.get(chartPoints.size() - 1));
double value = firstChartValueSeries.extractDataFromChartPoint(last);
int dy = getY(firstChartValueSeries, value) - pointer.getIntrinsicHeight();
canvas.translate(dx, dy);
pointer.draw(canvas);
@@ -0,0 +1,10 @@
package de.dennisguse.opentracks.chart;
import de.dennisguse.opentracks.data.TrackDataHub;
/**
* Interface for communication between activities that use {@link TrackDataHub} and their fragments that need thi data hub.
*/
public interface TrackDataHubInterface {
TrackDataHub getTrackDataHub();
}
@@ -1,91 +0,0 @@
/*
* Copyright 2011 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.content;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.content.data.Altitude;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Listener for track data changes.
*
* @author Rodrigo Damazio
*/
public interface TrackDataListener {
/**
* Called when the track or its statistics has been updated.
*
* @param track the track
*/
//TODO Could be @NonNull
void onTrackUpdated(Track track);
/**
* Called to clear previously-sent track points.
*/
void clearTrackPoints();
/**
* Called when a sampled in track point is read.
*
* @param trackPoint the trackPoint
*/
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, @Nullable Altitude smoothedAltitude_m) {
}
/**
* Called when a sampled out track point is read.
*
* @param trackPoint the trackPoint
*/
default void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
}
/**
* Called when finish sending new track points.
*/
default void onNewTrackPointsDone() {
}
/**
* Called to clear previously sent markers.
*/
default void clearMarkers() {
}
/**
* Called when a new marker is read.
*
* @param marker the marker
*/
default void onNewMarker(@NonNull Marker marker) {
}
/**
* Called when finish sending new markers.
* This gets called after every batch of calls to {@link #clearMarkers()} and {@link #onNewMarker(Marker)}.
*/
default void onNewMarkersDone() {
}
}
@@ -1,27 +0,0 @@
package de.dennisguse.opentracks.content.sensor;
import androidx.annotation.NonNull;
public class SensorDataCyclingPower extends SensorData<Float> {
public SensorDataCyclingPower(String address) {
super(address);
}
public SensorDataCyclingPower(String name, String address, float power_w) {
super(name, address);
this.value = power_w;
}
@NonNull
@Override
public String toString() {
return super.toString() + " power=" + value;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
}
@@ -1,27 +0,0 @@
package de.dennisguse.opentracks.content.sensor;
import androidx.annotation.NonNull;
public class SensorDataHeartRate extends SensorData<Float> {
public SensorDataHeartRate(String address) {
super(address);
}
public SensorDataHeartRate(String name, String address, float heartRate_bpm) {
super(name, address);
this.value = heartRate_bpm;
}
@NonNull
@Override
public String toString() {
return super.toString() + " heart=" + value;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
}
@@ -1,8 +1,8 @@
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import android.database.Cursor;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
/**
* A cache of track points indexes.
@@ -14,7 +14,7 @@
* the License.
*/
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -40,19 +40,22 @@ import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.content.data.Altitude;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.data.models.Altitude;
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.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.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.UUIDUtils;
/**
* {@link ContentProviderUtils} implementation.
@@ -475,7 +478,7 @@ public class ContentProviderUtils {
private void deleteMarkerPhoto(Context context, Marker marker) {
if (marker != null && marker.hasPhoto()) {
Uri uri = marker.getPhotoURI();
File file = FileUtils.buildInternalPhotoFile(context, marker.getTrackId(), uri);
File file = MarkerUtils.buildInternalPhotoFile(context, marker.getTrackId(), uri);
if (file.exists()) {
File parent = file.getParentFile();
file.delete();
@@ -578,10 +581,10 @@ public class ContentProviderUtils {
}
if (!cursor.isNull(indexes.sensorHeartRateIndex)) {
trackPoint.setHeartRate_bpm(cursor.getFloat(indexes.sensorHeartRateIndex));
trackPoint.setHeartRate(cursor.getFloat(indexes.sensorHeartRateIndex));
}
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
trackPoint.setCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
trackPoint.setCadence(cursor.getFloat(indexes.sensorCadenceIndex));
}
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
@@ -740,16 +743,16 @@ public class ContentProviderUtils {
}
if (trackPoint.hasHeartRate()) {
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate_bpm());
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate().getBPM());
}
if (trackPoint.hasCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCadence_rpm());
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCadence().getRPM());
}
if (trackPoint.hasSensorDistance()) {
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM());
}
if (trackPoint.hasPower()) {
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower().getW());
}
if (trackPoint.hasAltitudeGain()) {
@@ -832,12 +835,17 @@ public class ContentProviderUtils {
SensorStatistics sensorStatistics = null;
try (Cursor cursor = contentResolver.query(ContentUris.withAppendedId(TracksColumns.CONTENT_URI_SENSOR_STATS, trackId.getId()), null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
final int MAX_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_HR);
final int AVG_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_HR);
final int MAX_CADENCE_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_CADENCE);
final int AVG_CADENCE_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_CADENCE);
final int AVG_POWER_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_POWER);
sensorStatistics = new SensorStatistics(
!cursor.isNull(cursor.getColumnIndexOrThrow("max_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_HR)) : null,
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_hr")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_HR)) : null,
!cursor.isNull(cursor.getColumnIndexOrThrow("max_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_CADENCE)) : null,
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_cadence")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_CADENCE)) : null,
!cursor.isNull(cursor.getColumnIndexOrThrow("avg_power")) ? cursor.getFloat(cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_POWER)) : null
!cursor.isNull(MAX_HR_INDEX) ? HeartRate.of(cursor.getFloat(MAX_HR_INDEX)) : null,
!cursor.isNull(AVG_HR_INDEX) ? HeartRate.of(cursor.getFloat(AVG_HR_INDEX)) : null,
!cursor.isNull(MAX_CADENCE_INDEX) ? Cadence.of(cursor.getFloat(MAX_CADENCE_INDEX)) : null,
!cursor.isNull(AVG_CADENCE_INDEX) ? Cadence.of(cursor.getFloat(AVG_CADENCE_INDEX)) : null,
!cursor.isNull(AVG_POWER_INDEX) ? Power.of(cursor.getFloat(AVG_POWER_INDEX)) : null
);
}
@@ -14,7 +14,7 @@
* the License.
*/
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -35,10 +35,10 @@ import androidx.annotation.VisibleForTesting;
import java.util.Arrays;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
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;
/**
* A {@link ContentProvider} that handles access to track points, tracks, and markers tables.
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import android.content.ContentValues;
import android.content.Context;
@@ -15,11 +15,10 @@ import java.time.zone.ZoneRules;
import java.util.UUID;
import de.dennisguse.opentracks.Startup;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.util.UUIDUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns;
/**
* Database helper for creating and upgrading the database.
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
public class SelectionData {
private final String selection;
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import android.content.Context;
import android.content.UriMatcher;
@@ -25,8 +25,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
@@ -66,8 +66,6 @@ public class ShareContentProvider extends CustomContentProvider {
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.SHARE_PICTURE_PNG.getName() + "/*/*", URI_SHARE_PICTURE);
}
/**
@@ -96,7 +94,7 @@ public class ShareContentProvider extends CustomContentProvider {
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + trackIdBuilder + "/" + Uri.encode(trackName) + "." + trackFileFormat.getExtension());
String mime = getTypeMime(uri);
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
Log.d(TAG, "Created uri " + uri + " with MIME " + mime);
return new Pair<>(uri, mime);
}
@@ -148,9 +146,6 @@ public class ShareContentProvider extends CustomContentProvider {
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES:
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
case URI_SHARE_PICTURE:
return TrackFileFormat.SHARE_PICTURE_PNG;
default:
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
}
@@ -14,7 +14,7 @@
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.data;
import android.content.ContentResolver;
import android.content.Context;
@@ -25,28 +25,30 @@ import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
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.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Altitude;
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.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
/**
* Track data hub.
* Receives data from {@link de.dennisguse.opentracks.content.provider.CustomContentProvider} and distributes it to {@link TrackDataListener} after some processing.
* Receives data from {@link CustomContentProvider} and distributes it to {@link Listener} after some processing.
* <p>
* {@link TrackPoint}s are filtered/downsampled with a dynamic sampling frequency.
*
@@ -70,7 +72,7 @@ public class TrackDataHub {
private static final String TAG = TrackDataHub.class.getSimpleName();
private final Context context;
private final Set<TrackDataListener> listeners;
private final Set<Listener> listeners;
private final ContentProviderUtils contentProviderUtils;
private final int targetNumPoints;
@@ -82,9 +84,7 @@ public class TrackDataHub {
private Track.Id selectedTrackId;
private Distance recordingDistanceInterval;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
// Track points sampling state
private int numLoadedPoints;
@@ -178,11 +178,11 @@ public class TrackDataHub {
}
/**
* Registers a {@link TrackDataListener}.
* Registers a {@link Listener}.
*
* @param trackDataListener the track data listener
*/
public void registerTrackDataListener(final TrackDataListener trackDataListener) {
public void registerTrackDataListener(final Listener trackDataListener) {
handler.post(() -> {
listeners.add(trackDataListener);
if (isStarted()) {
@@ -192,11 +192,11 @@ public class TrackDataHub {
}
/**
* Unregisters a {@link TrackDataListener}.
* Unregisters a {@link Listener}.
*
* @param trackDataListener the track data listener
*/
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
public void unregisterTrackDataListener(final Listener trackDataListener) {
handler.post(() -> listeners.remove(trackDataListener));
}
@@ -218,7 +218,7 @@ public class TrackDataHub {
notifyTracksTableUpdate(listeners);
for (TrackDataListener listener : listeners) {
for (Listener listener : listeners) {
listener.clearTrackPoints();
}
notifyTrackPointsTableUpdate(true, listeners);
@@ -230,8 +230,8 @@ public class TrackDataHub {
*
* @param trackDataListener the track data listener.
*/
private void loadDataForListener(TrackDataListener trackDataListener) {
Set<TrackDataListener> trackDataListeners = Collections.singleton(trackDataListener);
private void loadDataForListener(Listener trackDataListener) {
Set<Listener> trackDataListeners = Collections.singleton(trackDataListener);
//Track
notifyTracksTableUpdate(trackDataListeners);
@@ -253,12 +253,12 @@ public class TrackDataHub {
*
* @param trackDataListeners the track data listeners to notify
*/
private void notifyTracksTableUpdate(Set<TrackDataListener> trackDataListeners) {
private void notifyTracksTableUpdate(Set<Listener> trackDataListeners) {
if (trackDataListeners.isEmpty()) {
return;
}
Track track = contentProviderUtils.getTrack(selectedTrackId);
for (TrackDataListener trackDataListener : trackDataListeners) {
for (Listener trackDataListener : trackDataListeners) {
trackDataListener.onTrackUpdated(track);
}
}
@@ -269,12 +269,12 @@ public class TrackDataHub {
*
* @param trackDataListeners the track data listeners to notify
*/
private void notifyMarkersTableUpdate(Set<TrackDataListener> trackDataListeners) {
private void notifyMarkersTableUpdate(Set<Listener> trackDataListeners) {
if (trackDataListeners.isEmpty()) {
return;
}
for (TrackDataListener trackDataListener : trackDataListeners) {
for (Listener trackDataListener : trackDataListeners) {
trackDataListener.clearMarkers();
}
@@ -282,14 +282,14 @@ public class TrackDataHub {
if (cursor != null && cursor.moveToFirst()) {
do {
Marker marker = contentProviderUtils.createMarker(cursor);
for (TrackDataListener trackDataListener : trackDataListeners) {
for (Listener trackDataListener : trackDataListeners) {
trackDataListener.onNewMarker(marker);
}
} while (cursor.moveToNext());
}
}
for (TrackDataListener trackDataListener : trackDataListeners) {
for (Listener trackDataListener : trackDataListeners) {
trackDataListener.onNewMarkersDone();
}
}
@@ -299,7 +299,7 @@ public class TrackDataHub {
*
* @param updateSamplingState true to update the sampling state
*/
private void notifyTrackPointsTableUpdate(boolean updateSamplingState, Set<TrackDataListener> listeners) {
private void notifyTrackPointsTableUpdate(boolean updateSamplingState, Set<Listener> listeners) {
if (listeners.isEmpty()) {
return;
}
@@ -308,7 +308,7 @@ public class TrackDataHub {
// Reload and resample the track at a lower frequency.
Log.i(TAG, "Resampling track after " + numLoadedPoints + " points.");
resetSamplingState();
for (TrackDataListener listener : listeners) {
for (Listener listener : listeners) {
listener.clearTrackPoints();
}
}
@@ -336,13 +336,13 @@ public class TrackDataHub {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
while (trackPointIterator.hasNext()) {
if (!isStarted()) {
break;
}
//Prevents a NPE if stop() is happening while notifyTrackPointsTableUpdate()
TrackStatisticsUpdater currentUpdater = trackStatisticsUpdater;
if (!isStarted()) {
return;
}
trackPoint = trackPointIterator.next();
TrackPoint.Id trackPointId = trackPoint.getId();
@@ -362,15 +362,15 @@ public class TrackDataHub {
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
currentUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
currentUpdater.addTrackPoint(trackPoint);
// Also include the last point if the selected track is not recording.
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
for (TrackDataListener trackDataListener : listeners) {
for (Listener trackDataListener : listeners) {
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics(), currentUpdater.getSmoothedSpeed(), currentUpdater.getSmoothedAltitude());
}
} else {
for (TrackDataListener trackDataListener : listeners) {
for (Listener trackDataListener : listeners) {
trackDataListener.onSampledOutTrackPoint(trackPoint, currentUpdater.getTrackStatistics());
}
}
@@ -389,7 +389,7 @@ public class TrackDataHub {
lastSeenTrackPointId = localLastSeenTrackPointIdId;
}
listeners.stream().forEach(TrackDataListener::onNewTrackPointsDone);
listeners.stream().forEach(Listener::onNewTrackPointsDone);
}
@@ -408,11 +408,66 @@ public class TrackDataHub {
return handlerThread != null;
}
public void setRecordingStatus(TrackRecordingService.RecordingStatus recordingStatus) {
public void setRecordingStatus(RecordingStatus recordingStatus) {
this.recordingStatus = recordingStatus;
}
public void setRecordingDistanceInterval(Distance recordingDistanceInterval) {
this.recordingDistanceInterval = recordingDistanceInterval;
public interface Listener {
/**
* Called when the track or its statistics has been updated.
*
* @param track the track
*/
//TODO Could be @NonNull
void onTrackUpdated(Track track);
/**
* Called to clear previously-sent track points.
*/
void clearTrackPoints();
/**
* Called when a sampled in track point is read.
*
* @param trackPoint the trackPoint
*/
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, @Nullable Altitude smoothedAltitude_m) {
}
/**
* Called when a sampled out track point is read.
*
* @param trackPoint the trackPoint
*/
default void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
}
/**
* Called when finish sending new track points.
*/
default void onNewTrackPointsDone() {
}
/**
* Called to clear previously sent markers.
*/
default void clearMarkers() {
}
/**
* Called when a new marker is read.
*
* @param marker the marker
*/
default void onNewMarker(@NonNull Marker marker) {
}
/**
* Called when finish sending new markers.
* This gets called after every batch of calls to {@link #clearMarkers()} and {@link #onNewMarker(Marker)}.
*/
default void onNewMarkersDone() {
}
}
}
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.content.provider;
package de.dennisguse.opentracks.data;
import android.database.Cursor;
@@ -8,8 +8,8 @@ import androidx.annotation.VisibleForTesting;
import java.util.Iterator;
import java.util.NoSuchElementException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
/**
* A lightweight wrapper around the original {@link Cursor}.
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.content.data;
package de.dennisguse.opentracks.data;
import android.text.TextUtils;
@@ -8,8 +8,8 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.SelectionData;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.tables.TracksColumns;
public class TrackSelection implements ContentProviderUtils.ContentProviderSelectionInterface {
private final List<Track.Id> trackIds = new ArrayList<>();
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.data;
import androidx.annotation.NonNull;
@@ -1,4 +1,6 @@
package de.dennisguse.opentracks.content.data;
package de.dennisguse.opentracks.data.models;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.R;
@@ -60,6 +62,7 @@ public abstract class Altitude {
}
}
@NonNull
@Override
public String toString() {
return "Altitude{" +
@@ -0,0 +1,43 @@
package de.dennisguse.opentracks.data.models;
import androidx.annotation.NonNull;
import java.util.Objects;
public class Cadence {
public static Cadence of(float value) {
return new Cadence(value);
}
private final float value;
private Cadence(float value) {
this.value = value;
}
public float getRPM() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cadence cadence = (Cadence) o;
return Float.compare(cadence.value, value) == 0;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@NonNull
@Override
public String toString() {
return "Cadence{" +
"value=" + value + " rpm" +
'}';
}
}
@@ -1,11 +1,9 @@
package de.dennisguse.opentracks.content.data;
package de.dennisguse.opentracks.data.models;
import androidx.annotation.NonNull;
import java.util.Objects;
import de.dennisguse.opentracks.util.UnitConversions;
public class Distance {
public static Distance of(double distance_m) {
@@ -0,0 +1,43 @@
package de.dennisguse.opentracks.data.models;
import androidx.annotation.NonNull;
import java.util.Objects;
public class HeartRate {
public static HeartRate of(float value) {
return new HeartRate(value);
}
private final float value;
private HeartRate(float value) {
this.value = value;
}
public float getBPM() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HeartRate heartRate = (HeartRate) o;
return Float.compare(heartRate.value, value) == 0;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@NonNull
@Override
public String toString() {
return "HeartRate{" +
"value=" + value + " bpm" +
'}';
}
}
@@ -14,7 +14,7 @@
* the License.
*/
package de.dennisguse.opentracks.content.data;
package de.dennisguse.opentracks.data.models;
import android.location.Location;
import android.net.Uri;

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