Compare commits

...

132 Commits

Author SHA1 Message Date
Dennis Guse 183b47a1ea Release: v3.23.0 2021-12-21 22:48:35 +01:00
mondstern bc84e8cb9a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (466 of 466 strings)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixed #1007.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixes #968.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-09-22 22:30:42 +02:00
179 changed files with 5384 additions and 2214 deletions
+1
View File
@@ -1,4 +1,5 @@
# These are supported funding model platforms
liberapay: OpenTracks
github: [rgmf, pstorch, dennisguse]
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
- uses: actions/setup-java@v2
with:
@@ -29,6 +29,6 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: OpenTracks-nightly.apk
path: ./build/outputs/apk/nightly/OpenTracks-nightly.apk
path: ./build/outputs/apk/nightly/*.apk
retention-days: 7
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
- uses: actions/setup-java@v2
with:
+4 -3
View File
@@ -7,11 +7,12 @@ jobs:
runs-on: macos-latest
strategy:
matrix:
api-level: [ 23, 26 ] #TODO Re-enable 29
api-level: [ 23, 30 ]
target: [ default ]
fail-fast: false
steps:
- name: checkout
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
- uses: actions/setup-java@v2
with:
@@ -19,7 +20,7 @@ jobs:
java-version: '11'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.20.0
uses: reactivecircus/android-emulator-runner@v2.21.0
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
+1 -1
View File
@@ -6,4 +6,4 @@
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
</project>
+10 -1
View File
@@ -47,6 +47,15 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
</td>
<td></td>
</tr>
<tr>
<td></td>
<td align="center">
<a href="https://www.amazon.de/dp/B09K2KNQ7X/">
<img alt="Get it on Amazon App Store" src="https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-white.png" height="40" align="middle">
</a>
</td>
<td></td>
</tr>
</table>
## Screenshots
@@ -132,4 +141,4 @@ Artwork, logos and user interface remained more or less unchanged.
More information about _Google My Tracks_:
* [Wikipedia page](https://en.wikipedia.org/wiki/MyTracks)
* [Google code archive](https://code.google.com/archive/p/mytracks/)
* [Google code archive](https://code.google.com/archive/p/mytracks/)
+25 -4
View File
@@ -1,6 +1,6 @@
# Overview of tested Bluetooth LE sensors
## HRS: Heartrate Service
## 0x180D: Heartrate Service
* Decathlon|Geonaute|Kalenji Dual HRM Belt
* moofit heart rate monitor
@@ -13,7 +13,7 @@
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
* Garmin HRM-Dual (Reference: 010-12883-00)
## CSCP: Cycling Cadence and Speed Service
## 0x1816: Cycling Cadence and Speed Service
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
However, often only one value is provided.
@@ -36,7 +36,7 @@ However, often only one value is provided.
This sensor reports cadence data as speed.
A workaround is in place.
## Cycling Power Measurement
## 0x1818: Power Measurement
We only support _Instantaneous Power_.
@@ -44,6 +44,7 @@ _WARNING_: we only support physically movements with valid GPS coordinates.
Using a device indoors (no GPS available) will only show the current power values, but won't store anything.
Also the distance is not computed.
For cycling:
* Assioma Duo Powermeter
* Elite drivo II
* QUARQ Red DZero Powermeter
@@ -51,8 +52,28 @@ Also the distance is not computed.
* Wahoo Kickr v4.0
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061)
## RSC: running cadence and speed
For running:
* Stryd
## 0x1814: running cadence and speed
We do not support type of movement (i.e., walking vs running).
* Polar Stride
* Stryd (incl. power)
## Tested Barometric Sensor Smartphones
There are smartphones that have a barometric sensor that OpenTracks uses to compute altitude gain and loss.
In our tests there are smartphones whose barometric sensor works well and others don't. Here is a list of the smartphones tested:
|Brand|Model|Android Version|Tests|
|-----|-----|---------------|-----------|
|Google|Pixel|LineageOS 17.1|Ok|
|Google|Pixel 2 XL|LineageOS 17.1|Ok on the bike handlebars but it fails when I carry it in a pocket or backpack|
|Google|Pixel 3|Android 11|Failure|
|Google|Pixel 3a|Android 11|Ok|
|Google|Pixel 3a|Lineage 18.1|Ok|
|Google|Pixel 4a|Android 11|Ok|
|Sony|Xperia XZ1 Compact|LineageOS 16.1|Failure|
+29 -9
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.2'
classpath 'com.android.tools.build:gradle:7.0.4'
}
}
@@ -30,6 +30,19 @@ def getVersionName = { ->
}
}
def getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', 'HEAD', '--count'
standardOutput = stdout
}
return Integer.valueOf(stdout.toString().trim())
} catch (ignored) {
return null
}
}
android {
compileOptions {
coreLibraryDesugaringEnabled true
@@ -41,7 +54,7 @@ android {
compileSdkVersion 31
buildFeatures {
viewBinding = true
viewBinding true
}
aaptOptions {
@@ -50,8 +63,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4448
versionName "v3.20.2"
versionCode 4580
versionName "v3.23.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -59,7 +72,7 @@ android {
minSdkVersion 21
targetSdkVersion 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
signingConfigs {
@@ -82,7 +95,6 @@ android {
nightly {
signingConfig signingConfigs.nightly
applicationIdSuffix ".nightly"
versionNameSuffix "-nightly"
}
release {
@@ -97,13 +109,21 @@ android {
applicationVariants.all { variant ->
variant.resValue "string", "applicationId", variant.applicationId
if (variant.buildType.name == 'nightly') {
variant.outputs.all {
setVersionCodeOverride(getVersionCode())
setVersionNameOverride(getVersionName())
outputFileName = "${applicationId}_${variant.versionCode}.apk"
}
}
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.4.0'
@@ -114,7 +134,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.mockito:mockito-android:3.12.4'
androidTestImplementation 'org.mockito:mockito-android:4.2.0'
androidTestUtil 'androidx.test:orchestrator:1.4.0'
androidTestUtil 'androidx.test:orchestrator:1.4.1'
}
@@ -0,0 +1,19 @@
v3.21.0: OpenTracks
Changes:
- While recording: remove idle/non-idle creation of TrackPoints
- Settings are now in separate folders
- Opening OpenTracks from notification (while recording) and pressing back opens track list
- GPX compatibilty: support <speed> for import
Bugfixes:
- Altitude and distance (from BlE sensor): only reset counters if data was actually stored
- Import without speed, requires computing speed
- BLE running sensor not connected, should not be reported as present
- BLE running sensor: show data while recording
- Activity type icon was not updated while changing activity
Developer:
- SharedPrefences via static reference
- Tests are executed against in-memory database
- TrackRecordingManager keeps last TrackPoint in memory
@@ -0,0 +1,11 @@
v3.22.0: OpenTracks
Changes:
- Locations are only stored if traveled distance > recording distance interval
(removed left over functionality from idle/non-idle detection)
- UI can be configured to show a clock
- Idle speed is now configurable
Bugfix:
- Intervals: crash when gain but not loss was stored
- While recording: prevent crash when disabling voice announcements
@@ -0,0 +1,10 @@
v3.23.0: OpenTracks
Changes:
- Added UI profiles
- Aggregated statistics for selected tracks
- Re-implemented idle/non-idle TrackPoints as moving time stopped working (introduced in v3.22.0)
Bugfixes:
- BLE sensors: clear data on disconnect
- Automatic export needs to persist permission
@@ -1,11 +1,22 @@
OpenTracks is a sport tracking application that completely respects your privacy.
A sport tracking buddy that respects your privacy.
Features:
* Sport tracking: track your sport and outdoor activities
* Photos and markers: mark interesting locations while tracking
* Altitude gain and loss: via barometric sensor
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84
* Voice announcements
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling)
* Export data: export tracks either as KMZ (incl. photos), KML, or GPX
* No Internet access: Internet is not used
Sport and outdoor activities grants happiness, skill and confidence.
Value your health by keeping track on your training.
Records as you go running or walking, and gives you a bike computer with a bigger screen for cycling.
Mark interesting locations along your way with pictures.
Keep recorded statistics in great detail for analysis.
Share only the data you want others to have.
* Voice announcements.
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling).
* Altitude gain and loss: via barometric sensor.
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84.
* Export data as tracks either as KMZ (incl. photos), KML, or GPX.
* No Internet access or extra permissions.
* Dark and light theme, respecting system settings.
* No ads.
Libre software / Free Software / Open Source
meaning you can use, study, change and share the source code.
Licensed Apache 2.0
@@ -1 +1 @@
A sport tracker that completely respects your privacy.
A sport tracker buddy that respects your privacy.
@@ -1,6 +1,6 @@
OpenTracks estas aplikaĵo por registri vian sportan aktivecon, kiu plene respektas vian privatecon.
Eblaĵoj:
Ebloj:
• registri vian sportan kaj alian aktivecon eksterdome
• fotoj kaj markoj: marki interesajn lokojn dum registri aktivecon
• registri ŝanĝon de altitudo uzante barometron
@@ -0,0 +1,8 @@
v3.19.1 : OpenTracks
Changements :
- Traductions mises à jour.
Correction de bogue :
- Correction du bug de l'exportation de toutes les pistes.
- Correction du bogue "Reprendre une piste".
@@ -0,0 +1,7 @@
v3.20.0 : OpenTracks
Changements :
- Pendant l'enregistrement : l'interface des statistiques est maintenant configurable
Correction d'un bug :
- Le changement de nom de piste/catégorie pendant l'enregistrement était ignoré (introduit dans la v3.19.0)
@@ -0,0 +1,6 @@
v3.20.1 : OpenTracks
Changements :
- Les annonces peuvent maintenant être utilisées à la fois par distance et par temps.
- Les annonces émettent un bip si aucun logiciel de synthèse vocale n'est installé.
- Suppression de la fréquence de localisation GPS adaptative
@@ -1,4 +1,4 @@
OpenTracks er en sportsporingsapp som respekterer ditt personvern.
OpenTracks er en app for opptak av sportsaktivitet som respekterer ditt personvern.
Funksjoner:
* Sportsporing: Registrer dine sports- og utendørsaktiviteter
@@ -1 +1 @@
En sportsregistrerer som respekterer ditt personvern.
En opptaker av sportsaktivitet som respekterer ditt personvern.
@@ -0,0 +1,16 @@
v3.18.6: OpenTracks
Zmiany:
- "Pokaż na mapie"- Funkcjonalość wraz z KML zamiast KMZ
- Komunikaty głosowe nie podają nazwy aplikacji
- Podczas śledzenia: Zmiana wysokości aktualizowana co sekundę
- Przetłumaczone opisy w sklepach aplikacji
Naprawione błędy:
- Naprawiono nieprawidłowe tłumaczenia wiadomości głosowych
Developer:
- Uprawnienia Android12 (BLUETOOTH_SCAN)
- Uprawnienia Android12 (ACCESS_COARSE_LOCATION)
- Zastosowanie TrackPoints (testowane)
- Usunięcie niepotrzebnych tłumaczeń
@@ -0,0 +1,11 @@
v3.22.0: OpenTracks
Zmiany:
- Lokalizacja zapisywana tylko wtedy gdy pokonany dystans jest większy od minimalnego przedziału śledzenia dystansu
(usunięto funkcjonalność z wykrywania bezczynności)
- UI może zostać skonfigurowane do wyświetlania zegara
- Prędkość ruchu bez oporu (prędkość jałowa) jest teraz konfigurowalna
Poprawki:
- Przedziały: awaria, gdy zapisano zwiększenie wysokości, ale nie spadek
- Podczas nagrywania: zapobieganie awariom podczas wyłączania komunikatów głosowych
@@ -3,9 +3,9 @@ OpenTracks jest aplikacją do śledzenia sportu, która całkowicie szanuje Twoj
Cechy:
* Śledzenie sportu: śledź swoją aktywność sportową i outdoorową.
* Zdjęcia i markery: zaznacz interesujące lokalizacje podczas śledzenia.
* Przyrost i utrata wysokości: poprzez czujnik barometryczny
* Zmiana wysokości: poprzez czujnik barometryczny
* Wysokość pokazana w EGM2008 (powyżej średniego poziomu morza); wyeksportowana jako WGS84
* Komunikaty głosowe
* Obsługa czujników Bluetooth LE: tętno, prędkość i dystans (kolarstwo), kadencja (kolarstwo) i miernik mocy (kolarstwo)
* Eksport danych: eksport tras jako KMZ (w tym zdjęcia), KML lub GPX
* Brak dostępu do Internetu: Internet nie jest używany
* Dostępu do Internetu: Internet nie jest używany
@@ -15,7 +15,6 @@ import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -27,7 +26,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.*;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -59,8 +58,7 @@ public class EspressoCustomLayoutTest {
trackControllerRecordButton.perform(click());
// Get custom layout preferences and check all data fields are showed.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
@@ -9,7 +8,7 @@ import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class PreferenceMetricUnitRule implements TestRule {
private final Context context;
@@ -25,14 +24,13 @@ public class PreferenceMetricUnitRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits();
try {
PreferencesUtils.setMetricUnits(sharedPreferences, context, metricUnits);
PreferencesUtils.setMetricUnits(metricUnits);
base.evaluate();
} finally {
PreferencesUtils.setMetricUnits(sharedPreferences, context, previousMetricUnits);
PreferencesUtils.setMetricUnits(previousMetricUnits);
}
}
};
@@ -0,0 +1,9 @@
package de.dennisguse.opentracks;
public class TestApplication extends Startup {
@Override
public String getDatabaseName() {
// null will make SQLiteOpenHelper create an in-memory database
return null;
}
}
@@ -0,0 +1,13 @@
package de.dennisguse.opentracks;
import android.app.Application;
import android.content.Context;
import androidx.test.runner.AndroidJUnitRunner;
public class TestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.newApplication(cl, TestApplication.class.getName(), context);
}
}
@@ -78,7 +78,7 @@ public class ChartPointTest {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setHeartRate_bpm(100f);
trackPoint.setCyclingCadence_rpm(101f);
trackPoint.setCadence_rpm(101f);
trackPoint.setPower(102f);
TrackStatistics statistics = new TrackStatistics();
@@ -0,0 +1,154 @@
package de.dennisguse.opentracks.content.data;
import android.content.Context;
import android.content.res.Resources;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(AndroidJUnit4.class)
public class LayoutTest extends TestCase {
private final Context context = ApplicationProvider.getApplicationContext();
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
@Test
public void testFromCsv() {
// given a csv line
String csv = "running;2;" + context.getString(R.string.stats_custom_layout_speed_key) + ",1,1,0;" + context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;" + context.getString(R.string.stats_custom_layout_altitude_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_gain_key) + ",0,0,0;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "running");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(dataFieldList.size(), 4);
assertEquals(dataFieldList.get(0).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertEquals(dataFieldList.get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertEquals(dataFieldList.get(2).getKey(), context.getString(R.string.stats_custom_layout_altitude_key));
assertEquals(dataFieldList.get(3).getKey(), context.getString(R.string.stats_custom_layout_gain_key));
assertEquals(dataFieldList.get(0).getTitle(), context.getString(R.string.stats_speed));
assertEquals(dataFieldList.get(1).getTitle(), context.getString(R.string.stats_distance));
assertEquals(dataFieldList.get(2).getTitle(), context.getString(R.string.stats_altitude));
assertEquals(dataFieldList.get(3).getTitle(), context.getString(R.string.stats_gain));
assertTrue(dataFieldList.get(0).isVisible());
assertTrue(dataFieldList.get(0).isPrimary());
assertFalse(dataFieldList.get(0).isWide());
assertTrue(dataFieldList.get(1).isVisible());
assertFalse(dataFieldList.get(1).isPrimary());
assertFalse(dataFieldList.get(1).isWide());
assertFalse(dataFieldList.get(2).isVisible());
assertTrue(dataFieldList.get(2).isPrimary());
assertFalse(dataFieldList.get(2).isWide());
assertFalse(dataFieldList.get(3).isVisible());
assertFalse(dataFieldList.get(3).isPrimary());
assertFalse(dataFieldList.get(3).isWide());
}
@Test
public void testFromCsv_Wrong1() {
// given a csv line without number of columns
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(layout.getFields().size(), 0);
}
@Test
public void testFromCsv_Wrong2() {
// given a csv line without any field
String csv1 = "Layout Name;2;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(layout.getFields().size(), 0);
}
@Test
public void testFromCsv_Wrong3() {
// given a csv line with wrong fields description
String csv1 = "Layout Name;2;speed,distance,total time;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "Layout Name");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().size(), 0);
}
@Test
public void testFromCsv_Wrong4() {
// given a csv line with the first field ok but not the others
String csv1 = "Layout Name;2;speed,1,0;distance;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "Layout Name");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().size(), 1);
}
@Test
public void testToCsv() {
// given a layout's object
Layout layout = new Layout("Test Layout", 2);
layout.addField("key1", "Title 1", false, false, false);
layout.addField("key2", "Title 2", false, true, false);
layout.addField("key3", "Title 3", true, false, false);
layout.addField("key4", "Title 4", true, true, false);
layout.addField("key5", "Title 5", true, true, true);
// when converts it to CSV
String csv = layout.toCsv();
// then csv is well built
assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
}
@Test
public void testToCsv_columnsByDefault() {
// given a layout's object
Layout layout = new Layout("Test Layout");
layout.addField("key1", "Title 1", false, false, false);
layout.addField("key2", "Title 2", false, true, false);
layout.addField("key3", "Title 3", true, false, false);
layout.addField("key4", "Title 4", true, true, false);
layout.addField("key5", "Title 5", true, true, true);
// when converts it to CSV
String csv = layout.toCsv();
// then csv is well built
assertEquals(csv, "Test Layout;" + PreferencesUtils.getLayoutColumnsByDefault() + ";key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
}
}
@@ -120,7 +120,7 @@ public class TestDataUtil {
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
trackPoint.setHeartRate_bpm(100f + i);
trackPoint.setCyclingCadence_rpm(300f + i);
trackPoint.setCadence_rpm(300f + i);
trackPoint.setPower(400f + i);
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
trackPoint.setAltitudeLoss(ALTITUDE_LOSS);
@@ -17,7 +17,7 @@ public class TestSensorDataUtil {
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
tp.setHeartRate_bpm(hr);
tp.setCyclingCadence_rpm(cadence);
tp.setCadence_rpm(cadence);
tp.setPower(power);
tp.setHorizontalAccuracy(Distance.of(1f));
tp.setAltitude(1f);
@@ -835,7 +835,7 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
trackPoint.setHeartRate_bpm(1F);
trackPoint.setCyclingCadence_rpm(2F);
trackPoint.setCadence_rpm(2F);
trackPoint.setPower(3F);
// when
@@ -845,7 +845,7 @@ public class CustomContentProviderUtilsTest {
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrue(trackPoints.get(10).hasHeartRate());
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
assertEquals(trackPoint.getCadence_rpm(), trackPoints.get(10).getCadence_rpm(), 0.01);
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
}
@@ -934,7 +934,7 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
trackPoint.setPower(null);
trackPoint.setCyclingCadence_rpm(null);
trackPoint.setCadence_rpm(null);
trackPoint.setHeartRate_bpm(null);
trackPointList.add(trackPoint);
Track.Id trackId = new Track.Id(System.currentTimeMillis());
@@ -124,7 +124,7 @@ public class ExportImportTest {
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackId = service.startNewTrack();
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
Distance sensorDistance = hasSensorDistance ? Distance.of(10) : null; // recording distance interval
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
@@ -218,12 +218,12 @@ public class ExportImportTest {
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance());
// Speed
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
assertEquals(Speed.of(3.75), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed());
// Altitude
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
@@ -312,16 +312,16 @@ public class ExportImportTest {
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(19), importedTrackStatistics.getMovingTime());
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance());
// Speed
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
assertEquals(Speed.of(1.5), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(1.5789473684210527), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(Speed.of(15), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed());
// Altitude
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
@@ -14,6 +14,7 @@ import org.junit.runners.JUnit4;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
@@ -23,6 +24,7 @@ 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.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -67,7 +69,7 @@ public class GPXImportTest {
importTrackId = importer.importFile(inputStream).get(0);
// then
// 1. track
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("the category", importedTrack.getCategory());
@@ -75,6 +77,8 @@ public class GPXImportTest {
assertEquals("2021-01-07 22:51", importedTrack.getName());
assertEquals("UNKNOWN", importedTrack.getIcon());
//TODO Check trackstatistics
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
@@ -106,4 +110,102 @@ public class GPXImportTest {
.setAltitude(10)
), importedTrackPoints);
}
@LargeTest
@Test
public void gpx_without_speed() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_without_speed);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getCategory());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals("", importedTrack.getIcon());
// 3. trackstatistics
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
assertEquals(1.44, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(53), trackStatistics.getMovingTime());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(3, importedTrackPoints.size());
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
.setLatitude(30.14184657)
.setLongitude(-40.38670089)
.setAltitude(-5)
.setSpeed(Speed.of(0.7976524233818054)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(0.7224021553993225))
), importedTrackPoints);
}
@LargeTest
@Test
public void gpx_speed_no_namespace() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_with_speed_no_namespace);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getCategory());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals("", importedTrack.getIcon());
// 3. trackstatistics
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
assertEquals(4, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(3, importedTrackPoints.size());
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(5)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
.setLatitude(30.14184657)
.setLongitude(-40.38670089)
.setAltitude(-5)
.setSpeed(Speed.of(0.7976524233818054))
.setSpeed(Speed.of(4)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(3))
), importedTrackPoints);
}
}
@@ -73,9 +73,9 @@ public class TrackPointAssert {
Assert.assertEquals(expected.getPower(), actual.getPower(), delta);
}
Assert.assertEquals(expected.hasCyclingCadence(), actual.hasCyclingCadence());
if (expected.hasCyclingCadence()) {
Assert.assertEquals(expected.getCyclingCadence_rpm(), actual.getCyclingCadence_rpm(), delta);
Assert.assertEquals(expected.hasCadence(), actual.hasCadence());
if (expected.hasCadence()) {
Assert.assertEquals(expected.getCadence_rpm(), actual.getCadence_rpm(), delta);
}
}
@@ -83,14 +83,14 @@ public class TrackPointAssert {
try {
Assert.assertEquals(expected.size(), actual.size());
} catch (AssertionError e) {
throw new AssertionError("Expected: " + expected + " actual: " + actual);
throw new AssertionError("Size difference; expected: " + expected.size() + "; actual: " + actual.size() + "\nExpected: " + expected + "\n actual: " + actual);
}
for (int i = 0; i < expected.size(); i++) {
try {
assertEquals(expected.get(i), actual.get(i));
} catch (AssertionError e) {
throw new AssertionError("Expected: " + expected.get(i) + " actual: " + actual.get(i), e);
throw new AssertionError("Expected: " + i + " to be " + expected.get(i) + "\n actual: " + actual.get(i), e);
}
}
Assert.assertEquals(expected.size(), actual.size());
@@ -25,7 +25,6 @@ import static org.junit.Assert.assertTrue;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.Looper;
@@ -65,16 +64,13 @@ import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Tests for the track recording service.
*
* @author Bartlomiej Niechwiej
* <p>
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTest {
@@ -97,7 +93,6 @@ public class TrackRecordingServiceTest {
}
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
@@ -124,7 +119,7 @@ public class TrackRecordingServiceTest {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
sharedPreferences.edit().clear().commit();
PreferencesUtils.clear();
// Ensure that the database is empty before every test
contentProviderUtils.deleteAllTracks(context);
@@ -456,10 +451,7 @@ public class TrackRecordingServiceTest {
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
}
/**
* Inserts a location and waits for 200ms.
*/
private static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(time))
.setLongitude(longitude)
.setLatitude(latitude)
@@ -1,8 +1,9 @@
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertFalse;
import android.content.ContentProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import androidx.annotation.NonNull;
@@ -32,19 +33,15 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertFalse;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Tests insert location.
* <p>
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
//TODO Implement as mock test; no need to store data in database
@@ -91,8 +88,7 @@ public class TrackRecordingServiceTestLocation {
tearDown();
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
PreferencesUtils.clear();
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
@@ -255,13 +251,6 @@ public class TrackRecordingServiceTestLocation {
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Why is this added? Systems is idle and not moving at all.
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
@@ -286,8 +275,8 @@ public class TrackRecordingServiceTestLocation {
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0); // will be ignored
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0); // will be ignored
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 15);
@@ -308,14 +297,14 @@ public class TrackRecordingServiceTestLocation {
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(5))
@@ -383,38 +372,6 @@ public class TrackRecordingServiceTestLocation {
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(3))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(4))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(5))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
.setHeartRate_bpm(5f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
@@ -430,6 +387,88 @@ public class TrackRecordingServiceTestLocation {
), trackPoints);
}
@Deprecated // Will be superseded when fixing #500
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withSensorDistance() {
BluetoothRemoteSensorManager remoteSensorManager = new BluetoothRemoteSensorManager(context) {
@Override
public boolean isEnabled() {
return true;
}
};
AltitudeSumManager altitudeSumManager = new AltitudeSumManager();
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager);
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
altitudeSumManager.stop(service);
altitudeSumManager.setConnected(true);
// when
altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(0)));
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(12)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 15);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(13)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 15);
altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(14)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 15);
altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16)));
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(5))
.setAltitudeGain(6f)
.setAltitudeLoss(6f)
.setSensorDistance(Distance.of(2)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(10)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(4))
.setSpeed(Speed.of(5))
.setAltitudeGain(6f)
.setAltitudeLoss(6f)
.setSensorDistance(Distance.of(2)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setSensorDistance(Distance.of(11))
.setSpeed(Speed.of(5))
.setAltitudeGain(6f)
.setAltitudeLoss(6f)
.setSensorDistance(Distance.of(2))
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_segment() {
@@ -32,7 +32,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -43,9 +43,6 @@ import static org.junit.Assert.assertTrue;
* Tests for the track recording service, which require a {@link Looper}.
*
* @author Bartlomiej Niechwiej
* <p>
* ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestLooper {
@@ -54,7 +51,6 @@ public class TrackRecordingServiceTestLooper {
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
@@ -79,7 +75,7 @@ public class TrackRecordingServiceTestLooper {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
sharedPreferences.edit().clear().apply();
PreferencesUtils.clear();
// Ensure that the database is empty before every test
contentProviderUtils.deleteAllTracks(context);
@@ -146,14 +142,14 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
PreferencesUtils.setString(R.string.stats_units_key, context.getString(R.string.stats_units_default));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
PreferencesUtils.setString(R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
fullRecordingSession();
}
@@ -215,7 +211,7 @@ public class TrackRecordingServiceTestLooper {
.setSpeed(Speed.of(10))
.setBearing(3.0f);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
service.getTrackPointCreator().onNewTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) {
@@ -0,0 +1,198 @@
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Tests resulting TrackStatistics.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestStatistics {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingService service;
private final AltitudeSumManager altitudeSumManager = new AltitudeSumManager() {
@Override
public void fill(@NonNull TrackPoint trackPoint) {
trackPoint.setAltitudeGain(0f);
trackPoint.setAltitudeLoss(0f);
}
};
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
@Before
public void setUp() throws TimeoutException {
// Set up the mock content resolver
ContentProvider customContentProvider = new CustomContentProvider() {
};
customContentProvider.attachInfo(context, null);
contentProviderUtils = new ContentProviderUtils(context);
tearDown();
// Let's use default values.
PreferencesUtils.clear();
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
service.getTrackPointCreator().stopGPS();
}
@After
public void tearDown() throws TimeoutException {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
service.getTrackPointCreator().setClock(Clock.systemUTC());
}
/**
* Moving time should increase if the previous and current TrackPoint have speed > threshold by the timeDiff(previousTrackPoint, currentTrackPoint).
*/
@MediumTest
@Test
public void movingtime_with_pauses() {
Assume.assumeTrue(
"Test fails on API23; reproducible on CI and some machines.",
Build.VERSION.SDK_INT > 23
);
// given
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault()));
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
Function<Integer, Void> assertMovingTime = expected -> {
Duration actual = contentProviderUtils.getTrack(trackId).getTrackStatistics().getMovingTime();
assertEquals(Duration.ofSeconds(expected), actual);
return null;
};
Function<Integer, Void> assertTotalTime = expected -> {
Duration actual = contentProviderUtils.getTrack(trackId).getTrackStatistics().getTotalTime();
assertEquals(Duration.ofSeconds(expected), actual);
return null;
};
// when / then
int movingtime_s = 0;
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15, 5 * 60000);
assertMovingTime.apply(movingtime_s);
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 2, 15, 6 * 60000);
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 2, 15, (long) (6.5 * 60000));
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 2, 15, 7 * 60000);
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 15, 8 * 60000);
movingtime_s += 3 * 60;
assertMovingTime.apply(movingtime_s);
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 0, 9 * 60000); //will be ignored
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 0, 10 * 60000); //will be ignored
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 0, 11 * 60000);
assertMovingTime.apply(movingtime_s);
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 15, 13 * 60000);
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 15, (long) (13.5 * 60000)); //will be ignored
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 2, 15, 14 * 60000);
TrackRecordingServiceTest.newTrackPoint(service, 45.0006, 35.0, 2, 15, 15 * 60000);
movingtime_s += 2 * 60;
assertMovingTime.apply(movingtime_s);
assertTotalTime.apply(15 * 60);
TrackRecordingServiceTest.newTrackPoint(service, 45.0006, 35.0, 2, 0, 16 * 60000); //will be ignored
assertMovingTime.apply(movingtime_s);
TrackRecordingServiceTest.newTrackPoint(service, 45.0015, 35.0, 2, 0, 17 * 60000);
TrackRecordingServiceTest.newTrackPoint(service, 45.0016, 35.0, 2, 15, 18 * 60000);
assertMovingTime.apply(movingtime_s);
TrackRecordingServiceTest.newTrackPoint(service, 45.0016, 35.0, 2, 0, 19 * 60000); //TODO we could ignore this TrackPoint
assertMovingTime.apply(movingtime_s);
assertTotalTime.apply(19 * 60);
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochSecond(40 * 60), ZoneId.systemDefault()));
assertMovingTime.apply(movingtime_s);
service.pauseCurrentTrack();
assertTotalTime.apply(40 * 60);
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochSecond(41 * 60), ZoneId.systemDefault()));
service.resumeCurrentTrack();
TrackRecordingServiceTest.newTrackPoint(service, 45.0016, 35.0, 2, 15, 42 * 60000);
assertMovingTime.apply(movingtime_s);
assertTotalTime.apply(41 * 60);
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochSecond(50 * 60), ZoneId.systemDefault()));
service.endCurrentTrack();
assertMovingTime.apply(movingtime_s);
assertTotalTime.apply(49 * 60);
// then
assertFalse(service.isRecording());
TrackStatistics trackStatistics = contentProviderUtils.getTrack(trackId).getTrackStatistics();
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(20, trackPoints.size());
assertEquals(Duration.ofMinutes(49), trackStatistics.getTotalTime());
}
}
@@ -5,7 +5,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Looper;
@@ -24,13 +23,12 @@ import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(MockitoJUnitRunner.class)
public class LocationHandlerTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@Mock
private TrackPointCreator trackPointCreator;
@@ -47,15 +45,12 @@ public class LocationHandlerTest {
@Before
public void setUp() {
// Let's use default values.
sharedPreferences.edit().clear().commit();
PreferencesUtils.clear();
Mockito.when(trackPointCreator.createNow())
.thenReturn(Instant.now());
//TODO REMOVE
// locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
// locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
locationHandler.onStart(context, sharedPreferences);
locationHandler.onStart(context);
}
/**
@@ -92,7 +87,7 @@ public class LocationHandlerTest {
@Test
public void testOnLocationChanged_poorAccuracy() {
// given
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
// when
locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis()));
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.util.Pair;
@@ -6,6 +6,7 @@ import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -18,7 +19,9 @@ 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.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import static org.junit.Assert.assertEquals;
@@ -68,7 +71,7 @@ public class AnnouncementUtilsTest {
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
// then
assertEquals(
Assert.assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
@@ -0,0 +1,118 @@
package de.dennisguse.opentracks.settings;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Layout;
@RunWith(AndroidJUnit4.class)
public class PreferencesOpenHelperTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
@Test
public void test_upgradeFrom0To1_withoutStatsCustomLayouts() {
// given the version 0
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(context.getString(R.string.stats_custom_layouts_key));
editor.putInt(context.getString(R.string.prefs_last_version_key), 0);
editor.commit();
// when update to version 1
PreferencesOpenHelper.newInstance(1).check();
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
}
@Test
public void test_upgradeFrom1To2_withOldVersion() {
// given the version 1
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
String oldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";distance,1,1,0;speed,1,1,0;";
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
editor.commit();
// when update to version 2
PreferencesOpenHelper.newInstance(2).check();
String updatedOldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";"
+ PreferencesUtils.getLayoutColumnsByDefault() + ";distance,1,1,0;speed,1,1,0;";
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
}
@Test
public void test_upgradeFrom1To2_withNewVersion() {
// given the version 1
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
String oldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
editor.commit();
// when update to version 2
PreferencesOpenHelper.newInstance(2).check();
String updatedOldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
}
@Test
public void test_downgrade() {
// given version 2
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
String customLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
editor.putString(context.getString(R.string.stats_custom_layouts_key), customLayoutCsv);
editor.putInt(context.getString(R.string.prefs_last_version_key), 2);
editor.commit();
// when downgrade to version 1
PreferencesOpenHelper.newInstance(1).check();
// then custom layout should be equals to default layout.
Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
List<Layout> customLayout = PreferencesUtils.getAllCustomLayouts();
assertEquals(customLayout.size(), 1);
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
}
}
@@ -1,5 +1,8 @@
package de.dennisguse.opentracks.stats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
@@ -16,9 +19,6 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(AndroidJUnit4.class)
public class TrackStatisticsUpdaterTest {
@@ -81,18 +81,18 @@ public class TrackStatisticsUpdaterTest {
// then
TrackStatistics statistics = subject.getTrackStatistics();
assertEquals(99.58, statistics.getTotalDistance().toM(), 0.01);
assertEquals(Duration.ofSeconds(14), statistics.getTotalTime());
assertEquals(Duration.ofSeconds(7), statistics.getMovingTime());
assertEquals(128.03, statistics.getTotalDistance().toM(), 0.01);
assertEquals(Duration.ofSeconds(13), statistics.getTotalTime());
assertEquals(Duration.ofSeconds(9), statistics.getMovingTime());
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
assertEquals(27.5, statistics.getMaxAltitude(), 0.01);
assertEquals(28.75, statistics.getMaxAltitude(), 0.01);
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(7.11, statistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(9.84, statistics.getAverageSpeed().toMPS(), 0.01);
}
@Test
@@ -141,9 +141,9 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
tp2.setSpeed(Speed.of(5f));
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp2.setSpeed(Speed.of(5f));
tp4.setSpeed(Speed.of(5f));
tp4.setSensorDistance(Distance.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
tp5.setSensorDistance(Distance.of(10f));
@@ -200,9 +200,12 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
tp3.setSensorDistance(Distance.of(5f));
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp4.setSpeed(Speed.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
// when
@@ -248,8 +251,11 @@ public class TrackStatisticsUpdaterTest {
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp4.setSpeed(Speed.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
subject.addTrackPoint(tp1, GPS_DISTANCE);
@@ -43,40 +43,40 @@ public class BluetoothUtilsTest {
@Test
public void parseCyclingSpeedCadence_crankOnly() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
characteristic.setValue(new byte[]{0x01, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
// when
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertNull(sensor.getDistanceSpeed());
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
}
@Test
public void parseCyclingSpeedCadence_wheelOnly() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
characteristic.setValue(new byte[]{0x02, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
// when
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(65535 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertNull(sensor.getCadence());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
}
@Test
public void parseCyclingSpeedCadence_crankWheel() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
// when
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertEquals(200 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertEquals(225, sensor.getCadence().getCrankRevolutionsCount());
}
@Test
@@ -93,8 +93,8 @@ public class BluetoothUtilsTest {
@Test
public void parseRunningSpeedAndCadence_with_distance() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, 0, 0);
characteristic.setValue(new byte[]{2, 0, 5, 80, 12, 1});
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID, 0, 0);
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
// when
SensorDataRunning sensor = BluetoothUtils.parseRunningSpeedAndCadence("address", "sensorName", characteristic);
@@ -102,6 +102,6 @@ public class BluetoothUtilsTest {
// then
assertEquals(Speed.of(5), sensor.getSpeed());
assertEquals(80, sensor.getCadence(), 0.01);
assertEquals(Distance.of(26.8), sensor.getTotalDistance());
assertEquals(Distance.of(6553.5 + 1677721.6), sensor.getTotalDistance());
}
}
@@ -0,0 +1,93 @@
package de.dennisguse.opentracks.util;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class CsvLayoutUtilsTest extends TestCase {
@Test
public void testGetCsvLineParts() {
// given
String csvLineOk = "Layout Name;2;distance,1,1;speed,1,1;";
String csvLineWrong1 = "Layout Name;distance,1,1;speed,1,1;";
String csvLineWrong2 = "Layout Name;1;";
// when
List<String> partsOk = CsvLayoutUtils.getCsvLineParts(csvLineOk);
List<String> partsWrong1 = CsvLayoutUtils.getCsvLineParts(csvLineWrong1);
List<String> partsWrong2 = CsvLayoutUtils.getCsvLineParts(csvLineWrong2);
// then
assertNotNull(partsOk);
assertEquals(partsOk.size(), 4);
assertEquals(partsOk.get(0), "Layout Name");
assertEquals(partsOk.get(1), "2");
assertEquals(partsOk.get(2), "distance,1,1");
assertEquals(partsOk.get(3), "speed,1,1");
assertNull(partsWrong1);
assertNull(partsWrong2);
}
@Test
public void testGetCsvFieldParts() {
// given
String csvFieldOk1 = "distance,0,0";
String csvFieldOk2 = "speed,0,1";
String csvFieldOk3 = "time,1,0";
String csvFieldOk4 = "moving time,1,1";
String csvFieldOk5 = ";moving time,1,1";
String csvLineWrong1 = "";
String csvLineWrong2 = "distance,speed,time";
String csvLineWrong3 = "distance,0,1o";
String csvLineWrong4 = "distance,0z,1";
String csvLineWrong5 = "distance";
String csvLineWrong6 = "distance,0";
String csvLineWrong7 = "distance,1,1;";
// when
String[] ok1 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk1);
String[] ok2 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk2);
String[] ok3 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk3);
String[] ok4 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk4);
String[] ok5 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk5);
String[] wrong1 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong1);
String[] wrong2 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong2);
String[] wrong3 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong3);
String[] wrong4 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong4);
String[] wrong5 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong5);
String[] wrong6 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong6);
String[] wrong7 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong7);
// then
assertNotNull(ok1);
assertNotNull(ok2);
assertNotNull(ok3);
assertNotNull(ok4);
assertNotNull(ok5);
assertFieldOk(ok1, "distance", "0", "0");
assertFieldOk(ok2, "speed", "0", "1");
assertFieldOk(ok3, "time", "1", "0");
assertFieldOk(ok4, "moving time", "1", "1");
assertFieldOk(ok5, ";moving time", "1", "1");
assertNull(wrong1);
assertNull(wrong2);
assertNull(wrong3);
assertNull(wrong4);
assertNull(wrong5);
assertNull(wrong6);
assertNull(wrong7);
}
private void assertFieldOk(String[] ok, String name, String val1, String val2) {
assertEquals(ok[0], name);
assertEquals(ok[1], val1);
assertEquals(ok[2], val2);
}
}
@@ -2,7 +2,9 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -13,28 +15,33 @@ 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;
@RunWith(AndroidJUnit4.class)
public class PreferencesUtilsTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
@Test
public void ExportTrackFileFormat_ok() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.name());
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -43,13 +50,13 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_invalid() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -58,43 +65,61 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_noValue() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
public void testGetCustomLayout_default() {
public void testGetAllCustomLayouts_default() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
// then
assertEquals(layouts.size(), 1);
assertTrue(layouts.get(0).getFields().size() > 0);
assertEquals(layouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(layouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
}
@Test
public void testGetCustomLayout_default() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertTrue(layout.getFields().size() > 0);
assertEquals(layout.getProfile(), context.getString(R.string.default_activity_default));
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
}
@Test
public void testGetCustomLayout_1() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
"run;"
context.getString(R.string.stats_custom_layouts_key),
"run;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
@@ -102,11 +127,12 @@ public class PreferencesUtilsTest {
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getProfile(), "run");
assertEquals(layout.getName(), "run");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
@@ -128,11 +154,11 @@ public class PreferencesUtilsTest {
@Test
public void testGetCustomLayout_coordinatesIsWide() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
"walking;"
context.getString(R.string.stats_custom_layouts_key),
"walking;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
@@ -140,11 +166,12 @@ public class PreferencesUtilsTest {
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getProfile(), "walking");
assertEquals(layout.getName(), "walking");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
@@ -167,7 +194,7 @@ public class PreferencesUtilsTest {
@Test
public void testSetCustomLayout() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
Layout layoutSrc = new Layout("road cycling");
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
@@ -175,20 +202,22 @@ public class PreferencesUtilsTest {
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
// when
PreferencesUtils.setCustomLayout(sharedPreferences, context, layoutSrc);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
editor.commit();
// then
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layouts_key), null);
assertNotNull(csv);
assertEquals(csv,
"road cycling;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
"road cycling;" + PreferencesUtils.getLayoutColumnsByDefault() + ";"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1,0;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
Layout layoutDst = PreferencesUtils.getCustomLayout(sharedPreferences, context);
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
Layout layoutDst = PreferencesUtils.getCustomLayout();
assertEquals(layoutSrc.getName(), layoutDst.getName());
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
@@ -196,4 +225,122 @@ public class PreferencesUtilsTest {
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
}
}
@Test
public void testEditCustomLayouts() {
// update all custom layouts
// given a custom layout with two profiles
String cyclingProfile = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
String runningProfile = "running;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.apply();
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
// when cyling profile is updated
String cyclingProfileUpdated = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
List<Layout> layoutsToBeUpdated = new ArrayList<>();
layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources));
layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources));
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
// then only updated profile is modified in the custom layouts
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
assertEquals(layoutsBefore.size(), 2);
assertEquals(layoutsAfter.size(), 2);
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
}
@Test
public void testEditCustomLayout() {
// Update only one custom layout
// given a custom layout with two profiles
String cyclingProfile = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
String runningProfile = "running;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.apply();
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
// when cyling profile is updated
String cyclingProfileUpdated = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
Layout layoutToBeUpdated = Layout.fromCsv(cyclingProfileUpdated, resources);
PreferencesUtils.updateCustomLayout(layoutToBeUpdated);
// then only updated profile is modified in the custom layouts
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
assertEquals(layoutsBefore.size(), 2);
assertEquals(layoutsAfter.size(), 2);
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
}
@Test
public void testGetCustomLayout_whenSelectedOneNotExists() {
// given a custom layout with two profiles and not existing custom layout selected
String cyclingProfile = "cycling;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
String runningProfile = "running;2;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.putString(context.getString(R.string.stats_custom_layout_selected_layout_key), "Not Exists");
editor.apply();
// when it gets the custom layout
Layout layout = PreferencesUtils.getCustomLayout();
// then the first one was returned
assertEquals(layout.getName(), "cycling");
}
}
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -31,6 +30,7 @@ import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import static org.junit.Assert.assertEquals;
@@ -47,14 +47,12 @@ public class TrackNameUtilsTest {
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
/**
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
*/
@Test
public void testTrackName_date_local() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
@@ -63,7 +61,7 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
@@ -73,7 +71,7 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_number() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
}
@@ -23,6 +23,7 @@ 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 {
@@ -115,9 +116,40 @@ public class IntervalStatisticsTest {
whenAndThen(10000, distanceInterval);
}
@Test
public void testWithNoLossTrackPoints() {
// TrackPoints with elevation gain but without elevation loss.
// given
float distanceInterval = 1000f;
int numberOfPoints = 10000;
Track dummyTrack = new Track();
dummyTrack.setId(new Track.Id(System.currentTimeMillis()));
dummyTrack.setName("Dummy Track Without Elevation Loss");
contentProviderUtils.insertTrack(dummyTrack);
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
for (int i = 0; i < numberOfPoints; i++) {
TrackPoint tp = TestDataUtil.createTrackPoint(i);
tp.setAltitudeLoss(null);
contentProviderUtils.insertTrackPoint(tp, dummyTrack.getId());
trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0));
}
dummyTrack.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(dummyTrack);
Pair<Track.Id, TrackStatistics> trackWithStats = new Pair<>(dummyTrack.getId(), trackStatisticsUpdater.getTrackStatistics());
// when and then
whenAndThen(trackWithStats, numberOfPoints, distanceInterval);
}
private void whenAndThen(int numberOfPoints, float distanceInterval) {
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0));
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
whenAndThen(trackWithStats, numberOfPoints, distanceInterval);
}
private void whenAndThen(Pair<Track.Id, TrackStatistics> trackWithStats, int numberOfPoints, float distanceInterval) {
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0));
Track.Id trackId = trackWithStats.first;
TrackStatistics trackStatistics = trackWithStats.second;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
@@ -127,18 +159,39 @@ public class IntervalStatisticsTest {
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
Distance totalDistance = Distance.of(0);
float totalTime = 0L;
float totalGain = 0f;
Float totalGain = null;
Float totalLoss = null;
for (IntervalStatistics.Interval i : intervalList) {
totalDistance = totalDistance.plus(i.getDistance());
totalTime += i.getDistance().toM() / i.getSpeed().toMPS();
totalGain += i.getGain_m();
if (totalGain == null) {
totalGain = i.getGain_m();
} else if (i.getGain_m() != null) {
totalGain += i.getGain_m();
}
if (totalLoss == null) {
totalLoss = i.getLoss_m();
} else if (i.getLoss_m() != null) {
totalLoss += i.getLoss_m();
}
}
// then
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
if (totalGain != null) {
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
} else {
assertTrue(intervalStatistics.getIntervalList().stream().noneMatch(IntervalStatistics.Interval::hasGain));
}
if (totalLoss != null) {
assertEquals(totalLoss, numberOfPoints * TestDataUtil.ALTITUDE_LOSS, 0.1);
} else {
assertTrue(intervalStatistics.getIntervalList().stream().noneMatch(IntervalStatistics.Interval::hasLoss));
}
for (int i = 0; i < intervalList.size() - 1; i++) {
assertEquals(intervalList.get(i).getDistance().toM(), distanceInterval, 0.001);
@@ -0,0 +1,26 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<name>20210907_213924.gpx</name>
<trkseg>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:10:19Z</time>
<hdop>4</hdop>
<speed>5</speed>
</trkpt>
<trkpt lat="30.14184657" lon="-40.38670089">
<ele>-5</ele>
<time>2021-09-07T22:11:07Z</time>
<hdop>4</hdop>
<speed>4</speed>
</trkpt>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:12:00Z</time>
<hdop>4</hdop>
<speed>3</speed>
</trkpt>
</trkseg>
</trk>
</gpx>
@@ -0,0 +1,23 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<name>20210907_213924.gpx</name>
<trkseg>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:10:19Z</time>
<hdop>4</hdop>
</trkpt>
<trkpt lat="30.14184657" lon="-40.38670089">
<ele>-5</ele>
<time>2021-09-07T22:11:07Z</time>
<hdop>4</hdop>
</trkpt>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:12:00Z</time>
<hdop>4</hdop>
</trkpt>
</trkseg>
</trk>
</gpx>
+7 -2
View File
@@ -308,7 +308,11 @@ limitations under the License.
<activity android:name=".TrackRecordedActivity" />
<activity android:name=".TrackRecordingActivity" />
<activity android:name=".TrackRecordingActivity" android:parentActivityName=".TrackListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".TrackListActivity" />
</activity>
<activity android:name=".TrackEditActivity" />
@@ -320,7 +324,8 @@ limitations under the License.
<activity android:name=".io.file.exporter.ExportActivity" />
<activity android:name=".settings.SettingsActivity" />
<activity android:name=".settings.SettingsCustomLayoutActivity" />
<activity android:name=".settings.SettingsCustomLayoutListActivity" />
<activity android:name=".settings.SettingsCustomLayoutEditActivity" />
<activity
android:name=".ShowOnMapProxyActivity$KML"
@@ -5,12 +5,17 @@ import android.view.View;
import androidx.lifecycle.ViewModelProvider;
import java.util.List;
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.databinding.AggregatedStatsBinding;
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
public class AggregatedStatisticsActivity extends AbstractActivity {
public static final String EXTRA_TRACK_IDS = "track_ids";
private AggregatedStatsBinding viewBinding;
private AggregatedStatisticsAdapter adapter;
@@ -21,8 +26,10 @@ public class AggregatedStatisticsActivity extends AbstractActivity {
viewBinding.aggregatedStatsList.setEmptyView(viewBinding.aggregatedStatsEmptyView);
List<Track.Id> trackIds = getIntent().getParcelableArrayListExtra(EXTRA_TRACK_IDS);
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
viewModel.getAggregatedStats().observe(this, aggregatedStatistics -> {
viewModel.getAggregatedStats(trackIds).observe(this, aggregatedStatistics -> {
if (aggregatedStatistics != null) {
adapter = new AggregatedStatisticsAdapter(this, aggregatedStatistics);
viewBinding.aggregatedStatsList.setAdapter(adapter);
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.TransitionDrawable;
import android.os.Bundle;
import android.os.Handler;
@@ -97,7 +98,10 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
return;
}
getCallback().recordStart();
Callback callback = (Callback) getContext();
if (callback != null) {
callback.recordStart();
}
});
viewBinding.controllerStop.setOnTouchListener(this);
@@ -114,6 +118,12 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
trackRecordingServiceConnection.startConnection(getContext());
}
@Override
public void onPause() {
super.onPause();
handlerUpdateTotalTime.removeCallbacksAndMessages(null);
}
@Override
public void onStop() {
super.onStop();
@@ -175,7 +185,10 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
buttonDelay = () -> {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
view.performClick();
getCallback().recordPause();
Callback callback = (Callback) getContext();
if (callback != null) {
callback.recordPause();
}
transition.resetTransition();
@@ -198,8 +211,12 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
buttonDelay = () -> {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
getCallback().recordStop();
ActivityUtils.vibrate(getContext(), 1000);
Context context = getContext();
Callback callback = (Callback) context;
if (callback != null) {
callback.recordStop();
ActivityUtils.vibrate(context, 1000);
}
transition.resetTransition();
};
@@ -222,10 +239,6 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
}
private Callback getCallback() {
return (Callback) getActivity();
}
public interface Callback {
void recordStart();
@@ -1,14 +1,12 @@
package de.dennisguse.opentracks;
import android.app.Application;
import android.content.SharedPreferences;
import android.os.StrictMode;
import android.util.Log;
import java.util.Locale;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Code that is executed when the application starts.
@@ -27,13 +25,14 @@ public class Startup extends Application {
// Include version information into stack traces.
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
PreferencesUtils.initPreferences(this, getResources());
// Set default values of preferences on first start.
SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) {
PreferencesUtils.setMetricUnits(sharedPreferences, this, !Locale.US.equals(Locale.getDefault()));
PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString( R.string.stats_units_key, "").equals("")) {
PreferencesUtils.setMetricUnits(!Locale.US.equals(Locale.getDefault()));
}
PreferencesUtils.applyNightMode();
ActivityUtils.applyNightMode(sharedPreferences, this);
// In debug builds: show thread and VM warnings.
if (BuildConfig.DEBUG) {
@@ -41,4 +40,13 @@ public class Startup extends Application {
StrictMode.enableDefaults();
}
}
/**
* Returns the name of the database used by SQLiteOpenHelper.
* See {@link android.database.sqlite.SQLiteOpenHelper} for details.
* @return SQLite database name.
*/
public String getDatabaseName() {
return "database.db";
}
}
@@ -21,7 +21,6 @@ import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.database.Cursor;
@@ -50,6 +49,10 @@ import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Track;
@@ -59,12 +62,12 @@ import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
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.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -78,7 +81,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
private static final String TAG = TrackListActivity.class.getSimpleName();
// The following are setFrequency in onCreate
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private ResourceCursorAdapter resourceCursorAdapter;
@@ -110,8 +112,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
};
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits();
}
if (key != null) {
runOnUiThread(() -> {
@@ -161,8 +163,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
@@ -231,8 +231,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
protected void onStart() {
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.startConnection(this);
}
@@ -249,10 +248,17 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
viewBinding = null;
trackRecordingServiceConnection = null;
}
@Override
protected View getRootView() {
viewBinding = TrackListBinding.inflate(getLayoutInflater());
@@ -468,6 +474,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
return true;
}
if (itemId == R.id.list_context_menu_aggregated_stats) {
Intent intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class)
.putParcelableArrayListExtra(AggregatedStatisticsActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
startActivity(intent);
return true;
}
if (itemId == R.id.list_context_menu_select_all) {
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
viewBinding.trackList.setItemChecked(i, true);
@@ -47,7 +47,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* An activity to show the track detail, record a new track or resumes an existing one.
@@ -68,7 +68,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
// The following are setFrequency in onCreate.
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
private TrackRecordedBinding viewBinding;
@@ -96,8 +95,8 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(TrackRecordedActivity.this, R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordedActivity.this));
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
}
}
};
@@ -107,7 +106,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
handleIntent(getIntent());
@@ -130,8 +128,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
protected void onStart() {
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackDataHub.start();
}
@@ -155,7 +152,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
super.onStop();
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -1,5 +1,6 @@
package de.dennisguse.opentracks;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -18,6 +19,8 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
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;
@@ -28,10 +31,10 @@ import de.dennisguse.opentracks.fragments.IntervalsFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
@@ -53,7 +56,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
// The following are setFrequency in onCreate
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackDataHub trackDataHub;
@@ -96,17 +98,17 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_show_on_lockscreen_while_recording_key, key)) {
setLockscreenPolicy();
}
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_keep_screen_on_while_recording_key, key)) {
setScreenOnPolicy();
}
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_fullscreen_while_recording_key, key)) {
setFullscreenPolicy();
}
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordingActivity.this));
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
}
if (key == null) return;
@@ -121,7 +123,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackId = null;
if (savedInstanceState != null) {
//Activity was recreated.
@@ -156,7 +157,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setLockscreenPolicy() {
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, TrackRecordingActivity.this);
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen);
@@ -168,7 +169,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setScreenOnPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn();
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -178,7 +179,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setFullscreenPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen();
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -191,8 +192,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
protected void onStart() {
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.startConnection(this);
trackDataHub.start();
@@ -230,11 +230,18 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
viewBinding = null;
trackRecordingServiceConnection = null;
}
@Override
protected View getRootView() {
viewBinding = TrackRecordingBinding.inflate(getLayoutInflater());
@@ -259,6 +266,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
IntentDashboardUtils.startDashboard(this, true, trackId);
return true;
}
if (item.getItemId() == R.id.track_detail_insert_marker) {
Intent intent = IntentUtils
.newIntent(this, MarkerEditActivity.class)
@@ -267,8 +279,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
return true;
}
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
IntentDashboardUtils.startDashboard(this, true, trackId);
if (item.getItemId() == R.id.track_detail_menu_select_layout) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
List<String> layoutNames = PreferencesUtils.getAllCustomLayoutNames();
builder.setTitle(getString(R.string.custom_layout_select_layout)).setItems(layoutNames.toArray(new String[0]), (dialog, which) -> PreferencesUtils.setDefaultLayout(layoutNames.get(which)));
builder.create().show();
return true;
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,7 +10,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
@@ -58,7 +57,7 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
viewHolder = (ViewHolder) convertView.getTag();
}
if (TrackIconUtils.isSpeedIcon(context, aggregatedStatistic.getCategory())) {
if (TrackIconUtils.isSpeedIcon(context.getResources(), aggregatedStatistic.getCategory())) {
viewHolder.setSpeed(aggregatedStatistic);
} else {
viewHolder.setPace(aggregatedStatistic);
@@ -139,9 +138,8 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String category = aggregatedStatistic.getCategory();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
reportSpeed = PreferencesUtils.isReportSpeed(category);
metricsUnits = PreferencesUtils.isMetricUnits();
sportIcon.setImageResource(getIcon(aggregatedStatistic));
typeLabel.setText(category);
@@ -18,13 +18,13 @@ import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.util.StatisticsUtils;
public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Layout layout;
private final Context context;
private final SettingsCustomLayoutItemClickListener itemClickListener;
public SettingsCustomLayoutAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
this.context = context;
this.itemClickListener = itemClickListener;
this.layout = layout;
@@ -34,12 +34,12 @@ public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerVi
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
return new SettingsCustomLayoutAdapter.ViewHolder(view);
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
SettingsCustomLayoutAdapter.ViewHolder viewHolder = (SettingsCustomLayoutAdapter.ViewHolder) holder;
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
DataField field = layout.getFields().get(position);
viewHolder.itemView.setTag(field.getKey());
viewHolder.title.setText(field.getTitle());
@@ -61,13 +61,8 @@ public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerVi
}
}
@Override
public int getItemViewType(int position) {
return layout.getFields().get(position).isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
}
public boolean isItemWide(int position) {
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
return layout.getFields().get(position).isWide();
}
public DataField getItem(int position) {
@@ -0,0 +1,95 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import java.util.Optional;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Layout> layoutList;
private final Context context;
private SettingsCustomLayoutProfileClickListener itemClickListener;
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
this.context = context;
this.itemClickListener = itemClickListener;
layoutList = PreferencesUtils.getAllCustomLayouts();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.custom_layout_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
SettingsCustomLayoutListAdapter.ViewHolder viewHolder = (SettingsCustomLayoutListAdapter.ViewHolder) holder;
Layout layout = layoutList.get(position);
viewHolder.itemView.setTag(layout.getName());
viewHolder.title.setText(layout.getName());
}
@Override
public int getItemCount() {
if (layoutList == null) {
return 0;
}
return layoutList.size();
}
public List<Layout> getLayouts() {
return layoutList;
}
public void reloadLayouts() {
layoutList = PreferencesUtils.getAllCustomLayouts();
notifyDataSetChanged();
}
public void removeLayout(int position) {
layoutList.remove(position);
PreferencesUtils.updateCustomLayouts(layoutList);
notifyDataSetChanged();
}
public void restoreItem(Layout layout, int position) {
layoutList.add(position, layout);
PreferencesUtils.updateCustomLayouts(layoutList);
notifyDataSetChanged();
}
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView title;
public ViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.custom_layout_title);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
String profile = (String) view.getTag();
Optional<Layout> optionalLayout = layoutList.stream().filter(layout -> layout.sameName(new Layout(profile))).findFirst();
optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked);
}
}
public interface SettingsCustomLayoutProfileClickListener {
void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout);
}
}
@@ -4,6 +4,7 @@ import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextClock;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -13,6 +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.viewmodels.StatisticData;
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@@ -28,15 +30,26 @@ public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_item, parent, false);
return new StatisticsAdapter.ViewHolder(view);
View view;
if (viewType == CustomLayoutFieldType.CLOCK.value()) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_clock_item, parent, false);
return new StatisticsAdapter.ViewClockHolder(view);
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_generic_item, parent, false);
return new StatisticsAdapter.ViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
StatisticData statisticData = statisticDataList.get(position);
viewHolder.setData(statisticData);
if (getItemViewType(position) == CustomLayoutFieldType.CLOCK.value()) {
StatisticsAdapter.ViewClockHolder viewHolder = (StatisticsAdapter.ViewClockHolder) holder;
viewHolder.setData(statisticData);
} else {
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
viewHolder.setData(statisticData);
}
}
@Override
@@ -50,11 +63,15 @@ public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
@Override
public int getItemViewType(int position) {
return statisticDataList.get(position).getField().isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
if (statisticDataList.get(position).getField().getType(context) == CustomLayoutFieldType.CLOCK) {
return CustomLayoutFieldType.CLOCK.value();
} else {
return CustomLayoutFieldType.GENERIC.value();
}
}
public boolean isItemWide(int position) {
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
return statisticDataList.get(position).getField().isWide();
}
public List<StatisticData> swapData(List<StatisticData> data) {
@@ -116,6 +133,25 @@ public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
}
private class ViewClockHolder extends RecyclerView.ViewHolder {
final TextClock value;
final TextView descMain;
public ViewClockHolder(@NonNull View itemView) {
super(itemView);
value = itemView.findViewById(R.id.stats_clock);
descMain = itemView.findViewById(R.id.stats_description_main);
}
public void setData(StatisticData statisticData) {
if (statisticData == null) {
return;
}
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue);
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel);
}
}
private class ViewRecordedHolder extends ViewHolder {
public ViewRecordedHolder(@NonNull View itemView) {
@@ -41,8 +41,8 @@ public class ChartPoint {
if (trackPoint.hasHeartRate()) {
heartRate = (double) trackPoint.getHeartRate_bpm();
}
if (trackPoint.hasCyclingCadence()) {
cadence = (double) trackPoint.getCyclingCadence_rpm();
if (trackPoint.hasCadence()) {
cadence = (double) trackPoint.getCadence_rpm();
}
if (trackPoint.hasPower()) {
power = (double) trackPoint.getPower();
@@ -1,8 +1,8 @@
package de.dennisguse.opentracks.adapters;
package de.dennisguse.opentracks.content.data;
enum CustomLayoutFieldType {
SHORT(0),
WIDE(1);
public enum CustomLayoutFieldType {
GENERIC(1),
CLOCK(2);
private final int value;
@@ -1,11 +1,14 @@
package de.dennisguse.opentracks.content.data;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.CsvConstants;
import de.dennisguse.opentracks.util.CsvLayoutUtils;
public class DataField {
public class DataField implements Parcelable {
public static final String YES_VALUE = "1";
public static final String NOT_VALUE = "0";
@@ -23,6 +26,26 @@ public class DataField {
this.isWide = isWide;
}
protected DataField(Parcel in) {
key = in.readString();
title = in.readString();
isVisible = in.readByte() != 0;
isPrimary = in.readByte() != 0;
isWide = in.readByte() != 0;
}
public static final Creator<DataField> CREATOR = new Creator<DataField>() {
@Override
public DataField createFromParcel(Parcel in) {
return new DataField(in);
}
@Override
public DataField[] newArray(int size) {
return new DataField[size];
}
};
public String getKey() {
return key;
}
@@ -51,51 +74,76 @@ public class DataField {
return isWide;
}
public CustomLayoutFieldType getType(Context context) {
if (key.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
return CustomLayoutFieldType.CLOCK;
} else {
return CustomLayoutFieldType.GENERIC;
}
}
public String toCsv() {
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
return key + CsvConstants.ITEM_SEPARATOR + visible + CsvConstants.ITEM_SEPARATOR + primary;
String wide = this.isWide ? YES_VALUE : NOT_VALUE;
return key + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide;
}
public static String getTitleByKey(Context context, String key) {
if (key.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
return context.getString(R.string.stats_total_time);
} else if (key.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
return context.getString(R.string.stats_moving_time);
} else if (key.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
return context.getString(R.string.stats_distance);
} else if (key.equals(context.getString(R.string.stats_custom_layout_speed_key))) {
return context.getString(R.string.stats_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
return context.getString(R.string.stats_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
return context.getString(R.string.stats_average_moving_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
return context.getString(R.string.stats_average_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
return context.getString(R.string.stats_max_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
return context.getString(R.string.stats_average_moving_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
return context.getString(R.string.stats_average_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
return context.getString(R.string.stats_fastest_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
return context.getString(R.string.stats_altitude);
} else if (key.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
return context.getString(R.string.stats_gain);
} else if (key.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
return context.getString(R.string.stats_loss);
} else if (key.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
return context.getString(R.string.stats_coordinates);
} else if (key.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
return context.getString(R.string.stats_sensors_heart_rate);
} else if (key.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
return context.getString(R.string.stats_sensors_cadence);
} else if (key.equals(context.getString(R.string.stats_custom_layout_power_key))) {
return context.getString(R.string.stats_sensors_power);
public static String getTitleByKey(Resources resources, String key) {
if (key.equals(resources.getString(R.string.stats_custom_layout_total_time_key))) {
return resources.getString(R.string.stats_total_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_moving_time_key))) {
return resources.getString(R.string.stats_moving_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_distance_key))) {
return resources.getString(R.string.stats_distance);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_speed_key))) {
return resources.getString(R.string.stats_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_pace_key))) {
return resources.getString(R.string.stats_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
return resources.getString(R.string.stats_average_moving_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_speed_key))) {
return resources.getString(R.string.stats_average_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_max_speed_key))) {
return resources.getString(R.string.stats_max_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
return resources.getString(R.string.stats_average_moving_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_pace_key))) {
return resources.getString(R.string.stats_average_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_fastest_pace_key))) {
return resources.getString(R.string.stats_fastest_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_altitude_key))) {
return resources.getString(R.string.stats_altitude);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_gain_key))) {
return resources.getString(R.string.stats_gain);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_loss_key))) {
return resources.getString(R.string.stats_loss);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_coordinates_key))) {
return resources.getString(R.string.stats_coordinates);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_heart_rate_key))) {
return resources.getString(R.string.stats_sensors_heart_rate);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_cadence_key))) {
return resources.getString(R.string.stats_sensors_cadence);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_power_key))) {
return resources.getString(R.string.stats_sensors_power);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_clock_key))) {
return resources.getString(R.string.stats_clock);
} else {
throw new RuntimeException("It doesn't exists a field with key: " + key);
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(key);
parcel.writeString(title);
parcel.writeByte((byte) (isVisible ? 1 : 0));
parcel.writeByte((byte) (isPrimary ? 1 : 0));
parcel.writeByte((byte) (isWide ? 1 : 0));
}
}
@@ -1,16 +1,75 @@
package de.dennisguse.opentracks.content.data;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Layout {
private final String profile;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.CsvLayoutUtils;
public class Layout implements Parcelable {
private static final String TAG = Layout.class.getSimpleName();
// User-generated layout's name.
private final String name;
private int columnsPerRow;
private final List<DataField> dataFields = new ArrayList<>();
public Layout(String profile) {
this.profile = profile;
public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
if (csvParts == null) {
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
return new Layout(PreferencesUtils.getDefaultLayoutName());
}
Layout layout = new Layout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));
for (int i = 2; i < csvParts.size(); i++) {
String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i));
if (fieldParts == null) {
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
return layout;
}
layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
}
return layout;
}
public Layout(String name) {
this.name = name;
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
}
public Layout(String name, int columnsPerRow) {
this.name = name;
this.columnsPerRow = columnsPerRow;
}
protected Layout(Parcel in) {
name = in.readString();
columnsPerRow = in.readInt();
in.readList(dataFields, DataField.class.getClassLoader());
}
public static final Creator<Layout> CREATOR = new Creator<Layout>() {
@Override
public Layout createFromParcel(Parcel in) {
return new Layout(in);
}
@Override
public Layout[] newArray(int size) {
return new Layout[size];
}
};
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
dataFields.add(new DataField(key, title, visible, primary, isWide));
}
@@ -27,6 +86,11 @@ public class Layout {
dataFields.remove(dataField);
}
public void replaceAllFields(List<DataField> newFields) {
dataFields.clear();
addFields(newFields);
}
public List<DataField> getFields() {
return new ArrayList<>(dataFields);
}
@@ -36,7 +100,46 @@ public class Layout {
dataFields.add(to, dataFieldToMove);
}
public String getProfile() {
return profile;
public String getName() {
return name;
}
public int getColumnsPerRow() {
return columnsPerRow;
}
public void setColumnsPerRow(int columnsPerRow) {
this.columnsPerRow = columnsPerRow;
}
public boolean sameName(Layout layout) {
return this.name.equalsIgnoreCase(layout.getName());
}
public boolean sameName(String name) {
return this.name.equalsIgnoreCase(name);
}
public String toCsv() {
List<DataField> fields = getFields();
if (fields.isEmpty()) {
return "";
}
return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
+ CsvLayoutUtils.ITEM_SEPARATOR;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeInt(columnsPerRow);
parcel.writeList(dataFields);
}
}
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
import java.time.Duration;
import java.util.Objects;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
public class Speed {
@@ -25,6 +26,10 @@ public class Speed {
return of(Float.parseFloat(speed_mps));
}
public static Speed ofKMH(double speed_kmh) {
return of(Distance.ofKilometer(speed_kmh), Duration.ofHours(1));
}
public static Speed zero() {
return of(0.0);
}
@@ -42,10 +47,6 @@ public class Speed {
return Speed.of(Math.abs(speed1.speed_mps - speed2.speed_mps));
}
// Anything faster than that (in meters per second) will be considered moving.
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
private final double speed_mps;
private Speed(double speed_mps) {
@@ -65,7 +66,7 @@ public class Speed {
}
public boolean isMoving() {
return !isInvalid() && speed_mps >= MAX_NO_MOVEMENT_SPEED;
return !isInvalid() && greaterThan(PreferencesUtils.getIdleSpeed());
}
public boolean lessThan(Speed speed) {
@@ -91,7 +91,7 @@ public class TrackPoint {
private Type type;
private Float heartRate_bpm = null;
private Float cyclingCadence_rpm = null;
private Float cadence_rpm = null;
private Float power = null;
private Float altitudeGain_m = null;
private Float altitudeLoss_m = null;
@@ -349,8 +349,21 @@ public class TrackPoint {
return this;
}
public TrackPoint minusCumulativeSensorData(@NonNull TrackPoint lastTrackPoint) {
if (hasSensorDistance() && lastTrackPoint.hasSensorDistance()) {
sensorDistance = sensorDistance.minus(lastTrackPoint.getSensorDistance());
}
if (hasAltitudeGain() && lastTrackPoint.hasAltitudeGain()) {
altitudeGain_m -= lastTrackPoint.altitudeGain_m;
}
if (hasAltitudeLoss() && lastTrackPoint.hasAltitudeLoss()) {
altitudeLoss_m -= lastTrackPoint.altitudeLoss_m;
}
return this;
}
public boolean hasSensorData() {
return hasHeartRate() || hasCyclingCadence() || hasPower();
return hasHeartRate() || hasCadence() || hasPower();
}
public boolean hasHeartRate() {
@@ -366,16 +379,16 @@ public class TrackPoint {
return this;
}
public boolean hasCyclingCadence() {
return cyclingCadence_rpm != null;
public boolean hasCadence() {
return cadence_rpm != null;
}
public float getCyclingCadence_rpm() {
return cyclingCadence_rpm;
public float getCadence_rpm() {
return cadence_rpm;
}
public TrackPoint setCyclingCadence_rpm(Float cyclingCadence_rpm) {
this.cyclingCadence_rpm = cyclingCadence_rpm;
public TrackPoint setCadence_rpm(Float cadence_rpm) {
this.cadence_rpm = cadence_rpm;
return this;
}
@@ -396,15 +409,17 @@ public class TrackPoint {
@Override
public String toString() {
String result = "time=" + getTime() + " (type=" + getType() + ")";
if (!hasLocation()) {
return result;
if (hasLocation()) {
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
}
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
if (!hasHorizontalAccuracy()) {
return result;
if (hasHorizontalAccuracy()) {
result += " acc=" + getHorizontalAccuracy();
}
if (hasSensorDistance()) {
result += " distance=" + getSensorDistance();
}
return result + " acc=" + getHorizontalAccuracy();
return result;
}
public static class Id {
@@ -205,6 +205,26 @@ public class ContentProviderUtils {
return tracks;
}
public List<Track> getTracks(List<Track.Id> trackIds) {
if (trackIds == null || trackIds.isEmpty()) {
return getTracks();
}
String selection = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
String[] selectionArgs = trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new);
ArrayList<Track> tracks = new ArrayList<>();
try (Cursor cursor = getTrackCursor(selection, selectionArgs, TracksColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
tracks.ensureCapacity(cursor.getCount());
do {
tracks.add(createTrack(cursor));
} while (cursor.moveToNext());
}
}
return tracks;
}
public Track getTrack(@NonNull Track.Id trackId) {
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
if (cursor != null && cursor.moveToNext()) {
@@ -558,7 +578,7 @@ public class ContentProviderUtils {
trackPoint.setHeartRate_bpm(cursor.getFloat(indexes.sensorHeartRateIndex));
}
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
trackPoint.setCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
}
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
@@ -719,8 +739,8 @@ public class ContentProviderUtils {
if (trackPoint.hasHeartRate()) {
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate_bpm());
}
if (trackPoint.hasCyclingCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
if (trackPoint.hasCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCadence_rpm());
}
if (trackPoint.hasSensorDistance()) {
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM());
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting;
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;
@@ -27,11 +28,8 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 31;
@VisibleForTesting
public static final String DATABASE_NAME = "database.db";
public CustomSQLiteOpenHelper(Context context) {
this(context, DATABASE_NAME);
this(context, ((Startup) context.getApplicationContext()).getDatabaseName());
}
@VisibleForTesting
@@ -29,6 +29,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.util.FileUtils;
/**
* A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files.
@@ -54,6 +54,9 @@ public abstract class SensorData<T> {
protected abstract T getNoneValue();
public T getValue() {
if (!hasValue()) {
return null;
}
if (isRecent()) {
return value;
}
@@ -43,7 +43,6 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
return cadence;
}
@VisibleForTesting
public Speed getSpeed() {
return speed;
}
@@ -57,22 +56,24 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
@Override
protected Data getNoneValue() {
if (value != null) {
return new Data(Speed.zero(), 0f, ((Data) value).distance);
return new Data(Speed.zero(), 0f, value.distance);
} else {
return new Data(Speed.zero(), 0f, Distance.of(0));
}
}
public void compute(SensorDataRunning previous) {
Distance overallDistance = null;
if (hasTotalDistance() && previous != null && previous.hasTotalDistance()) {
overallDistance = this.totalDistance.minus(previous.totalDistance);
if (previous.hasValue() && previous.getValue().getDistance() != null) {
overallDistance = overallDistance.plus(previous.getValue().getDistance());
if (speed != null && hasTotalDistance()) {
Distance overallDistance = null;
if (previous != null && previous.hasTotalDistance()) {
overallDistance = this.totalDistance.minus(previous.totalDistance);
if (previous.hasValue() && previous.getValue().getDistance() != null) {
overallDistance = overallDistance.plus(previous.getValue().getDistance());
}
}
}
value = new Data(speed, cadence, overallDistance);
value = new Data(speed, cadence, overallDistance);
}
}
@Override
@@ -1,7 +1,10 @@
package de.dennisguse.opentracks.content.sensor;
import android.util.Pair;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
public final class SensorDataSet {
@@ -27,8 +30,36 @@ public final class SensorDataSet {
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
}
public SensorDataHeartRate getHeartRate() {
return heartRate;
public Pair<Float, String> getHeartRate() {
if (heartRate != null) {
return new Pair<>(heartRate.getValue(), heartRate.getSensorNameOrAddress());
}
return null;
}
public Pair<Float, String> getCadence() {
if (cyclingCadence != null) {
return new Pair<>(cyclingCadence.getValue(), cyclingCadence.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null) {
return new Pair<>(runningDistanceSpeedCadence.getCadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public Pair<Speed, String> getSpeed() {
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue() && cyclingDistanceSpeed.getValue().getSpeed() != null) {
return new Pair<>(cyclingDistanceSpeed.getValue().getSpeed(), cyclingDistanceSpeed.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue() && runningDistanceSpeedCadence.getValue().getSpeed() != null) {
return new Pair<>(runningDistanceSpeedCadence.getSpeed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public SensorDataCycling.Cadence getCyclingCadence() {
@@ -64,17 +95,20 @@ public final class SensorDataSet {
}
public void fillTrackPoint(TrackPoint trackPoint) {
if (heartRate != null) {
trackPoint.setHeartRate_bpm(heartRate.getValue());
if (getHeartRate() != null) {
trackPoint.setHeartRate_bpm(getHeartRate().first);
}
if (cyclingCadence != null && cyclingCadence.hasValue()) {
trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue());
if (getCadence() != null) {
trackPoint.setCadence_rpm(getCadence().first);
}
if (getSpeed() != null) {
trackPoint.setSpeed(getSpeed().first);
}
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().getDistanceOverall());
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().getSpeed());
}
if (cyclingPower != null && cyclingPower.hasValue()) {
@@ -83,8 +117,6 @@ public final class SensorDataSet {
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue()) {
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().getDistance());
trackPoint.setSpeed(runningDistanceSpeedCadence.getValue().getSpeed());
trackPoint.setCyclingCadence_rpm(runningDistanceSpeedCadence.getValue().getCadence());
}
}
@@ -93,18 +125,17 @@ public final class SensorDataSet {
if (cyclingCadence != null) cyclingCadence.reset();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
if (cyclingPower != null) cyclingPower.reset();
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
}
@NonNull
@Override
public String toString() {
return (getHeartRate() != null ? "" + getHeartRate() : "")
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
+ (getCyclingPower() != null ? " " + getCyclingPower() : "")
+ (getRunningDistanceSpeedCadence() != null ? " " + getRunningDistanceSpeedCadence() : "");
return (heartRate != null ? "" + heartRate : "")
+ (cyclingCadence != null ? " " + cyclingCadence : "")
+ (cyclingDistanceSpeed != null ? " " + cyclingDistanceSpeed : "")
+ (cyclingPower != null ? " " + cyclingPower : "")
+ (runningDistanceSpeedCadence != null ? " " + runningDistanceSpeedCadence : "");
}
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
@@ -42,7 +42,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.databinding.ChartBinding;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* A fragment to display track chart to the user.
@@ -64,8 +64,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return chartFragment;
}
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
// Stats gathered from the received data
@@ -80,8 +78,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits();
if (metricUnits != viewBinding.chartView.getMetricUnits()) {
viewBinding.chartView.setMetricUnits(metricUnits);
runOnUiThread(() -> {
@@ -91,8 +89,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
});
}
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
if (PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
boolean reportSpeed = PreferencesUtils.isReportSpeed(category);
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
viewBinding.chartView.setReportSpeed(reportSpeed);
viewBinding.chartView.applyReportSpeed();
@@ -141,9 +139,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onResume() {
super.onResume();
resumeTrackDataHub();
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
checkChartSettings();
getActivity().runOnUiThread(updateChart);
@@ -153,7 +150,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onPause() {
super.onPause();
pauseTrackDataHub();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -162,12 +159,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
viewBinding = null;
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
@Override
public void onTrackUpdated(Track track) {
if (isResumed()) {
@@ -177,7 +168,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
category = track.getCategory();
boolean reportSpeed = PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category);
boolean reportSpeed = PreferencesUtils.isReportSpeed(category);
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
viewBinding.chartView.setReportSpeed(reportSpeed);
viewBinding.chartView.applyReportSpeed();
@@ -23,7 +23,7 @@ import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
@@ -46,15 +46,13 @@ public class IntervalsFragment extends Fragment {
private IntervalStatisticsAdapter adapter;
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
private SharedPreferences sharedPreferences;
private boolean isReportSpeed;
private IntervalListViewBinding viewBinding;
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
updateIntervals(PreferencesUtils.isMetricUnits(sharedPreferences, getContext()), selectedInterval);
if (PreferencesUtils.isKey(R.string.stats_units_key, key) || PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
updateIntervals(PreferencesUtils.isMetricUnits(), selectedInterval);
if (spinnerAdapter != null) {
spinnerAdapter.notifyDataSetChanged();
}
@@ -93,8 +91,6 @@ public class IntervalsFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
adapter = new IntervalStatisticsAdapter(getContext(), stackModeListView, metricUnits, isReportSpeed);
viewBinding.intervalList.setLayoutManager(new LinearLayoutManager(getContext()));
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
@@ -136,13 +132,12 @@ public class IntervalsFragment extends Fragment {
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext());
Track track = contentProviderUtils.getTrack(trackId);
if (track != null) {
isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
isReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
}
viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class);
@@ -152,7 +147,9 @@ public class IntervalsFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
if (viewModel != null) {
viewModel.onPause();
}
@@ -168,8 +165,6 @@ public class IntervalsFragment extends Fragment {
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
adapter = null;
viewModel = null;
}
@@ -41,7 +41,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.StatisticData;
@@ -79,21 +79,20 @@ public class StatisticsRecordedFragment extends Fragment {
private StatisticsRecordedBinding viewBinding;
private SharedPreferences sharedPreferences;
private boolean preferenceMetricUnits;
private boolean preferenceReportSpeed;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
boolean updateUInecessary = false;
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
updateUInecessary = true;
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && track != null) {
if (PreferencesUtils.isKey(R.string.stats_rate_key, key) && track != null) {
updateUInecessary = true;
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
preferenceReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
}
if (key != null && updateUInecessary && isResumed()) {
@@ -112,8 +111,6 @@ public class StatisticsRecordedFragment extends Fragment {
trackId = getArguments().getParcelable(TRACK_ID_KEY);
contentProviderUtils = new ContentProviderUtils(getContext());
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
}
@@ -132,24 +129,22 @@ public class StatisticsRecordedFragment extends Fragment {
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
loadStatistics();
}
@Override
public void onPause() {
super.onPause();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDestroyView() {
super.onDestroyView();
viewBinding = null;
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
public void loadStatistics() {
@@ -168,7 +163,7 @@ public class StatisticsRecordedFragment extends Fragment {
boolean prefsChanged = this.track == null || (!this.track.getCategory().equals(track.getCategory()));
this.track = track;
if (prefsChanged) {
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
}
loadTrackDescription(track);
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.StatisticData;
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
@@ -55,24 +55,20 @@ public class StatisticsRecordingFragment extends Fragment {
private StatisticsDataModel viewModel;
private LiveData<List<StatisticData>> statisticsLiveData;
private SharedPreferences sharedPreferences;
private boolean preferenceMetricUnits;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
boolean updateUInecessary = false;
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
updateUInecessary = true;
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_fields_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_custom_layouts_key, key) || PreferencesUtils.isKey(R.string.stats_custom_layout_selected_layout_key, key)) {
updateUInecessary = true;
layout = PreferencesUtils.getCustomLayout(sharedPreferences, getContext());
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_columns_key, key)) {
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns(sharedPreferences, getContext()));
layout = PreferencesUtils.getCustomLayout();
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
}
if (key != null && updateUInecessary && isResumed()) {
@@ -97,7 +93,6 @@ public class StatisticsRecordingFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
statisticsAdapter = new StatisticsAdapter(getContext());
@@ -109,13 +104,13 @@ public class StatisticsRecordingFragment extends Fragment {
RecyclerView recyclerView = viewBinding.statsRecyclerView;
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
final int numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, getContext());
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
layout = PreferencesUtils.getCustomLayout();
gridLayoutManager = new GridLayoutManager(getContext(), layout.getColumnsPerRow());
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (statisticsAdapter.isItemWide(position)) {
return numColumns;
return layout.getColumnsPerRow();
}
return 1;
}
@@ -130,8 +125,7 @@ public class StatisticsRecordingFragment extends Fragment {
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
statisticsLiveData = viewModel.getStatsData();
@@ -143,7 +137,7 @@ public class StatisticsRecordingFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -162,9 +156,12 @@ public class StatisticsRecordingFragment extends Fragment {
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection = null;
sharedPreferences = null;
viewModel = null;
statisticsLiveData.removeObservers(getActivity());
if (statisticsLiveData != null) {
statisticsLiveData.removeObservers(getActivity());
}
statisticsLiveData = null;
}
private void updateUI() {
@@ -179,7 +176,7 @@ public class StatisticsRecordingFragment extends Fragment {
this.recordingData = recordingData;
if (!oldCategory.equals(newCategory)) {
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
}
latestTrackPoint = recordingData.getLatestTrackPoint();
@@ -6,6 +6,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService;
@@ -24,6 +25,7 @@ public class ExportService extends JobIntentService {
private static final String EXTRA_TRACK_ID = "extra_track_id";
private static final String EXTRA_TRACK_FILE_FORMAT = "extra_track_file_format";
private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri";
private static final String TAG = ExportService.class.getSimpleName();
public static void enqueue(Context context, ExportServiceResultReceiver receiver, Track.Id trackId, TrackFileFormat trackFileFormat, Uri directoryUri) {
Intent intent = new Intent(context, JobService.class);
@@ -42,18 +44,23 @@ public class ExportService extends JobIntentService {
TrackFileFormat trackFileFormat = (TrackFileFormat) intent.getSerializableExtra(EXTRA_TRACK_FILE_FORMAT);
Uri directoryUri = intent.getParcelableExtra(EXTRA_DIRECTORY_URI);
// Prepare resultCode and bundle to send to the receiver.
Bundle bundle = new Bundle();
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId);
// Build directory file.
DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri);
if (directoryFile == null || !directoryFile.canWrite()) {
Log.e(TAG, "Can't write to directory: " + directoryFile);
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle);
return;
}
// Export.
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Track track = contentProviderUtils.getTrack(trackId);
boolean success = ExportUtils.exportTrack(this, trackFileFormat, directoryFile, track);
// Prepare resultCode and bundle to send to the receiver.
Bundle bundle = new Bundle();
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId);
// Send result to the receiver.
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR;
resultReceiver.send(resultCode, bundle);
@@ -285,7 +285,7 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
if (trackPoint.hasSpeed()) {
@@ -296,8 +296,8 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
}
if (trackPoint.hasCyclingCadence()) {
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
if (trackPoint.hasCadence()) {
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "</gpxtpx:cad>");
}
if (trackPoint.hasPower()) {
@@ -382,7 +382,7 @@ public class KMLTrackExporter implements TrackExporter {
distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : null);
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
cadenceList.add(trackPoint.hasCadence() ? trackPoint.getCadence_rpm() : null);
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
altitudeGainList.add(trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : null);
@@ -14,7 +14,7 @@ import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.databinding.TrackSharePictureBinding;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
@@ -61,7 +61,7 @@ public class SharePictureTrackExporter implements TrackExporter {
TrackSharePictureBinding viewBinding = TrackSharePictureBinding.inflate(LayoutInflater.from(context));
viewBinding.sharePictureCategory.setText(track.getCategory());
boolean metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
boolean metricUnits = PreferencesUtils.isMetricUnits();
TrackStatistics trackStatistics = track.getTrackStatistics();
viewBinding.sharePictureTotalTime.setText(StringUtils.formatElapsedTimeWithHour(track.getTrackStatistics().getTotalTime()));
@@ -2,8 +2,8 @@ package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.DocumentsContract;
@@ -13,23 +13,19 @@ import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public abstract class DirectoryChooserActivity extends AppCompatActivity {
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
protected SharedPreferences sharedPreferences;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
if (directoryUri == null) {
if (!isDirectoryValid(directoryUri)) {
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
} else {
startActivity(createNextActivityIntent(directoryUri.getUri())); //TODO Refactor to DocumentFile
@@ -37,18 +33,16 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
}
}
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
return directoryUri != null && directoryUri.isDirectory() && directoryUri.canRead();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
onActivityResultCustom(requestCode, resultCode, resultData);
}
@Override
protected void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) {
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
@@ -91,14 +85,19 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
return PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this);
return PreferencesUtils.getDefaultExportDirectoryUri(this);
}
@Override
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
}
@Override
protected Intent createNextActivityIntent(Uri directoryUri) {
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(sharedPreferences, this));
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat());
return intent;
}
}
@@ -110,14 +109,15 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
switch (resultCode) {
case RESULT_OK:
Uri directoryUri = resultData.getData();
IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), PreferencesUtils.getDefaultExportDirectoryUri(this));
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, directoryUri);
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri);
Uri directoryUri = resultData.getData();
PreferencesUtils.setDefaultExportDirectoryUri(directoryUri);
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri, resultData.getFlags());
break;
case RESULT_CANCELED:
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, null);
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), PreferencesUtils.getDefaultExportDirectoryUri(this));
PreferencesUtils.setDefaultExportDirectoryUri(null);
break;
}
@@ -125,12 +125,19 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
finish();
}
@Override
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
}
@Override
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, this)) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this).getUri());
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
if (PreferencesUtils.isDefaultExportDirectoryUri(this)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri());
}
}
return null;
}
@@ -70,6 +70,10 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
private static final String ATTRIBUTE_LON = "lon";
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
/**
* Often speed is exported without the proper namespace.
*/
private static final String TAG_EXTENSION_SPEED_COMPAT = "speed";
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
@@ -192,6 +196,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
}
break;
case TAG_EXTENSION_SPEED:
case TAG_EXTENSION_SPEED_COMPAT:
if (content != null) {
speed = content.trim();
}
@@ -295,7 +300,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
if (cadence != null) {
try {
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
trackPoint.setCadence_rpm(Float.parseFloat(cadence));
} catch (Exception e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
}
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.io.file.importer;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -22,7 +21,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class ImportService extends JobIntentService {
@@ -54,10 +53,9 @@ public class ImportService extends JobIntentService {
String fileExtension = FileUtils.getExtension(file);
try {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(this);
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, this);
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, this);
boolean preventReimport = PreferencesUtils.getPreventReimportTracks(sharedPreferences, this);
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), recordingDistanceInterval, maxRecordingDistance, preventReimport);
@@ -316,7 +316,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
trackPoint.setHeartRate_bpm(sensorHeartRateList.get(i));
}
if (i < sensorCadenceList.size()) {
trackPoint.setCyclingCadence_rpm(sensorCadenceList.get(i));
trackPoint.setCadence_rpm(sensorCadenceList.get(i));
}
if (i < sensorPowerList.size()) {
trackPoint.setPower(sensorPowerList.get(i));
@@ -144,6 +144,8 @@ public class TrackImporter {
return 0;
});
adjustTrackPoints();
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
updater.addTrackPoints(trackPoints, recordingDistanceInterval);
track.setTrackStatistics(updater.getTrackStatistics());
@@ -151,7 +153,6 @@ public class TrackImporter {
Track.Id trackId = contentProviderUtils.insertTrack(track);
// Store TrackPoints
adjustTrackPoints();
contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId);
// Store Markers
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.services;
import android.content.ContentUris;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.util.Log;
@@ -18,9 +17,9 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackNameUtils;
@@ -37,9 +36,9 @@ class TrackRecordingManager {
private Track.Id trackId;
private TrackStatisticsUpdater trackStatisticsUpdater;
private boolean currentSegmentHasTrackPoint;
private TrackPoint lastTrackPoint;
private TrackPoint lastValidTrackPoint;
private boolean isIdle;
private TrackPoint lastStoredTrackPoint;
TrackRecordingManager(Context context) {
this.context = context;
@@ -59,12 +58,14 @@ class TrackRecordingManager {
//TODO Pass TrackPoint
track.setName(TrackNameUtils.getTrackName(context, trackId, segmentStartTrackPoint.getTime()));
String category = PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), context); //TODO Re-use sharedpreferences
String category = PreferencesUtils.getDefaultActivity();
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(track);
currentSegmentHasTrackPoint = false;
return trackId;
}
@@ -79,40 +80,38 @@ class TrackRecordingManager {
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
insertTrackPoint(trackId, segmentStartTrackPoint);
}
currentSegmentHasTrackPoint = false;
lastTrackPoint = null;
lastStoredTrackPoint = null; }
void pause(TrackPointCreator trackPointCreator) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
}
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
currentSegmentHasTrackPoint = false;
lastTrackPoint = null;
lastStoredTrackPoint = null;
}
void end(TrackPointCreator trackPointCreator) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
}
TrackPoint segmentEnd = trackPointCreator.createSegmentEnd();
insertTrackPoint(trackId, segmentEnd);
trackId = null;
trackStatisticsUpdater = null;
lastTrackPoint = null;
lastValidTrackPoint = null;
isIdle = false;
lastStoredTrackPoint = null;
currentSegmentHasTrackPoint = false;
}
Pair<Track, Pair<TrackPoint, SensorDataSet>> get(TrackPointCreator trackPointCreator) {
if (trackPointCreator == null) {
return null;
}
TrackStatisticsUpdater tmpTrackStatisticsUpdater = getTrackStatisticsUpdater();
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater);
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPoint);
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
Track track = getTrack(); //Get copy
Track track = contentProviderUtils.getTrack(trackId); //Get copy
if (track == null) {
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
return null;
@@ -132,8 +131,7 @@ class TrackRecordingManager {
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
}
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(trackId);
if (trackPoint == null) {
if (lastStoredTrackPoint == null) {
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
return null;
}
@@ -143,131 +141,74 @@ class TrackRecordingManager {
String icon = context.getString(R.string.marker_icon_url);
photoUrl = photoUrl != null ? photoUrl : "";
// Insert marker
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), trackPoint, photoUrl);
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPoint, photoUrl);
Uri uri = contentProviderUtils.insertMarker(marker);
return new Marker.Id(ContentUris.parseId(uri));
}
void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(trackId);
boolean onNewTrackPoint(TrackPoint trackPoint) {
//Storing trackPoint
// Always insert the first segment location
if (!currentSegmentHasTrackPoint()) {
if (!currentSegmentHasTrackPoint) {
insertTrackPoint(trackId, trackPoint);
lastTrackPoint = trackPoint;
return;
currentSegmentHasTrackPoint = true;
return true;
}
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastStoredTrackPoint);
if (distanceToLastTrackLocation != null) {
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackId, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
return true;
}
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
if (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving()) {
insertTrackPoint(trackId, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
return true;
}
}
if (!isIdle && !trackPoint.isMoving()) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
if (lastStoredTrackPoint != null && trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) {
// Moving from non-moving to moving or vice versa; required to compute moving time correctly.
insertTrackPoint(trackId, trackPoint);
isIdle = true;
lastTrackPoint = trackPoint;
return;
return true;
}
if (isIdle && trackPoint.isMoving()) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
insertTrackPoint(trackId, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
}
Log.d(TAG, "Not recording TrackPoint, idle");
Log.d(TAG, "Not recording TrackPoint");
lastTrackPoint = trackPoint;
}
Track getTrack() {
return contentProviderUtils.getTrack(trackId);
}
//Functionality that uses this method should happen here.
@Deprecated
TrackStatisticsUpdater getTrackStatisticsUpdater() {
return new TrackStatisticsUpdater(trackStatisticsUpdater);
return false;
}
TrackStatistics getTrackStatistics() {
return trackStatisticsUpdater.getTrackStatistics();
}
/**
* Gets the last valid track point in the current segment.
*
* @param trackId the track id
* @return the location or null
*/
@Deprecated
//Use lastValidTrackPoint
private TrackPoint getLastValidTrackPointInCurrentSegment(Track.Id trackId) {
if (!currentSegmentHasTrackPoint()) {
return null;
}
return contentProviderUtils.getLastValidTrackPoint(trackId);
}
private boolean currentSegmentHasTrackPoint() {
return lastTrackPoint != null;
}
/**
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
*/
private void insertTrackPointIfNewer(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(trackId);
if (lastValidTrackPoint != null && trackPoint.getTime().equals(lastValidTrackPoint.getTime())) {
// Do not insert if inserted already
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
return;
}
insertTrackPoint(trackId, trackPoint);
}
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
if (lastTrackPoint != null) {
if (lastStoredTrackPoint != null && lastTrackPoint.getTime().equals(lastStoredTrackPoint.getTime())) {
// Do not insert if inserted already
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
} else {
insertTrackPointHelper(trackId, lastTrackPoint);
// Remove the sensorDistance from trackPoint that is already going be stored with lastTrackPoint.
trackPoint.minusCumulativeSensorData(lastTrackPoint);
}
lastTrackPoint = null;
}
insertTrackPointHelper(trackId, trackPoint);
}
private void insertTrackPointHelper(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
try {
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics());
lastStoredTrackPoint = trackPoint;
} catch (SQLiteException e) {
/*
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
@@ -277,12 +218,12 @@ class TrackRecordingManager {
}
}
public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
public void onSharedPreferenceChanged(String key) {
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
}
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.max_recording_distance_key, key)) {
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
}
}
}
@@ -54,7 +54,7 @@ import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.ExportUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
/**
@@ -96,25 +96,22 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
};
private SharedPreferences sharedPreferences;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits();
notificationManager.setMetricUnits(metricUnits);
}
if (PreferencesUtils.isKey(context, R.string.voice_announcement_frequency_key, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency(sharedPreferences, context));
if (PreferencesUtils.isKey(R.string.voice_announcement_frequency_key, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency());
}
if (PreferencesUtils.isKey(context, new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementDistance(sharedPreferences, context));
if (PreferencesUtils.isKey(new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementDistance());
}
trackPointCreator.onSharedPreferenceChanged(sharedPreferences, key);
trackRecordingManager.onSharedPreferenceChanged(sharedPreferences, key);
trackPointCreator.onSharedPreferenceChanged(key);
trackRecordingManager.onSharedPreferenceChanged(key);
}
};
@@ -148,9 +145,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
notificationManager = new TrackRecordingServiceNotificationManager(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -173,8 +168,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
// Reverse order from onCreate
showNotification(false); //TODO Why?
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferences = null;
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
try {
voiceAnnouncementManager.shutdown();
@@ -360,14 +354,14 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
@Override
public void newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
public boolean newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
if (!isRecording() || isPaused()) {
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
return;
return false;
}
trackRecordingManager.onNewTrackPoint(trackPoint, thresholdHorizontalAccuracy);
notificationManager.updateTrackPoint(this, trackRecordingManager.getTrackStatistics(), trackPoint, thresholdHorizontalAccuracy);
return trackRecordingManager.onNewTrackPoint(trackPoint);
}
@Override
@@ -382,16 +376,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private void showNotification(boolean isGpsStarted) {
if (isRecording()) {
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingStatus.getTrackId());
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class);
int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackRecordingActivity.class)
.addNextIntent(intent)
.addNextIntentWithParentStack(intent)
.getPendingIntent(0, pendingIntentFlags);
notificationManager.updatePendingIntent(pendingIntent);
@@ -403,9 +396,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
int pendingIntentFlags = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntentFlags = PendingIntent.FLAG_IMMUTABLE;
pendingIntentFlags = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
}
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackListActivity.class)
.addNextIntent(intent)
.getPendingIntent(0, pendingIntentFlags);
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.LocationManager;
import android.os.Handler;
@@ -13,7 +12,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* This class handle GPS status according to received locations` and some thresholds.
@@ -68,10 +67,9 @@ class GpsStatus {
this.client = client;
this.context = context;
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
thresholdHorizontalAccuracy = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
thresholdHorizontalAccuracy = PreferencesUtils.getRecordingDistanceInterval();
Duration minRecordingInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
Duration minRecordingInterval = PreferencesUtils.getMinRecordingInterval();
signalLostThreshold = SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval);
gpsStatusHandler = new Handler();
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
@@ -17,7 +16,7 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class LocationHandler implements LocationListener, GpsStatus.GpsStatusListener {
@@ -35,8 +34,8 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
this.trackPointCreator = trackPointCreator;
}
public void onStart(@NonNull Context context, SharedPreferences sharedPreferences) {
onSharedPreferenceChanged(context, sharedPreferences, null);
public void onStart(@NonNull Context context) {
onSharedPreferenceChanged(null);
gpsStatus = new GpsStatus(context, this);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
registerLocationListener();
@@ -62,26 +61,26 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
}
}
public void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences sharedPreferences, String key) {
public void onSharedPreferenceChanged(String key) {
boolean registerListener = false;
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
if (PreferencesUtils.isKey(R.string.min_recording_interval_key, key)) {
registerListener = true;
gpsInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
gpsInterval = PreferencesUtils.getMinRecordingInterval();
if (gpsStatus != null) {
gpsStatus.onMinRecordingIntervalChanged(gpsInterval);
}
}
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.recording_gps_accuracy_key, key)) {
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
}
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
registerListener = true;
if (gpsStatus != null) {
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval();
gpsStatus.onRecordingDistanceChanged(gpsMinDistance);
}
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.util.Pair;
@@ -17,7 +16,6 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
@@ -51,8 +49,7 @@ public class TrackPointCreator {
public void start(@NonNull Context context) {
this.context = context;
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
locationHandler.onStart(context, sharedPreferences);
locationHandler.onStart(context);
remoteSensorManager = new BluetoothRemoteSensorManager(context);
remoteSensorManager.start();
@@ -77,23 +74,13 @@ public class TrackPointCreator {
altitudeSumManager.reset();
}
//TODO TrackPoint should be created by TrackPointCreator; instead of in the TrackRecordingService.
@Deprecated
public SensorDataSet fill(TrackPoint trackPoint) {
private SensorDataSet fill(TrackPoint trackPoint) {
SensorDataSet sensorDataSet = remoteSensorManager.fill(trackPoint);
altitudeSumManager.fill(trackPoint);
return sensorDataSet;
}
public SensorDataSet fillAndReset(TrackPoint trackPoint) {
SensorDataSet sensorDataSet = fill(trackPoint);
resetSensorData();
return sensorDataSet;
}
public void stop() {
locationHandler.onStop();
@@ -110,19 +97,17 @@ public class TrackPointCreator {
this.context = null;
}
public void onSharedPreferenceChanged(@NonNull SharedPreferences preferences, String key) {
if (context == null) {
Log.w(TAG, "not started yet.");
return;
}
locationHandler.onSharedPreferenceChanged(context, preferences, key);
public void onSharedPreferenceChanged(String key) {
locationHandler.onSharedPreferenceChanged(key);
}
public void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
fillAndReset(trackPoint);
fill(trackPoint);
service.newTrackPoint(trackPoint, thresholdHorizontalAccuracy);
boolean stored = service.newTrackPoint(trackPoint, thresholdHorizontalAccuracy);
if (stored) {
resetSensorData();
}
}
public TrackPoint createSegmentStartManual() {
@@ -131,7 +116,8 @@ public class TrackPointCreator {
public TrackPoint createSegmentEnd() {
TrackPoint segmentEnd = TrackPoint.createSegmentEndWithTime(createNow());
fillAndReset(segmentEnd);
fill(segmentEnd);
resetSensorData();
return segmentEnd;
}
@@ -189,7 +175,10 @@ public class TrackPointCreator {
}
public interface Callback {
void newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy);
/**
* @return Was TrackPoint stored (not discarded)?
*/
boolean newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy);
void newGpsStatus(GpsStatusValue gpsStatusValue);
}
@@ -70,6 +70,19 @@ public class AltitudeSumManager implements SensorEventListener {
return isConnected ? altitudeGain_m : null;
}
@VisibleForTesting
public void addAltitudeGain_m(float altitudeGain_m) {
this.altitudeGain_m = this.altitudeGain_m == null ? 0f : this.altitudeGain_m;
this.altitudeGain_m += altitudeGain_m ;
}
@VisibleForTesting
public void addAltitudeLoss_m(Float altitudeLoss_m) {
this.altitudeLoss_m = this.altitudeLoss_m == null ? 0f : this.altitudeLoss_m;
this.altitudeLoss_m += altitudeLoss_m ;
}
public @Nullable
Float getAltitudeLoss_m() {
return isConnected ? altitudeLoss_m : null;
@@ -69,6 +69,8 @@ public abstract class BluetoothConnectionManager<DataType> {
case BluetoothProfile.STATE_DISCONNECTED:
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
clearData();
break;
}
}
@@ -130,14 +132,18 @@ public abstract class BluetoothConnectionManager<DataType> {
observer.onChanged(sensorData);
}
private synchronized void clearData() {
observer.onDisconnecting(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
}
synchronized void disconnect() {
if (bluetoothGatt == null) {
Log.w(TAG, "Cannot disconnect if not connected.");
return;
}
bluetoothGatt.close();
observer.onDisconnecting(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
clearData();
bluetoothGatt = null;
}
@@ -34,7 +34,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
@@ -61,7 +61,6 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
private boolean started = false;
private final SharedPreferences sharedPreferences;
private Distance preferenceWheelCircumference;
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
@@ -77,34 +76,34 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (!started) return;
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress();
connect(heartRate, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress();
connect(cyclingCadence, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress();
connect(cyclingSpeed, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference();
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress();
connect(cyclingPower, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress();
connect(runningSpeedAndCadence, address);
}
@@ -113,16 +112,14 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
public BluetoothRemoteSensorManager(Context context) {
this.context = context;
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
bluetoothAdapter = BluetoothUtils.getAdapter(context);
}
public void start() {
started = true;
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
//Trigger connection startup
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
//Registering triggers connection startup
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
public synchronized void stop() {
@@ -134,7 +131,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
sensorDataSet.clear();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
started = false;
}
@@ -148,7 +145,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return;
}
if (PreferencesUtils.isBluetoothSensorAddressNone(context, address)) {
if (PreferencesUtils.isBluetoothSensorAddressNone(address)) {
Log.w(TAG, "No Bluetooth address.");
connectionManager.disconnect();
return;
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
@@ -12,12 +12,12 @@ import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
public class AnnouncementUtils {
class AnnouncementUtils {
private AnnouncementUtils() {
}
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
Distance distance = trackStatistics.getTotalDistance();
Speed distancePerTime = trackStatistics.getAverageMovingSpeed();
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.speech.tts.TextToSpeech;
@@ -35,8 +34,7 @@ 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.stats.TrackStatistics;
import de.dennisguse.opentracks.util.AnnouncementUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
/**
@@ -103,8 +101,6 @@ public class VoiceAnnouncement {
}
};
private SharedPreferences sharedPreferences;
private TextToSpeech tts;
// Response from TTS after its initialization
private int ttsInitStatus = TextToSpeech.ERROR;
@@ -122,8 +118,6 @@ public class VoiceAnnouncement {
public void start() {
Log.d(TAG, "Start");
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
if (tts == null) {
tts = new TextToSpeech(context, status -> {
Log.i(TAG, "TextToSpeech initialized with status " + status);
@@ -161,9 +155,9 @@ public class VoiceAnnouncement {
}
boolean isMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, track.getCategory());
Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
boolean isMetricUnits = PreferencesUtils.isMetricUnits();
boolean isReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval();
//TODO Do not load all trackpoints for every announcement
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
@@ -187,8 +181,6 @@ public class VoiceAnnouncement {
ttsFallback.release();
ttsFallback = null;
}
sharedPreferences = null;
}
private void onTtsReady() {
@@ -203,7 +195,7 @@ public class VoiceAnnouncement {
*/
}
tts.setLanguage(locale);
tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate(PreferencesUtils.getSharedPreferences(context), context));
tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate());
tts.setOnUtteranceProgressListener(utteranceListener);
}
}
@@ -23,7 +23,6 @@ import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.services.TrackRecordingService;
@@ -63,13 +62,8 @@ public class VoiceAnnouncementManager {
voiceAnnouncement.start();
this.trackStatistics = trackStatistics;
if (!totalTimeFrequency.isZero()) {
nextTotalTime = calculateNextDuration();
}
if (!distanceFrequency.isZero()) {
nextTotalDistance = calculateNextTaskDistance();
}
updateNextDuration();
updateNextTaskDistance();
}
public void update(@NonNull Track track) {
@@ -81,11 +75,11 @@ public class VoiceAnnouncementManager {
boolean announce = false;
this.trackStatistics = track.getTrackStatistics();
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
nextTotalDistance = calculateNextTaskDistance();
updateNextTaskDistance();
announce = true;
}
if (!trackStatistics.getTotalTime().minus(nextTotalTime).isNegative()) {
nextTotalTime = calculateNextDuration();
updateNextDuration();
announce = true;
}
@@ -111,27 +105,29 @@ public class VoiceAnnouncementManager {
restore(this.trackStatistics);
}
@VisibleForTesting
public Distance calculateNextTaskDistance() {
if (trackStatistics == null) {
return DISTANCE_OFF;
public void updateNextTaskDistance() {
if (trackStatistics == null || distanceFrequency.isZero()) {
nextTotalDistance = DISTANCE_OFF;
} else {
Distance distance = trackStatistics.getTotalDistance();
int index = (int) (distance.dividedBy(distanceFrequency));
nextTotalDistance = distanceFrequency.multipliedBy(index + 1);
}
Distance distance = trackStatistics.getTotalDistance();
int index = (int) (distance.dividedBy(distanceFrequency));
return distanceFrequency.multipliedBy(index + 1);
}
private Duration calculateNextDuration() {
if (trackStatistics == null) {
return TOTALTIME_OFF;
private void updateNextDuration() {
if (trackStatistics == null || totalTimeFrequency.isZero()) {
nextTotalTime = TOTALTIME_OFF;
} else {
Duration totalTime = trackStatistics.getTotalTime();
Duration intervalMod = Duration.ofMillis(trackStatistics.getTotalTime().toMillis() % totalTimeFrequency.toMillis());
nextTotalTime = totalTime.plus(totalTimeFrequency.minus(intervalMod));
}
Duration totalTime = trackStatistics.getTotalTime();
Duration intervalMod = Duration.ofMillis(trackStatistics.getTotalTime().toMillis() % totalTimeFrequency.toMillis());
return totalTime.plus(totalTimeFrequency.minus(intervalMod));
}
@VisibleForTesting
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
@@ -31,7 +30,6 @@ import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.HackUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
/**
@@ -49,7 +47,7 @@ public class ActivityTypePreference extends DialogPreference {
setDialogIcon(null);
setPersistent(true);
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), ActivityTypePreference.this.getContext());
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity();
setSummaryProvider(summaryProvider);
}
@@ -61,8 +59,6 @@ public class ActivityTypePreference extends DialogPreference {
public static class ActivityPreferenceDialog extends PreferenceDialogFragmentCompat {
private SharedPreferences sharedPreferences;
private AutoCompleteTextView textView;
private ImageView iconView;
@@ -80,11 +76,9 @@ public class ActivityTypePreference extends DialogPreference {
super.onBindDialogView(view);
final Context context = getActivity();
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
textView = view.findViewById(R.id.activity_type_preference_text_view);
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, context);
String category = PreferencesUtils.getDefaultActivity();
textView.setText(category);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
textView.setAdapter(adapter);
@@ -105,14 +99,8 @@ public class ActivityTypePreference extends DialogPreference {
updateIcon(TrackIconUtils.getIconValue(context, category));
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
private void showIconSelectDialog() {
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, getActivity());
String category = PreferencesUtils.getDefaultActivity();
ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
}
@@ -121,13 +109,14 @@ public class ActivityTypePreference extends DialogPreference {
if (positiveResult) {
String newDefaultActivity = textView.getText().toString();
if (getPreference().callChangeListener(newDefaultActivity)) {
PreferencesUtils.setDefaultActivity(sharedPreferences, getActivity(), newDefaultActivity);
PreferencesUtils.setDefaultActivity(newDefaultActivity);
HackUtils.invalidatePreference(getPreference());
}
}
}
public void updateUI(String iconValue) {
updateIcon(iconValue);
textView.setText(getActivity().getString(TrackIconUtils.getIconActivityType(iconValue)));
textView.clearFocus();
}
@@ -0,0 +1,33 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
public class AnnouncementsSettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_announcements);
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_announcements_title);
}
@Override
public void onResume() {
super.onResume();
ListPreference voiceFrequency = findPreference(getString(R.string.voice_announcement_frequency_key));
voiceFrequency.setEntries(PreferencesUtils.getVoiceAnnouncementFrequencyEntries());
ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key));
voiceDistance.setEntries(PreferencesUtils.getVoiceAnnouncementDistanceEntries());
}
}
@@ -0,0 +1,83 @@
package de.dennisguse.opentracks.settings;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.HackUtils;
public class DefaultsSettingsFragment extends PreferenceFragmentCompat implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
// Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData.
private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
getActivity().runOnUiThread(this::updateUnits);
}
};
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_defaults);
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_defaults_title);
}
@Override
public void onResume() {
super.onResume();
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
updateUnits();
}
@Override
public void onPause() {
super.onPause();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
if (preference instanceof ActivityTypePreference) {
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
dialogFragment = activityPreferenceDialog;
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
return;
}
super.onDisplayPreferenceDialog(preference);
}
private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits();
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
statsRatePreferences.setEntries(entries);
HackUtils.invalidatePreference(statsRatePreferences);
}
@Override
public void onChooseActivityTypeDone(String iconValue) {
if (activityPreferenceDialog != null) {
activityPreferenceDialog.updateUI(iconValue);
}
}
}
@@ -0,0 +1,81 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.util.StringUtils;
public class GpsSettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_gps);
findPreference(getString(R.string.recording_distance_interval_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getRecordingDistanceInterval();
return getString(R.string.settings_recording_location_frequency_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.max_recording_distance_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getMaxRecordingDistance();
return getString(R.string.settings_recording_max_recording_distance_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.recording_gps_accuracy_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getThresholdHorizontalAccuracy();
return getString(R.string.settings_recording_min_required_accuracy_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.min_recording_interval_key))
.setSummaryProvider(
preference -> {
Duration interval = PreferencesUtils.getMinRecordingInterval();
return getString(R.string.settings_recording_location_frequency_summary, getString(R.string.value_integer_second, interval.getSeconds()));
}
);
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_gps_title);
}
@Override
public void onResume() {
super.onResume();
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
minRecordingInterval.setEntries(PreferencesUtils.getMinRecordingIntervalEntries());
ListPreference recordingDistanceInterval = findPreference(getString(R.string.recording_distance_interval_key));
recordingDistanceInterval.setEntries(PreferencesUtils.getRecordingDistanceIntervalEntries());
ListPreference maxRecordingDistance = findPreference(getString(R.string.max_recording_distance_key));
maxRecordingDistance.setEntries(PreferencesUtils.getMaxRecordingDistanceEntries());
ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key));
recordingGpsAccuracy.setEntries(PreferencesUtils.getThresholdHorizontalAccuracyEntries());
ListPreference idleSpeed = findPreference(getString(R.string.idle_speed_key));
idleSpeed.setEntries(PreferencesUtils.getIdleSpeedEntries());
}
}

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