Compare commits

...

398 Commits

Author SHA1 Message Date
Dennis Guse 1672a5c722 Release: v3.17.3 2021-04-27 21:03:44 +02:00
Román Martínez bf552a9219 Bugfix: crash deleting when activity restarts. Fixes #705. 2021-04-27 19:54:02 +02:00
Dennis Guse 348f697b56 TrackRecordingActivity needs to update TrackDataHub onResume().
Part of #719.
2021-04-27 19:02:53 +02:00
Dennis Guse 887d4700d4 TrackDataHub computes TrackStatistics and passes it to listeners.
Part of #719.
2021-04-27 19:02:53 +02:00
Dennis Guse 391dfd30e2 TrackDataHub does not need to register for status changes of TrackRecordingService.
This is handled by TrackRecordingActivity alone.
Part of #719.
2021-04-27 19:02:53 +02:00
Dennis Guse b416857001 TrackRecordingService log warnings if unexpected methods are invoked. 2021-04-27 19:02:53 +02:00
Dennis Guse df3720ef0d Bugfix: TrackDataManager.unregisterTrackDataListener was not removing listener. 2021-04-27 19:02:53 +02:00
Román Martínez b784a47af6 Bugfix: track stats always show pace. Fixes #726. 2021-04-25 15:21:08 +02:00
Dennis Guse 0f3156e71b i18n: French error message has no zero. 2021-04-22 18:36:45 +02:00
Dennis Guse 0035c85922 AboutActivity: back via top left arrow is working now.
Fixes #727.
2021-04-22 18:23:18 +02:00
Román Martínez 976a50ca15 Bugfixed spanish translation. Fixes #725. 2021-04-22 09:52:46 +02:00
mondstern c0ad53eef1 Translated using Weblate (Portuguese)
Currently translated at 78.6% (416 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt/
2021-04-21 19:57:41 +02:00
Quang Trung 1192b02c0a Translated using Weblate (Vietnamese)
Currently translated at 78.6% (416 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-04-21 19:57:41 +02:00
mondstern 7a3abe9962 Translated using Weblate (Hungarian)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/hu/
2021-04-21 19:57:41 +02:00
mondstern 60b989c13a Translated using Weblate (French)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-04-21 19:57:41 +02:00
mondstern 340f16dc99 Translated using Weblate (French)
Currently translated at 91.1% (482 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-04-21 19:57:41 +02:00
Max Schallert 7cf29f7cea Translated using Weblate (German)
Currently translated at 95.4% (505 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-04-21 19:57:41 +02:00
dial25sd e39352cc9e Translated using Weblate (German)
Currently translated at 95.4% (505 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-04-21 19:57:41 +02:00
Dennis Guse e2964e805e Bugfix: NPE in voice announcements using pace.
Fixes #724.
2021-04-21 19:57:29 +02:00
Dennis Guse 8cab43d558 Release: v3.17.2 2021-04-19 19:38:31 +02:00
Dennis Guse 13f2a09652 Bugfix: AnnouncementPeriodicTask does not need to register as TrackRecordingService.Listener. 2021-04-19 19:29:51 +02:00
Dennis Guse 7d4072c0d8 Cleanup: ChartPoint uses null instead of NaN. 2021-04-18 17:50:16 +02:00
Dennis Guse 310b35713d ChartFragmentTest: added test for small changes. 2021-04-18 14:39:48 +02:00
Dennis Guse 7e6237de3a Release: v3.17.1 2021-04-17 22:40:05 +02:00
Román Martínez 772dad4837 announce called after getting recordingTrackId. Fixed a potencial race condition. 2021-04-17 22:26:23 +02:00
Román Martínez 99126fa357 Distance interval bugfixed: interval options are km/mi values. 2021-04-17 21:16:32 +02:00
Román Martínez b2c91d7592 registerListener for barometric sensor uses SENSOR_DELAY_FASTEST instead of nanoseconds. Fixes #714. 2021-04-17 19:34:05 +02:00
Rodrigo Oliveira 3c5a1be16b Fix #711: declare TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE on queries element
This enables the use of TextToSpeech engine on Android 11.
Ref.: https://developer.android.com/reference/android/speech/tts/TextToSpeech
2021-04-17 19:31:26 +02:00
Dennis Guse 01d83deefd Release: v3.17.0 2021-04-13 20:40:51 +02:00
Dennis Guse bc2a3ddee3 IntervalStatistics/Announcement: use Distance. 2021-04-13 19:18:18 +02:00
Dennis Guse da70fca389 Bugfix: TrackStatistics handles SEGMENT_START_AUTOMATIC properly. 2021-04-13 19:18:18 +02:00
Dennis Guse 67bbd9ca24 Bugfix: GPX exported altitude loss instead as distance. 2021-04-13 19:18:18 +02:00
Dennis Guse 5e1293fc82 For delays/timers: use Duration. 2021-04-13 19:18:18 +02:00
Dennis Guse 610da5a106 Statistics*Fragment: keep reference to track instead of TrackStatistics and category. 2021-04-13 19:18:18 +02:00
Dennis Guse 2b5744ccc1 Code cleanup. 2021-04-13 19:18:18 +02:00
Dennis Guse f0341e831f Github Actions: somehow the emulator has not enough memory.
Fixes #599.
2021-04-13 19:18:18 +02:00
Dennis Guse 99e5dfbe72 Github Actions: somehow the emulator has not enough memory.
Fixes #599.
2021-04-13 19:18:18 +02:00
Dennis Guse 0fb43f6530 Test: fix failing Export/Import tests.
While recording with the sensor is used while on import only the GPS distance is available.
Introduced in a5158a55d9

Fixes #670.
2021-04-13 19:18:18 +02:00
Dennis Guse 6f9c788965 Value objects distance and speed.
#679
2021-04-13 19:18:18 +02:00
Quang Trung f2b1d2a386 Translated using Weblate (Vietnamese)
Currently translated at 77.8% (412 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-04-13 04:33:26 +02:00
mondstern 18789a0729 Translated using Weblate (Polish)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2021-04-10 17:03:01 +02:00
mondstern d0f99be9d3 Translated using Weblate (French)
Currently translated at 86.5% (458 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-04-10 17:03:01 +02:00
Dennis Guse a899be4598 Use Duration for Markers (instead of long).
Now also tests.
2021-04-10 00:08:54 +02:00
Dennis Guse 1e518a4c35 Use a non-static Binder for TrackRecordingService.
Fixes #98.
2021-04-09 18:34:23 +02:00
Dennis Guse 1c6012596e Use Duration for Markers (instead of long). 2021-04-09 18:34:03 +02:00
Román Martínez 978206dee4 IntervalsFragment/IntervalStatisticsModel refactoring: better performance with AndroidViewModel (it doesn't recreate the model every track point done anymore).
Also, it doesn't jump anymore. Fixes #669.
2021-04-08 19:46:40 +02:00
Peter Storch 8713eb3d0c fix #698 added activity icon for kayaking 2021-04-07 13:30:14 +02:00
racuna d450e045b1 U: README_TESTED_SENSORS.md
A: Amazfit Neo
2021-04-06 18:13:21 +02:00
Dennis Guse 9f87f67153 While recording show coordinates by default. 2021-04-05 22:05:31 +02:00
Dennis Guse 3a073a3b0a Code cleanup. 2021-04-04 23:31:36 +02:00
Dennis Guse bc1b4e3cd2 Rename elevation to altitude (without translations).
Fixes #678.
2021-04-04 17:45:38 +02:00
Román Martínez e09b04308c Track recording service handles the status of recording: track's id, pause and GPS status.
Created an interface for all components that want to know about recording status through the service.
All interface's methods are optional so every component can implement what they need.
Fixes #388.
2021-04-04 13:15:42 +02:00
Román Martínez f3cf1a2571 Should instant export after workout if user set a directory and post_workout_export_enabled_key is True. Fixes #694. 2021-04-03 17:18:40 +02:00
Dennis Guse 7be4ac436c ChartView: missing (sensor) data does not draw until the end.
Fixes #42.
2021-04-03 15:27:11 +02:00
Mateusz Korniak 405120a78b Pl translation update.
Pl voice notification fixes.
2021-04-02 18:06:34 +02:00
Dennis Guse e86aeace79 Fix strings.xml for Ukrainian. 2021-03-31 21:56:59 +02:00
mondstern afe5c6ee92 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-03-31 21:54:11 +02:00
mondstern 055764b462 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-03-31 21:54:10 +02:00
mondstern 360738f4c8 Translated using Weblate (Dutch)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nl/
2021-03-31 21:54:10 +02:00
mondstern 3bd3e8fd9b Translated using Weblate (Italian)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2021-03-31 21:54:10 +02:00
mondstern 5cf421cf42 Translated using Weblate (Galician)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-03-31 21:54:10 +02:00
mondstern 455522a03d Translated using Weblate (Finnish)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-03-31 21:54:09 +02:00
mondstern b4c258c686 Translated using Weblate (Estonian)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2021-03-31 21:54:09 +02:00
mondstern 05ee54d58c Translated using Weblate (Spanish)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-03-31 21:54:09 +02:00
mondstern 28fbcdd2ca Translated using Weblate (Danish)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/da/
2021-03-31 21:54:08 +02:00
mondstern 0231343aa8 Translated using Weblate (Czech)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-03-31 21:54:08 +02:00
Max Schallert 852885cca1 Translated using Weblate (German)
Currently translated at 95.0% (503 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-03-30 17:33:21 +02:00
Dennis Guse d3c1247617 Bugfix: GPX TrackPointExtension/v2 broken.
Fixes #684.
2021-03-28 18:29:10 +02:00
Dennis Guse 0b30ecabb1 Release: v3.16.0 2021-03-24 22:35:33 +01:00
mondstern 107cc14a2d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-03-24 17:23:33 +01:00
mondstern 6d3d8bb83c Translated using Weblate (Basque)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/eu/
2021-03-24 17:23:33 +01:00
Michael Munzert 9c8e0335ef Translated using Weblate (German)
Currently translated at 90.1% (477 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-03-24 17:23:33 +01:00
FTno 8c648c69e8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (529 of 529 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-03-24 17:23:33 +01:00
Valentine A 4422464f8d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-03-24 17:23:33 +01:00
mondstern d094531779 Translated using Weblate (Polish)
Currently translated at 88.2% (464 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2021-03-24 17:23:33 +01:00
mondstern a01341d235 Translated using Weblate (Basque)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/eu/
2021-03-24 17:23:33 +01:00
mondstern 83065dad39 Translated using Weblate (Estonian)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2021-03-24 17:23:33 +01:00
Dennis Guse 68ba3e0c2a Bugfix: announcements should consider hours when computing minutes.
Fixes #674.
2021-03-24 17:23:27 +01:00
Dennis Guse 3f21edc44c Cleanup. 2021-03-21 20:01:57 +01:00
Dennis Guse aefd1a6a35 Fixed test. 2021-03-21 19:58:18 +01:00
Dennis Guse 74fa73e4a4 TrackList: show aggregated stats icon in the first slot.
Fixes #657.
2021-03-20 22:21:18 +01:00
Dennis Guse 0a006d64f9 AnnouncementPeriodicTask: acquire sharedprefences on start (so it can be restarted).
Introduced in 15fbd90ca4.
Fixes #665.
2021-03-20 15:31:57 +01:00
dependabot[bot] 997ab6060e Bump gradle from 4.1.2 to 4.1.3
Bumps gradle from 4.1.2 to 4.1.3.

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-19 08:19:53 +01:00
Román Martínez 6d926ca7a8 Notification manager shows GPS accuracy, distance and speed. Fixes #584 and #483. 2021-03-18 21:49:55 +01:00
dependabot[bot] 3c1f1a9ba5 Bump espresso-core from 3.4.0-alpha04 to 3.4.0-alpha05
Bumps espresso-core from 3.4.0-alpha04 to 3.4.0-alpha05.

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-16 17:56:23 +01:00
Dennis Guse ed5da55fba Hack: Garmin Speed 2 might count backwards. 2021-03-15 17:27:41 +01:00
Dennis Guse d1e9649505 Garmin Speed 2: added workaround if cadence instead of speed is provided. 2021-03-15 17:27:41 +01:00
Dennis Guse 2431d60171 Cleanup. 2021-03-15 17:27:41 +01:00
Dennis Guse e08f5e7273 Cleanup: AnnouncementPeriodicTask uses TrackPointsIterator. 2021-03-15 17:27:41 +01:00
Dennis Guse 7d2a6fff59 TrackPointIterator: removed manual batching as it should not be necessary. 2021-03-15 17:27:41 +01:00
Dennis Guse a5158a55d9 Use distance from cycling speed sensors.
Fixes #505.
2021-03-15 17:27:41 +01:00
Dennis Guse 15fbd90ca4 Performance: Keep reference to sharedPreferences.
So far, we opened the SharedPreferences very often, which is very slow; also happened in the UI thread a lot.

Fixes #642.
2021-03-15 12:01:32 +01:00
mondstern eebc33f60c Translated using Weblate (Dutch)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nl/
2021-03-14 19:22:32 +01:00
mondstern ba2a755fb7 Translated using Weblate (Italian)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2021-03-14 19:22:32 +01:00
mondstern 344c50a20c Translated using Weblate (Finnish)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-03-14 19:22:32 +01:00
mondstern 9a9a610321 Translated using Weblate (Spanish)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-03-14 19:22:32 +01:00
mondstern 8af427ea48 Translated using Weblate (Danish)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/da/
2021-03-14 19:22:32 +01:00
Dennis Guse 7b7d7c8061 GPX: export and import power data.
Fixes #638.
2021-03-14 19:22:29 +01:00
Román Martínez 7d6919d3f9 Bug fixed: ImportService send a list of Track.Id. 2021-03-14 19:22:05 +01:00
Román Martínez 7eceb979c3 Merge pull request #646 from OpenTracksApp/exportTrackName#512
Track exported names: UUID, track's name and extension
2021-03-14 18:43:28 +01:00
Román Martínez bd48944ad5 Track export name: first UUID, name and extension (<First 32bits UUID>_<track's name>.<extension>). Fixes #512. 2021-03-14 18:42:27 +01:00
Dennis Guse 503cba1315 Import: allow import multiple select files.
Fixes #559.
2021-03-14 12:48:27 +01:00
Dennis Guse a313cb6244 Enabling StrictMode for debugging simplified. 2021-03-12 20:15:50 +01:00
Román Martínez 7f0c6309c0 Merge pull request #634 from OpenTracksApp/ui#566
Recorded fragment shows name, desc, start datetime.
2021-03-12 19:55:12 +01:00
Román Martínez 02c578327d Shared element transition (from List to Recorded). 2021-03-12 19:37:35 +01:00
Román Martínez 7fec0dcff4 StatisticsRecordedFragment does not need to check null for track/trackStatistics. Fixes #635. 2021-03-12 19:37:35 +01:00
Román Martínez 34eb7a833a Statistics recorded fragment shows activity's information (name, description, start datetime).
Re-arrange stats putting the focus on moving stats.
Fixes #566.
2021-03-12 19:37:35 +01:00
Román Martínez 5db3aa7ac1 Lowercase units for sensors and unit next to value. 2021-03-12 19:37:35 +01:00
mondstern 47fd7c51a9 Translated using Weblate (Finnish)
Currently translated at 98.6% (519 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-03-11 21:16:40 +01:00
mondstern 2f6f0b4e17 Translated using Weblate (German)
Currently translated at 90.1% (474 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-03-11 21:16:40 +01:00
mondstern 41b794f66b Translated using Weblate (Finnish)
Currently translated at 97.9% (515 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-03-11 21:16:40 +01:00
paulle e2c592367a Translated using Weblate (German)
Currently translated at 88.9% (468 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2021-03-11 21:16:40 +01:00
Hatsune Miku 7a24c56474 Translated using Weblate (Sinhala)
Currently translated at 0.3% (2 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/si/
2021-03-11 21:16:40 +01:00
Hatsune Miku 3f6bd6926a Added translation using Weblate (Sinhala) 2021-03-11 21:16:40 +01:00
Dennis Guse 508af45449 Refactored TrackImporter interface. 2021-03-11 21:16:36 +01:00
Dennis Guse 0f89cd7875 GPX: support multiple tracks.
Fixes #617.
2021-03-11 21:16:36 +01:00
Dennis Guse 29b522b8a2 Cleanup. 2021-03-11 21:16:24 +01:00
Dennis Guse 14e63a1a54 Release: v3.15.4 2021-03-09 21:44:02 +01:00
Román Martínez e559583d23 Voice announcement in seconds instead of minutes. Fixes #640. 2021-03-09 20:14:54 +01:00
Dennis Guse 3b9711d82e Release: v3.15.3 2021-03-08 18:16:18 +01:00
Dennis Guse 114fc8255f KML export: remove #start (was broken due to #316) and #end (included track statistics) placemarks.
Fixes #629.
2021-03-08 18:11:36 +01:00
Dennis Guse 26e655d58f Cleanup. 2021-03-08 18:11:36 +01:00
Dennis Guse 815b6a3cf1 StatisticsRecordingFragment: set speed=0 when TrackPoint is outdated. 2021-03-07 22:28:53 +01:00
Dennis Guse 3dc8a724d8 Bug: StatisticsRecordingFragment: speed and location where sometimes 0.
Fixes #628.
2021-03-07 22:28:53 +01:00
Dennis Guse 70f571cd09 CI: avoid OutOfMemoryError.
Fixes #599.
2021-03-07 19:46:37 +01:00
Dennis Guse 3f5c3a5c08 Bugfix: Close cursor. 2021-03-06 18:27:24 +01:00
Dennis Guse a13465391f Import: replaced SAXParserException as it only shows the message of it's cause. 2021-03-06 18:13:06 +01:00
Dennis Guse 97454b5369 Statistics*Fragment now updates the units on track category change. 2021-03-04 19:18:31 +01:00
Dennis Guse f6a47c4594 Statistics*Fragement now uses SharedPreferenceListener instead of accessing preferences repeatedly. 2021-03-04 19:18:31 +01:00
Dennis Guse fce76230e1 TrackDataListener now passes most recent TrackPoint to onNewTrackPointsDone(). 2021-03-04 19:18:31 +01:00
Dennis Guse fdf4336a9b Cleanup. 2021-03-04 19:18:31 +01:00
Dennis Guse 844c6e9609 TrackPoint: moved methods into class. 2021-03-04 19:18:31 +01:00
Manuel Zangl 784942ffd2 Fixed longitudeText in StatisticsRecordingFragment 2021-03-04 17:38:42 +01:00
Román Martínez edf4ccc941 It computes the average for heart rate, cadence, and power (duration-based average) and the maximum for heart rate and cadence. 2021-03-02 19:55:18 +01:00
Dennis Guse d90258294f Release: v3.15.2 2021-03-01 19:42:16 +01:00
mondstern 01f4c13307 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-02-28 20:56:11 +01:00
FTno 873956426d Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-02-28 20:56:11 +01:00
Xosé M 4128c54960 Translated using Weblate (Galician)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-02-28 20:56:11 +01:00
mondstern 9978bddf21 Translated using Weblate (Czech)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-02-28 20:56:11 +01:00
Nikita Epifanov a9726b9a94 Translated using Weblate (Russian)
Currently translated at 81.5% (429 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ru/
2021-02-28 20:56:11 +01:00
Crystal-RainSlide 05ba52bab5 Translated using Weblate (Chinese (Simplified))
Currently translated at 80.7% (425 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/zh_Hans/
2021-02-28 20:56:11 +01:00
FTno ca95f40e77 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (526 of 526 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-02-28 20:56:11 +01:00
mondstern 5cb85c4383 Translated using Weblate (Italian)
Currently translated at 98.4% (513 of 521 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2021-02-28 20:56:11 +01:00
mondstern d0a477b112 Translated using Weblate (Finnish)
Currently translated at 94.4% (492 of 521 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-02-28 20:56:11 +01:00
mondstern ec365a18ca Translated using Weblate (Danish)
Currently translated at 88.2% (460 of 521 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/da/
2021-02-28 20:56:11 +01:00
mondstern 0ff735e559 Translated using Weblate (Czech)
Currently translated at 100.0% (521 of 521 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-02-28 20:56:11 +01:00
Dennis Guse 8a35835791 GPX export: re-implemented basic support for multiple tracks.
Fixes #614.
2021-02-28 20:56:05 +01:00
Dennis Guse e3569dab56 Release: v3.15.1 2021-02-27 18:40:05 +01:00
Dennis Guse 4040e3f45b Bugfix: crash on recording start due to show latitude/longitude option.
Fixes #613.
2021-02-27 18:37:42 +01:00
Dennis Guse eac7efba64 Upgrade desugar lib. 2021-02-26 21:30:08 +01:00
dependabot[bot] 21a3971a56 Bump mockito-android from 3.7.7 to 3.8.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.7.7 to 3.8.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.7.7...v3.8.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-23 08:07:43 +01:00
Dennis Guse 4e4a9abf6f Localization: fix/remove translation for wheel circumference (instead of wheel size).
Fixes #605.
2021-02-22 23:11:06 +01:00
Román Martínez a9624f72e7 Track recorded stats shows sensor data from trackpoints table.
Heart rate: maximum and average.
Cadence: maximum and average.
Power: average.
Fixes #525 and #502.
2021-02-14 17:34:19 +01:00
Dennis Guse bb38d274fb Voice announcements: cleanup. 2021-02-13 14:27:54 +01:00
Dennis Guse 418f092db9 Release: v3.15.0 2021-02-12 23:29:58 +01:00
Dennis Guse fa12086951 GPX: only export TrackPoints with lat/lng. (#591)
GPX: only export TrackPoints with lat/lng.
Thus, segmentation (manual/automatic) cannot be restored on import.
This also affects the TrackStatistics.
2021-02-09 19:00:28 +01:00
dependabot[bot] 4a340380ac Bump espresso-core from 3.4.0-alpha03 to 3.4.0-alpha04
Bumps espresso-core from 3.4.0-alpha03 to 3.4.0-alpha04.

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-09 18:50:10 +01:00
Dennis Guse cd4e0abd6f SensorData: use Instant instead of long. 2021-02-07 23:16:12 +01:00
dependabot[bot] 854c8727e5 Bump material from 1.2.1 to 1.3.0
Bumps [material](https://github.com/material-components/material-components-android) from 1.2.1 to 1.3.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.2.1...1.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-05 16:52:57 +01:00
Román Martínez 2f3d9a29f3 Fixed a bug: TrackDataHub will send to listeners all TrackPoints every time a change on trackpoints table was observed. Fixes #582. 2021-01-30 23:15:54 +01:00
Dennis Guse 18cdbe94a9 i18n: app_name cannot be translated anymore.
Fixes #581.
2021-01-30 21:33:17 +01:00
Dennis Guse 18e179cfe3 Merge remote-tracking branch 'weblate/main' into main 2021-01-30 21:24:20 +01:00
Román Martínez 63e3c7a4e7 Updated README file with new screenshots. 2021-01-29 16:04:28 +01:00
Xosé M c6deca9bbd Translated using Weblate (Galician)
Currently translated at 100.0% (522 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-01-29 16:03:27 +01:00
Vinicius 9ef6ae5e25 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (522 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-01-29 16:03:27 +01:00
Román Martínez 29b1ded68c Updated README file with new screenshots. 2021-01-29 16:03:16 +01:00
Román Martínez 6239e2fcad New screenshots and script to convert SVG to PNG. 2021-01-29 15:56:34 +01:00
Dennis Guse bdfcd9c89f TrackStatistics: merge previous segment whenever a new starts/ends.
(also adapting the test).
2021-01-24 22:49:40 +01:00
Dennis Guse f8f78d28dd TrackStatistics: merge previous segment whenever a new starts/ends. 2021-01-24 22:21:37 +01:00
Dennis Guse cbe469d5e4 Import: empty tracks cannot be imported as TrackStatistics cannot be restored.
#316.
2021-01-24 22:21:37 +01:00
Dennis Guse e0553f812f Import: segments might contain no TrackPoints.
#316.
2021-01-24 22:21:37 +01:00
Dennis Guse 5aa016b5b9 Import: compute TrackStatistics onTrackEnd.
ExportImportTest were not checking TrackStatistics correctly.

#316.
2021-01-24 22:21:37 +01:00
Dennis Guse c3741611dc TrackStatisticsUpdater updates only by adding TrackPoints.
#316.
2021-01-24 22:21:37 +01:00
Dennis Guse d157e94220 Marker refactoring.
Location values are stored directly.
2021-01-24 22:21:37 +01:00
Dennis Guse 022d8146fe TrackDataHub always sends via handler. 2021-01-24 22:21:37 +01:00
Dennis Guse 912fd1aa2f TrackStatistics startTime/stopTime is null on creation. 2021-01-24 22:21:36 +01:00
Dennis Guse 1d46981717 Cleanup. 2021-01-24 22:21:36 +01:00
Dennis Guse e990b413cd Replace long time_ms with Instant and Duration, respectively.
Fixes #561.
2021-01-24 22:21:36 +01:00
Dennis Guse 3bbcbfe202 Dashboard API: send protocol version v2. 2021-01-24 22:21:36 +01:00
Dennis Guse 9d8f49f1dc Segments: document new TrackPoint segment structure.
#316.
2021-01-24 22:21:36 +01:00
Dennis Guse 9f6d6711e5 Segments: on import restore manual segment start/end if resume/pause trackpoints were exported. 2021-01-24 22:21:36 +01:00
Dennis Guse f635d56ceb Cleanup. 2021-01-24 22:21:36 +01:00
Dennis Guse ab47320216 Segments: import/export is working incl. restoring TrackStatistics.
KML exports sensor data if actually present.

#316.
2021-01-24 22:21:36 +01:00
Dennis Guse 47b2d0748a Segments: only insert one trackpoint and set the type.
Existing data is not migrated.
#316.
2021-01-24 22:21:36 +01:00
Dennis Guse bc66cd33a9 Use system time instead of GPS-provided time.
#316.
2021-01-24 22:21:36 +01:00
Dennis Guse fcc17413e3 Segments: use TrackPoint.type.
So, far specific latitude for segment start/end (i.e., PAUSE/RESUME) was used.
#316.
2021-01-24 22:21:36 +01:00
Dennis Guse 163af2dab2 Release: v3.14.3 2021-01-24 22:08:26 +01:00
Dennis Guse 393e2022a1 Bugfix: database downgrade from 30 to 29 removed required elevation_gain column. 2021-01-24 22:06:48 +01:00
Dennis Guse 793928a5a6 Release: v3.14.2 2021-01-24 21:27:25 +01:00
Dennis Guse 50bb0b14b4 Segments: database upgrade/downgrade functionality for version 30; version 29 is still used.
This will store segment data in TrackPoint.type.
This commit will enable downgrading to previous version.

#316.
2021-01-24 21:14:50 +01:00
Román Martínez 8a08e7b2b5 Creates thumbnail from marker's photos for marker's list.
These thumbnails are created and loaded async using cache from free memory available.
Fixes #545.
2021-01-24 17:43:18 +01:00
FTno e9e9fdcc3f Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (522 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 83c471ccb6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (522 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 17479c438b Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.6% (514 of 516 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 943c80f9ec Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.6% (514 of 516 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 23751306a4 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.4% (513 of 516 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
dependabot[bot] 78b712a291 Bump gradle from 4.1.1 to 4.1.2
Bumps gradle from 4.1.1 to 4.1.2.

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-20 07:52:48 +01:00
dependabot[bot] f8432adaa0 Bump mockito-android from 3.7.0 to 3.7.7
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.7.0 to 3.7.7.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.7.0...v3.7.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 17:27:56 +01:00
Dennis Guse 80b26f2a04 MarkerDetail accepts Marker.Id also as long.
Fixes #567.
2021-01-17 17:54:07 +01:00
Dennis Guse 41e6e80cfc Statistics: fix position of moving time. 2021-01-16 23:09:15 +01:00
dependabot[bot] 7e72b29c3f Bump espresso-core from 3.4.0-alpha02 to 3.4.0-alpha03
Bumps espresso-core from 3.4.0-alpha02 to 3.4.0-alpha03.

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-14 12:28:38 +01:00
Román Martínez 860df0197e ImportActivity offers to open track option (when only one track is imported). Fixes #558. 2021-01-10 20:40:14 +01:00
Román Martínez f1d791a62c Import/Export summary messages improved (only show useful data). 2021-01-10 20:40:14 +01:00
Dennis Guse e9551bea36 Introduced TrackPoint.Id. 2021-01-10 20:25:59 +01:00
Dennis Guse 577b978a5c Cleanup. 2021-01-10 20:25:59 +01:00
Dennis Guse a689b6eb2c Cleanup: removed descending iterator for TrackPoints. 2021-01-10 20:25:59 +01:00
Dennis Guse ccd93dd17d Track.Id must now always be valid (if present). 2021-01-10 20:25:59 +01:00
Dennis Guse f4fe1d8d97 Marker.Id must now always be valid (if present). 2021-01-10 20:25:59 +01:00
Dennis Guse d1290c8f22 Prevent re-import should be translatable. 2021-01-09 12:51:12 +01:00
dependabot[bot] a340cb57de Bump mockito-android from 3.6.28 to 3.7.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.6.28 to 3.7.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.6.28...v3.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 18:56:18 +01:00
Dennis Guse 75b8f3b9cf Release: v3.14.1 2021-01-03 13:25:19 +01:00
Jaroslav Lichtblau 9462221a28 Translated using Weblate (Czech)
Currently translated at 100.0% (516 of 516 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2020-12-31 12:34:58 +01:00
FTno af82eae8a4 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.4% (511 of 514 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2020-12-31 12:34:58 +01:00
Román Martínez 1c4946e85b Move RuntimeException to ImportParseExceptio so app is not crashes and import process go on. Fixes #557. 2020-12-31 12:34:38 +01:00
Román Martínez fd1b7b8af9 Added opentracks:gain and opentracks:loss information in GPX files. Fixes #555. 2020-12-26 20:30:10 +01:00
Dennis Guse eb02c4c469 Marker images can now be shared from the list directly via the context menu (like tracks).
Fixes #495.
2020-12-25 18:36:41 +01:00
Román Martínez 4714171e21 Added elevation loss via barometer (like elevation gain). Fixes #547. 2020-12-25 18:19:01 +01:00
Dennis Guse 4bff376442 pt-BR: plural vs lint. 2020-12-20 00:17:00 +01:00
Dennis Guse c14413cbe7 CodeQL action: removed now unecessary step. 2020-12-20 00:03:42 +01:00
Vinicius 778fb4b346 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (507 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2020-12-19 23:16:02 +01:00
Xosé M 130552f0eb Translated using Weblate (Galician)
Currently translated at 100.0% (507 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2020-12-19 23:16:02 +01:00
FTno 41eca663e8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (507 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2020-12-19 23:16:02 +01:00
FTno 6c79702075 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.0% (502 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2020-12-19 23:16:02 +01:00
FTno 6d109a1c5a Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.0% (497 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2020-12-19 23:16:02 +01:00
FTno b71904a3bd Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.4% (494 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2020-12-19 23:16:01 +01:00
bortox ef28784682 Translated using Weblate (Italian)
Currently translated at 100.0% (507 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2020-12-19 23:16:01 +01:00
FTno 05b7242191 Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.4% (474 of 507 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2020-12-19 23:16:01 +01:00
Xosé M 94c69f92a6 Translated using Weblate (Galician)
Currently translated at 100.0% (506 of 506 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2020-12-19 23:16:01 +01:00
mitakjakub 8f12ba3d48 Translated using Weblate (Polish)
Currently translated at 86.1% (436 of 506 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2020-12-19 23:16:01 +01:00
Román Martínez 190a572951 Translated using Weblate (Spanish)
Currently translated at 100.0% (506 of 506 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2020-12-19 23:16:01 +01:00
FTno 7ae8809101 Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.6% (474 of 506 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2020-12-19 23:16:01 +01:00
Dennis Guse 19a7ef3217 Activity icon now properly tint'ed in detail activity. 2020-12-19 18:29:34 +01:00
Dennis Guse f261e8ab9d Cleanup. 2020-12-17 23:16:09 +01:00
Dennis Guse a41160229d Implement basic EspressoUI test. 2020-12-17 23:16:09 +01:00
Dennis Guse 2fa8b998f9 Code cleanup using AndroidStudio's analyze feature. 2020-12-17 19:59:53 +01:00
Román Martínez 99847dbed9 Just tiny changes. 2020-12-17 19:15:35 +01:00
Román Martínez 483736637e Refactoring MarkerEditActivity. Logic has been moved to a ViewModel. 2020-12-17 19:15:35 +01:00
Dennis Guse e133743547 Actionbar: icon provides back functionality (except in TrackListActivity). 2020-12-17 18:56:42 +01:00
Dennis Guse eb2c2b4d78 As of ADT 14 as well as Gradle Plugin 5.0 those are not final anymore.
Fixes #544.

Missed in 1862e38a20
2020-12-15 23:36:03 +01:00
Dennis Guse 1409537e56 AggregatedStatistics: fixed ViewHolder pattern. 2020-12-15 22:53:52 +01:00
Dennis Guse 76c3699518 Intervals: list height should match parent. 2020-12-15 22:46:01 +01:00
Dennis Guse 5675dc800d Aggregated stats: ordered by number number of tracks per category. 2020-12-13 22:44:44 +01:00
Dennis Guse e1e7c24d9f Aggregated stats: icon now primary color, value centered. 2020-12-13 18:40:06 +01:00
Dennis Guse 2733071a4d Aggregated stats: simplified layout. 2020-12-13 18:14:39 +01:00
Dennis Guse 2255624235 Cleanup: rename waypoint to marker (tests). 2020-12-13 18:05:08 +01:00
Dennis Guse 55fa6d2d79 Code cleanup using AndroidStudio. 2020-12-13 18:05:08 +01:00
Dennis Guse 96371336f4 MarkerDetailFragment: load markerId once in onCreate(). 2020-12-13 17:12:03 +01:00
Dennis Guse 1862e38a20 Compare resourceIds via if rather than switch.
As of ADT 14 as well as Gradle Plugin 5.0 those are not final anymore.
Fixes #544.
2020-12-12 22:14:51 +01:00
Dennis Guse 397e056f6f Release: v3.14.0 2020-12-10 22:52:02 +01:00
Dennis Guse eb79f2277a ChartView let's parent handle TouchEvents if not zoomed. 2020-12-06 22:35:44 +01:00
Román Martínez ae8833d21b ViewPager migrate to ViewPager2. Fixes #522.
Swiping between tabs work. Fixes #105.
2020-12-06 22:35:44 +01:00
Dennis Guse e8017a4e9d Bugfix: In preferences, the Bluetooth search icon was only animated after opening the dialog a 2nd time. 2020-12-06 00:09:24 +01:00
Dennis Guse cea180f871 Code cleanup using AndroidStudio's Analyze feature. 2020-12-05 23:22:15 +01:00
Dennis Guse be1b6ece9d IntervalsFragment: generate UI values from strings.xml (i18n). 2020-12-05 23:11:29 +01:00
Vanous 0592863d3c add Huami devices into Readme Tested Sensors 2020-12-05 15:42:09 +01:00
Dennis Guse 18bc100d5a BLE sensors: Miband3's is now considered a heart rate sensor during discovery.
Some BLE devices do not announce all there supported services in the BLE advertisement packages.
Android's ScanFilter decides based upon this information only.
When connecting to such a device (and trigger service discovery), the service can still be used.

Fixes #447.
2020-12-05 12:36:57 +01:00
Peter Storch 4476a15558 throw Exception when no doc.kml file found in kmz 2020-11-29 13:56:22 +01:00
Peter Storch 215c28be85 fix #537 ignore waypoint markers with invalid
coordinates on import.

I've also added a unit test with such an export file.
2020-11-29 13:56:22 +01:00
Dennis Guse a429fbe4df Interval list: RTL and localized formatting (only default strings.xml). 2020-11-28 15:05:49 +01:00
Dennis Guse 6b470c7fdc Intervals: change label from elevation to gain. 2020-11-28 15:05:49 +01:00
Dennis Guse 6b690f38ad Charts: text size is resolved from styles.
Fixes #524.
2020-11-28 15:05:49 +01:00
Dennis Guse f138826d60 Text in charts now scales using Android's global settings.
Fixes #524.
2020-11-28 15:05:49 +01:00
Dennis Guse 14ac29095d Removed unused theme. 2020-11-28 15:05:49 +01:00
Dennis Guse a3f4de7d18 Interval list: table header in accent color. 2020-11-28 15:05:49 +01:00
Dennis Guse 6392e88dc7 Enable RTL. 2020-11-28 15:05:49 +01:00
FTno 1e913d6bb5 Update strings.xml
Typos in source strings - Issue #530
2020-11-26 07:35:34 +01:00
dependabot[bot] ced8454f59 Bump mockito-android from 3.6.0 to 3.6.28
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.6.0 to 3.6.28.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.6.0...v3.6.28)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-25 12:15:57 +01:00
Román Martínez 974a5afeb1 Only clean if parse error is out of <trk> 2020-11-25 06:39:07 +01:00
Román Martínez 9c9bdde6b9 Import files by track in memory (avoid clean track when error). Fixes #473. 2020-11-25 06:39:07 +01:00
Dennis Guse ca2bc6426c Release: v3.13.2 2020-11-22 20:21:50 +01:00
Dennis Guse 81097aac40 Bugfix: crash in landscape mode in TrackController due to wrong background. 2020-11-22 20:21:11 +01:00
Dennis Guse a74b5fc743 Cleanup. 2020-11-21 18:42:32 +01:00
Dennis Guse e996ad0a0c Release: v3.13.1 2020-11-21 10:37:25 +01:00
Dennis Guse 0f726958e0 Revert "Shrinking all APK builds without obfuscation. Fixes #433."
Introduced #523.

This reverts commit 34834120
2020-11-21 10:34:15 +01:00
Peter Storch 3a84520819 removed duplicate export 2020-11-20 21:56:39 +01:00
Dennis Guse 8328069b9d README: add human tech badge :)
Fixes #521.
2020-11-20 21:51:45 +01:00
Peter Storch b2df79caf2 fix #516 post workout export fails silently (#520)
Change the post workout export to use the ExportService (JobIntentService) and directing the user to the SettingsActivity in case the export failed to check the export directory.
2020-11-20 20:59:52 +01:00
Dennis Guse 6bcb8db40c Release: v3.13.0 2020-11-17 22:21:24 +01:00
Román Martínez 974baaf611 Marker Bugfixes: markers deletion didn't work and track import didn't include marker's photos. Fixes #514.
Marker edition bugfixed: orphan photos are deleted.
2020-11-17 22:02:26 +01:00
Dennis Guse fd77e77bb9 TrackController: readded stop button's pressed state.
Removed in 18546fd3cc
2020-11-17 21:49:21 +01:00
Dennis Guse 1e8beff015 TrackController: refactor button (delayed action) UI and use handler. 2020-11-17 21:43:12 +01:00
Dennis Guse 96bc347374 Activity icon uses now an image instead of a spinner (which was only used as an icon).
Fixed #503.
2020-11-17 21:43:12 +01:00
dependabot[bot] 8f144d4a97 Bump desugar_jdk_libs from 1.1.0 to 1.1.1
Bumps [desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-13 07:26:29 +01:00
Dennis Guse 3c3641376d Added power meter infos to READMEs. 2020-11-12 17:12:44 +01:00
Dennis Guse 35f4427aec Merge pull request #444 from OpenTracksApp/cyclingPower
Bluetooth Sensor: add cycling powermeter.
2020-11-12 12:00:13 +01:00
Dennis Guse 5a0cb10838 Bugfix: cadence and speed now can compare when data is not set. 2020-11-11 21:25:56 +01:00
Dennis Guse bf92851c97 Show sensor data's unit. 2020-11-11 21:08:39 +01:00
Dennis Guse 55c45b2050 Bugfix: SensorDataCycling.toString() should use attributes instead of their getters. 2020-11-11 21:08:39 +01:00
Román Martínez d01715444f Adaptive recording layout for sensors (heart rate, cycling cadence and cycling power). 2020-11-11 21:08:39 +01:00
Dennis Guse 634a7a4321 Bluetooth Sensor: add cycling powermeter. 2020-11-11 21:08:39 +01:00
Dennis Guse e5911a79ab Translation: Norwegian used wrong language code.
Introduced in 56aab12110
Fixes #515.
2020-11-11 20:07:59 +01:00
dependabot[bot] a78b3eb66d Bump gradle from 4.1.0 to 4.1.1
Bumps gradle from 4.1.0 to 4.1.1.

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-11 07:53:54 +01:00
Román Martínez 28890abd47 Bugfix: view changed from wrong thread. Fixes #509. 2020-11-09 20:19:08 +01:00
Dennis Guse 0a466d240b Wrap searchQuery in SQLite wildcards. 2020-11-09 18:56:25 +01:00
Dennis Guse 9ef312fc93 Search is now handled by TrackListActivity and MarkerListActivity.
* data is loaded via cursor,
* removed custom ranking (handled in database),
* search text is wrapped with wildcards, and
* search view can be closed by back button.

Fixes #492.
2020-11-09 18:56:25 +01:00
Dennis Guse 56aab12110 Noway's translation was included twice. 2020-11-09 18:55:38 +01:00
Dennis Guse c394448273 Removed Transifex configuration. 2020-11-09 18:41:38 +01:00
Dennis Guse 95671f4927 Translation is now on Weblate. 2020-11-09 18:27:38 +01:00
Dennis Guse 348341204e Shrinking all APK builds without obfuscation.
Fixes #433.
2020-11-09 12:09:37 +01:00
Román Martínez 82f5c127ee Bugfix: NPE in import. Fixes #506. 2020-11-08 14:33:31 +01:00
Dennis Guse dd490f528e Cursors must be closed. 2020-11-08 00:37:01 +01:00
Christoph Wurst 27731319f0 Fix internet access typo in readme
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
2020-11-07 10:05:41 +01:00
Christoph Wurst 86bfe6d0ce Fix formatting of md lists, remove trailing whitespaces
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
2020-11-07 09:21:55 +01:00
Christoph Wurst ea8bbb0b36 Update list of tested sensors with Polar H10 and Garmin SPD/CAD
Adds three tested sensors
* "Polar H10" heart rate sensor
* "Garmin Cadence Sensor 2" cadence sensor
* "Garmin Speed Sensor 2" speed sensor

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
2020-11-07 09:21:55 +01:00
dependabot[bot] c02fdd3bba Bump desugar_jdk_libs from 1.0.10 to 1.1.0
Bumps [desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 1.0.10 to 1.1.0.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-04 07:46:08 +01:00
Dennis Guse d16cbe04a5 Release: v3.12.0 2020-11-03 20:11:21 +01:00
Peter Storch 90a8fc898a fix #491 crash export on android 11 2020-11-03 19:57:19 +01:00
Dennis Guse 8d141b1324 TrackListActivity now loads marker count via join rather than a 2nd select.
Fixes #328.
2020-11-01 21:53:11 +01:00
Peter Storch 43e0f33776 optimize multiple track deletion
After #480 got implemented, we can now delete multiple tracks in one go and only cause DB vacuum once.
2020-11-01 21:11:58 +01:00
Román Martínez e4270b983a ExportActivity bugfix: setContentView was missing. 2020-11-01 13:21:53 +01:00
Dennis Guse 0209a90e01 Code cleanup. 2020-11-01 08:38:47 +01:00
Dennis Guse 0f92a37146 Use ViewBinding for activities. 2020-11-01 08:20:26 +01:00
Dennis Guse 9cebb1013c Implement ViewBindings for Fragments. 2020-10-31 22:21:49 +01:00
Dennis Guse 6ac0db7238 Move handling of sensor removal (from settings) in BluetoothConnectionManager.BluetoothConnectionManager. 2020-10-31 22:21:49 +01:00
Dennis Guse 3c1ab01df6 SensorData uses null internally instead of NaN. 2020-10-31 22:21:49 +01:00
Dennis Guse b6850eb46f BluetoothSensorManager: on initiate connect to sensor, report address to be shown on UI.
Fixes #64.
2020-10-31 22:21:49 +01:00
Dennis Guse 08b9cb31fa StatisticsRecordingFragment does not need to check GPS accuracy. 2020-10-31 21:38:57 +01:00
Dennis Guse 9541a92657 Small cleanups and improvements code. 2020-10-31 20:39:37 +01:00
Román Martínez acc2627bde Activity with progress and summary for import all option. Fixes #471.
ErrorListDialog shared by ExportActivity and ImportActivity.
2020-10-31 20:39:37 +01:00
Dennis Guse 48c3695b5d Database enforces foreign key constraints incl. cascading deletes. (#480)
* Database enforces foreign key constraints incl. cascading deletes.
Fixes #317.
2020-10-30 21:26:13 +01:00
Dennis Guse 2b280f050c SharedContentProvider now uses parent ContentProvider directly instead of ContentProviderUtils. 2020-10-30 21:25:41 +01:00
dependabot[bot] 68f34d9682 Bump constraint-layout from 2.0.3 to 2.0.4
Bumps constraint-layout from 2.0.3 to 2.0.4.

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-30 09:07:44 +01:00
Dennis Guse f56820e132 Documentation: added TrackPoint descriptions regarding track segments. 2020-10-29 19:10:39 +01:00
dependabot[bot] 0f5d72f098 Bump constraint-layout from 2.0.2 to 2.0.3
Bumps constraint-layout from 2.0.2 to 2.0.3.

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-28 23:08:14 +01:00
dependabot[bot] accbc5a8ac Bump mockito-android from 3.5.15 to 3.6.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.5.15 to 3.6.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.5.15...v3.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-27 08:12:20 +01:00
Dennis Guse 0bc9f6a042 Update README_TESTED_SENSORS.md
Fixes #468.
2020-10-26 18:24:42 +01:00
Peter Storch bbb91fedc5 fix #470 Unable to parse kml in kmz 2020-10-22 19:22:46 +02:00
Peter Storch 433ab01ece Track.Id.toString returns string representation of id 2020-10-22 19:17:53 +02:00
Peter Storch 88e1a95da7 fix NPE introduced in #466 2020-10-22 19:08:52 +02:00
Román Martínez d4483819eb ImportActivity intent filters. (#466)
* ImportActivity intent filters.

* add intent_filter to work with Locus and OsmAnd

* extent intent filters

Co-authored-by: Peter Storch <peter.storch@gmail.com>
2020-10-21 22:52:39 +02:00
Román 917b8f4056 Edit texts: select all on focus and capitalize by words/sentences. Fixes #461. 2020-10-20 21:31:43 +02:00
Román 70c1379778 Export All: ask to the user when conflicts (track already exported in the selected directory) and show a summary. Fixes #451. 2020-10-20 12:07:58 +02:00
dependabot[bot] 5a60f40308 Bump mockito-android from 3.5.13 to 3.5.15
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.5.13 to 3.5.15.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.5.13...v3.5.15)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-20 08:23:36 +02:00
Peter Storch df9589697a idea config files with android studio upgrade 2020-10-19 22:42:53 +02:00
Dennis Guse 27443cc716 Updated translations. 2020-10-17 12:45:48 +02:00
Dennis Guse 654be751a7 Theming: day/night mode is defaults to system. 2020-10-16 20:08:12 +02:00
Dennis Guse 3a8f3e46c7 Refactor post-workout export. 2020-10-16 12:23:39 +02:00
thrillfall b270c45691 - add default export directory
- add instant post workout track export
2020-10-16 12:23:39 +02:00
dependabot[bot] 257d8c1276 Bump gradle from 4.0.2 to 4.1.0
Bumps gradle from 4.0.2 to 4.1.0.

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 21:41:15 +02:00
Dennis Guse 8521b98f4b Bitcoin addresses are lower case (Bech32). 2020-10-13 21:24:59 +02:00
Dennis Guse eaf655f6e7 Upgrade gradle wrapper to 6.5 2020-10-13 18:15:47 +02:00
Dennis Guse 24c7f3fd7a Add option to donate via Bitcoin. 2020-10-13 18:07:28 +02:00
Román a4bab656e4 New screenshots. 2020-10-11 13:57:39 +02:00
Román 679c1d7e3e Intervals: TrackDataHub instead of ContentProvider. Fixes #453. 2020-10-11 12:12:44 +02:00
Román 0036934fb3 Intervals: added elevation gain and only shows speed or pace, depending on settings. 2020-10-11 12:12:44 +02:00
Román c508747203 Bugfix: import gpx file added heart rate like a speed because a break missing. 2020-10-08 12:39:31 +02:00
dependabot[bot] 0f4c0079d7 Bump gradle from 4.0.1 to 4.0.2
Bumps gradle from 4.0.1 to 4.0.2.

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-07 12:55:35 +02:00
dependabot[bot] 9e6bb1c511 Bump constraint-layout from 2.0.1 to 2.0.2
Bumps constraint-layout from 2.0.1 to 2.0.2.

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-07 12:55:17 +02:00
Dennis Guse a42bdf32e1 Release: v3.11.1 2020-10-06 08:16:58 +02:00
Dennis Guse c29d69b44d Cleanup. 2020-10-05 22:59:21 +02:00
Román 547b1d07bd Bugfix: interval stats announced incorrectly because an error in last interval computing. Fixes #443. 2020-10-05 20:08:07 +02:00
Dennis Guse 22a1dfd354 README: removed link to Maps.ME due to the amount of embedded trackers. 2020-10-04 12:51:14 +02:00
Dennis Guse be81e758c6 License: use non-Markdown, so Github-API identifies it. 2020-09-26 23:16:19 +02:00
Dennis Guse b3117e6410 Release: v3.11.0 2020-09-26 20:23:32 +02:00
Dennis Guse 47ab109c2b F-Droid now signs APKs with v2, so installing on Android 11 works.
More info: #421
This reverts commit ee22fd8e
2020-09-26 19:56:22 +02:00
Dennis Guse bc04efbaac Bugfix: for export all write permissions are required.
Android 11 enforces this (and it is good practice).
Fixes #439.
2020-09-26 19:52:48 +02:00
Román fbb9d21e73 TrackRecordingActivity keeps Track.Id value instead of track object. Every time the track is needed it gets it. Fixes #435. 2020-09-26 16:47:51 +02:00
dependabot[bot] f5308e4287 Bump mockito-android from 3.5.11 to 3.5.13
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.5.11 to 3.5.13.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.5.11...v3.5.13)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-25 18:20:32 +02:00
Dennis Guse 15b5920e3b README: add F-Droid version shield 2020-09-25 12:37:58 +02:00
Dennis Guse d09aaab398 Theming: import/export finished dialog needs to be AndroidX. 2020-09-24 22:45:20 +02:00
pappapisshu f7008e2362 Update strings.xml
Fixed italian translation.
2020-09-24 17:16:54 +02:00
Dennis Guse e939778beb Refactor: use marker instead of waypoint.
Fixes #389.
2020-09-23 17:16:34 +02:00
Dennis Guse 8643b50bd1 Bugfix: application context does not provide access to theme attributes. 2020-09-23 12:12:34 +02:00
Dennis Guse 74e42525d4 Cleanup. 2020-09-23 08:11:10 +02:00
Dennis Guse 833a387574 Theming: splashscreen uses dark background color. 2020-09-23 08:05:40 +02:00
Dennis Guse e574c67ad6 Theming: activity icons are now in textColorPrimary.
And replaced spinner in StatisticsRecordedFragment.
2020-09-23 07:36:15 +02:00
Dennis Guse ce38d5e891 Theming: define colorAccent. 2020-09-23 07:36:15 +02:00
Dennis Guse 3a44da4a9d Theming: stats labels use color accent. 2020-09-23 07:36:15 +02:00
Dennis Guse eba9106db5 Theming: delete dialog needs to be Androidx for proper theming. 2020-09-23 07:36:15 +02:00
Dennis Guse 4501226562 Theming: reduce font size of lists first line. 2020-09-23 07:36:15 +02:00
Dennis Guse b974193564 Theming: delete dialog uses delete icon. 2020-09-23 07:36:15 +02:00
Dennis Guse 0b3e447258 Theming: charts use theme text colors. 2020-09-23 07:36:15 +02:00
Dennis Guse 9908cc02a2 Theming: added color.xml for night styling. 2020-09-23 07:36:15 +02:00
Dennis Guse 4e67011d7c Theming: style selected list items with transparent color. 2020-09-23 07:36:15 +02:00
Dennis Guse 5f93c0bab3 Theming: tabs use default indicator color. 2020-09-23 07:36:15 +02:00
Dennis Guse caec5e7c45 Theming: chart use default design background color. 2020-09-23 07:36:15 +02:00
Dennis Guse 240cb4b363 Theming: chart colors extracted. 2020-09-23 07:36:15 +02:00
Dennis Guse 91e8eb66b1 Theming: chart text is now rendered as FILL_AND_STROKE. 2020-09-23 07:36:15 +02:00
Dennis Guse 27bd69ec62 Code cleanup. 2020-09-23 07:36:15 +02:00
Dennis Guse 553fd39e21 Theming: make SplashScreen fullscreen. 2020-09-23 07:36:15 +02:00
Dennis Guse 733ee1c6e6 Theming: move TrackEdit to material design text fields. 2020-09-23 07:36:15 +02:00
Dennis Guse ef61c9597d Theming: activity chooser matches parent height. 2020-09-23 07:36:15 +02:00
Dennis Guse e874af791e Theming: removed all custom modifications to Holo theme. 2020-09-23 07:36:15 +02:00
Dennis Guse a4b2bb1aa0 Theming: tint all icons with colorControlNormal. 2020-09-23 07:36:15 +02:00
Dennis Guse bc9b20ffc8 Theming: marker detail uses default background. 2020-09-23 07:36:15 +02:00
Dennis Guse 6f212328a1 Theming: merged edit buttons into layout. 2020-09-23 07:36:15 +02:00
Dennis Guse a68cde7ac9 Theming: listView main item is now TextLarge. 2020-09-23 07:36:15 +02:00
Dennis Guse 5cab331058 Theming: removed custom spinner style. 2020-09-23 07:36:15 +02:00
Dennis Guse 6d25a10abd Theming: use default text color. 2020-09-23 07:36:15 +02:00
Dennis Guse d2516947f2 Theming: removed custom radio button style - use Material design. 2020-09-23 07:36:15 +02:00
Dennis Guse 4150d060ee Theming: enable day/night mode incl. app-wide setting. 2020-09-23 07:36:15 +02:00
Dennis Guse 1688076457 Theming: use AppCompat themes instead of Holo. 2020-09-23 07:36:15 +02:00
Román 5c1f669b2b Bugfix: spinner adapter needed notifyDataSetChanged to reload intervals with unit when user change from km to mi and the other way around. 2020-09-21 19:12:57 +02:00
Dennis Guse cc1e080ce1 Interval statistics: show unit in dropdown. 2020-09-20 19:05:32 +02:00
Dennis Guse 0dfd31a308 Code cleanup via Android Studio analyze.
Notably change: TrackPoint's copy constructor missed to copy the elevationGain.
2020-09-20 18:46:05 +02:00
dependabot[bot] bd13535d3c Bump mockito-android from 3.5.10 to 3.5.11
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.5.10 to 3.5.11.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.5.10...v3.5.11)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-18 08:07:00 +02:00
Dennis Guse 27386bb4f0 Localization: update via Transifex CLI. 2020-09-17 23:44:37 +02:00
Dennis Guse c6c07f03bf Localization: add configuration for Transifex CLI. 2020-09-17 23:43:28 +02:00
501 changed files with 19604 additions and 12686 deletions
-3
View File
@@ -19,9 +19,6 @@ jobs:
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
+2
View File
@@ -18,4 +18,6 @@ jobs:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: Nexus 6
script: ./gradlew connectedCheck
+21
View File
@@ -1,5 +1,26 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
</JetCodeStyleSettings>
<MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-194
View File
@@ -1,194 +0,0 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+47 -26
View File
@@ -2,77 +2,98 @@
_OpenTracks_ is a sport tracking application that completely respects your privacy.
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech)
<table>
<tr>
<th>Free</th>
<th>Paid (same as free)</th>
<th>Donations</th>
<th>Translations</th>
</tr>
<tr>
<td>
<td align="center">
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
<img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="60" align="middle">
</a>
</td>
<td>
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
</a>
</td>
<td>
<td align="center">
<a href="https://liberapay.com/OpenTracks/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
</a>
</td>
<td>
<a href="https://www.transifex.com/opentracks/opentracks/">
Transifex
<td align="center">
<a href="https://weblate.bubu1.eu/engage/OpenTracks/">
<img src="https://weblate.bubu1.eu/widgets/OpenTracks/-/opentracks/svg-badge.svg" alt="Translation status" />
</a>
</td>
</tr>
<tr>
<td align="center">
<img alt="OpenTracks version published on F-Droid" src="https://img.shields.io/f-droid/v/de.dennisguse.opentracks.svg" align="middle" >
</td>
<td align="center">
<a href="bitcoin:bc1qckpls3lpjlcpyxzm66hcy65hnesas9zgacy6yc">
<img alt="bitcoin:bc1qckpls3lpjlcpyxzm66hcy65hnesas9zgacy6yc" src="promotion/donations-bitcoin.png" height="90" align="middle">
</a>
</td align="center">
<td></td>
</tr>
<tr>
<td></td>
<td align="center">
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
</a>
</td>
<td></td>
</tr>
</table>
## Features:
* __Tracking:__ track your sport and outdoor activities
* __Tracking:__ track your sport and outdoor activities
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
* __Bluetooth LE sensors:__
* __Bluetooth LE sensors:__
* heart rate
* cycling: speed and cadence
* cycling: power meter
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
* __No Internet access:__ Internet is not used
* __Voice announcements__
* __No advertising__
* __Privacy:__
* _does not contain any in-app analytics_
* _does not use Google Play Services_
* _does not use Google Play Services_
* _has no cloud integration_
__Only required permissions:__
* _ACCESS_FINE_LOCATION_: required to use the GPS.
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
_Please note:_
_OpenTracks_ does not contain a _map_ (as this would required Internet access).
_OpenTracks_ does not contain a _map_ (as this would require internet access).
## Custom Dashboards (incl. Map)
As of v3.3.1 OpenTracks enables to use custom dashboards.
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
The developer documentation is in [README_API.md](README_API.md).
Alternatively, recorded tracks can be shared as KMZ/GPX to installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)).
Alternatively, recorded tracks can be shared as KMZ/GPX to installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)).
However, this is rather slow and does not provide updates while recording.
## Screenshots
<div>
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_5.png">
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_6.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot1.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot2.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot3.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot4.png">
</div>
<div>
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot5.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot6.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot7.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot8.png">
</div>
## Project history
@@ -85,8 +106,8 @@ Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
In 2019, _OpenTracks_ was forked from Plonk42's _My Tracks_ and major rework was conducted.
Rework of _OpenTracks_ included:
* removing Google's analytics code,
* removing integration into Google Drive,
* removing Google's analytics code,
* removing integration into Google Drive,
* removing Google Maps integration,
* removing Google Earth integration,
* removing use of Google Play service,
@@ -96,7 +117,7 @@ Rework of _OpenTracks_ included:
* removing Protobuf (store sensosensorr data in SQLite columns directly), and
* removing Android Service API for other apps.
Artwork, logos and user interface remained more or less unchanged.
Artwork, logos and user interface remained more or less unchanged.
More information about _Google My Tracks_:
* [Wikipedia page](https://en.wikipedia.org/wiki/MyTracks)
+1 -1
View File
@@ -34,4 +34,4 @@ Requirement: `gem install fastlane`
## Translations (localization)
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
Translations of the OpenTracks are handled in Weblate: [https://weblate.bubu1.eu/projects/OpenTracks/](https://weblate.bubu1.eu/projects/OpenTracks/).
+33 -2
View File
@@ -1,20 +1,51 @@
# Overview of tested Bluetooth LE sensors
## HRS: Heartrate Service
* Decathlon|Geonaute|Kalenji Dual HRM Belt
* moofit heart rate monitor
* Polar H7
* Polar H10
* Polar OH1
Has updatable firmware that requires a user account at polar.com
* Wahoo Tickr (Model: WFBTHR02)
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
## CSCP: Cycling Cadence and Speed Service
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
However, often only one value is provided.
### Speed
* Garmin Speed Sensor 2
* Has updatable firmware that requires an account for garmin.com
* This sensor reports speed data as cadence.
A workaround is in place.
* This sensor sometimes counts backwards (unknown software version).
A workaround is in place.
* Wahoo Speed (Model: WFRPMSPD)
### Cadence
### Cadence
* Garmin Cadence Sensor 2
* Wahoo Cadence (Model: WFPODCAD2)
This sensor reports cadence data as speed.
A workaround is in place.
A workaround is in place.
## Cycling Power Measurement
We only support _Instantaneous Power_.
_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.
* Assioma Duo Powermeter
* Elite drivo II
* QUARQ Red DZero Powermeter
* Tacx Satori Smart
* Wahoo Kickr v4.0
+1 -1
View File
@@ -27,7 +27,7 @@ cp "$CHANGELOG_DIR/next_release.txt" "$CHANGELOG_FILE"
sed -i "s/(versionName)/$VERSIONNAME/" "$CHANGELOG_FILE"
echo -e "\nGit commit since last release\n------------"
git log $(git describe --tags --abbrev=0)..HEAD --no-merges --oneline
git log "$(git describe --tags --abbrev=0)..HEAD" --no-merges --oneline
echo -e "\nPlease edit the changelog for the new release"
gedit -w "$CHANGELOG_FILE"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
set -e
# Folder where svg screenshots are expected.
SRC_DIR="screenshots-svg/"
# Folder where png files will be saved.
DST_DIR="fastlane/metadata/android/en-US/phoneScreenshots/"
echo "Exporting svg files to png..."
# Source and destination folder must exist.
if [ ! -d "$SRC_DIR" ]
then
echo "\nERROR: ${SRC_DIR} doesn't exist."
exit
fi
if [ ! -d "$DST_DIR" ]
then
echo "\nERROR: ${DST_DIR} doesn't exist."
exit
fi
# It needs Inkscape that is the tool used to convert svg to png.
if ! command -v inkscape > /dev/null
then
echo "\nERROR: inkscape couldn't be found and it's needed to convert screenshots from svg to png."
exit
fi
# Convert all svg files from source directory.
for path in ${SRC_DIR}/*.svg
do
filename="${path##*/}"
basename="${filename%.svg}"
`inkscape ${path} --export-filename ${DST_DIR}/${basename}.png`
done
+13 -8
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:4.1.3'
}
}
@@ -45,17 +45,21 @@ android {
compileSdkVersion 30
buildFeatures {
viewBinding = true
}
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 3611
versionName "v3.10.0"
versionCode 4009
versionName "v3.17.3"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -82,17 +86,18 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha05'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
implementation 'com.google.android.material:material:1.3.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'org.mockito:mockito-android:3.5.10'
androidTestImplementation 'org.mockito:mockito-android:3.8.0'
}
+9 -9
View File
@@ -10,15 +10,11 @@ endlegend
[*] --> TrackListActivity
TrackListActivity --> TrackRecordingActivity : Start recording
TrackListActivity --> SearchListActivity
TrackListActivity --> TrackRecordedActivity : Show track
TrackListActivity --> MarkerListActivity : Show all markers
TrackListActivity --> AggregatedStatisticsActivity : Aggregated stats
TrackListActivity --> TrackRecordedActivity : Show track
TrackListActivity --> TrackDeleteActivity
SearchListActivity --> MarkerDetailActivity : Search result
SearchListActivity --> TrackRecordedActivity : Search result
TrackRecordedActivity --> TrackDeleteActivity
TrackRecordedActivity --> TrackEditActivity
TrackRecordedActivity --> MarkerListActivity
@@ -46,10 +42,14 @@ MarkerDetailActivity --> CoordinateOnMapIntent : URI
SettingsActivity --> AboutActivity
SettingsActivity --> HelpActivity
SettingsActivity --> ImportActivity
SettingsActivity --> ExportActivity
SettingsActivity --> DirectoryChooserActivity
SettingsActivity --> DirectoryChooserActivity
DirectoryChooserActivity$ImportDirectoryChooserActivity --> ImportActivity
DirectoryChooserActivity$ExportDirectoryChooserActivity --> ExportActivity
SettingsActivity --> DirectoryChooserActivity.ImportDirectoryChooserActivity
SettingsActivity --> DirectoryChooserActivity.ExportDirectoryChooserActivity
SettingsActivity --> DirectoryChooserActivity.DefaultTrackExportDirectoryChooserActivity
DirectoryChooserActivity.ImportDirectoryChooserActivity --> ImportActivity
DirectoryChooserActivity.ExportDirectoryChooserActivity --> ExportActivity
FileImportIntent --> ImportActivity
@enduml
+1 -11
View File
@@ -2,17 +2,7 @@
interface TrackExporter {
}
class KmzTrackExporter implements TrackExporter
KmzTrackExporter o-- FileTrackExporter
class FileTrackExporter implements TrackExporter {
/' One track only '/
}
FileTrackExporter o-- TrackWriter
interface TrackWriter {
}
class GpxTrackWriter implements TrackWriter
class KmlTrackWriter implements TrackWriter
class KmlTrackExporter implements TrackExporter
interface TrackImporter {
}
+4 -3
View File
@@ -1,10 +1,11 @@
@startuml
SensorManager --> ElevationSumManager
SensorManager --> AltitudeSumManager
LocationManager --> TrackRecordingService
ElevationSumManager --> TrackRecordingService
TrackRecordingService --> TrackRecordingServiceConnection
TrackRecordingService --> Database
TrackRecordingService --> CustomContentProvider
CustomContentProvider --> Database
Database --> CustomContentProvider
CustomContentProvider --> TrackDataHub
@@ -13,6 +14,6 @@ TrackDataHub --> StatisticsRecordingFragment : TrackPoints, TrackStatistics, Way
TrackDataHub --> StatisticsRecordedFragment : TrackPoints, TrackStatistics, Waypoints (push)
TrackDataHub --> ChartFragment --> ChartView
TrackRecordingServiceConnection --> StatisticsRecordingFragment : Sensor data (heartrate, cadence, elevation gain; pull 1s)
TrackRecordingServiceConnection --> StatisticsRecordingFragment : Sensor data (heartrate, cadence, altitude gain; pull 1s)
TrackRecordingServiceConnection --> TrackController : Start time (pull, onStart)
@enduml
+37 -9
View File
@@ -8,21 +8,28 @@ class Track {
-icon
}
Track o-- TrackPoint
Track o-- Waypoint
Track "1" - "1" TripStatistics
Track o-- Marker
Track "1" - "1" TrackStatistics
class TripStatistics {
class TrackStatistics {
- startTime
- stopTime
- totalDistance
- totalTime
- movingTime
- maxSpeed
- totalElevationGain
- totalGain
}
class TrackPoint extends android.location.Location {
- id
class TrackStatisticsUpdater {
}
TrackStatisticsUpdater ..> TrackStatistics : creates/updates
TrackStatisticsUpdater ..> TrackPoint : uses
class TrackPoint {
- id (database id, order)
- type
- trackId
- longitude
- latitude
@@ -36,7 +43,8 @@ class TrackPoint extends android.location.Location {
- sensor_power
}
class Waypoint {
class Marker {
- id
- trackId
- name
@@ -45,8 +53,28 @@ class Waypoint {
- icon
- length
- duration
- location
- tripStatistics
- longitude
- latitude
- photoUrl
}
note left of TrackPoint
As of OpenTracks version 3.15.0, all times are using device time.
Before that TrackPoint.time contained GPS time (determined by GPS hardware).
However, start/pause/stop events (also stored as TrackPoints) used device time.
end note
note right of Track
A track is an ordered collection of one or more segments (i.e., continuous parts of distance covered).
Segments may be started by a user (i.e., start a track recording, continue a paused track, or resume a track) as well as stopped by the user (i.e., pausing or stopping a recording).
Also segments may started automatically while recording (i.e., distance to previous location was to large).
Note that this finishes the previous segment.
Segment data is stored as TrackPoints (Type.SEGMENT_START_MANUAL, Type.SEGMENT_START_AUTOMATIC, or Type.SEGMENT_END_MANUAL).
Trackpoints with Type.SEGMENT_START_AUTOMATIC also mark the end of the previous segment.
All TrackPoints of Type.TRACKPOINT belong to the segment started by the prior TrackPoint with Type.SEGMENT_START_(MANUAL|AUTOMATIC).
Trackpoints of Type.SEGMENT_START_MANUAL or Type.SEGMENT_END_MANUAL do not contain location data or sensor data.
Tracks recorded prior to OpenTracks version 3.15.0, do neither begin with a Type.SEGMENT_START_MANUAL nor end with a Type.SEGMENT_END_MANUAL.
end note
@enduml
+28 -4
View File
@@ -1,8 +1,32 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
<xs:element name="trackuuid"
type="xs:string" />
<xsd:element name="trackuuid"
type="xsd:string">
<xsd:annotation>
<xsd:documentation xml:lang="en">
A unique track identifier stored as UUID.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xs:schema>
<xsd:element name="loss"
type="xsd:float">
<xsd:annotation>
<xsd:documentation xml:lang="en">
The lost elevation in meters from the previous to the current TrackPoint.
Only used in GPX.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="gain"
type="xsd:string">
<xsd:annotation>
<xsd:documentation xml:lang="en">
The gained elevation in meters from the previous to the current TrackPoint.
Only used in GPX.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="22"
width="22"
fill="#000000"
version="1.0"
x="0px"
y="0px"
viewBox="0 0 21.84952 21.99934"
enable-background="new 0 0 99.316 99.997"
xml:space="preserve"
id="svg10"
sodipodi:docname="ic_activity_kayaking.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
id="metadata16"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs14" /><sodipodi:namedview
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1024"
id="namedview12"
showgrid="false"
inkscape:pagecheckerboard="false"
inkscape:zoom="22.472123"
inkscape:cx="11.932326"
inkscape:cy="8.4821218"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg10"
inkscape:document-rotation="0" /><path
id="path2"
style="fill:#ffffff;stroke-width:0.20758"
d="m 18.296044,13.516712 c -1.017125,-0.01824 -1.903328,0.770666 -2.945647,0.500454 -0.952298,-0.271819 -1.950516,-0.765534 -2.949277,-0.338881 -0.938402,0.492101 -2.078868,0.493759 -3.017025,0 C 8.3845167,13.251481 7.3852162,13.744815 6.4321693,14.017166 5.229901,14.31201 4.2005498,13.256398 2.9984219,13.558671 2.2076549,13.806805 1.0222411,14.11299 1.0222411,14.11299 v 1.764949 c 1.0226762,0.01947 1.9128198,-0.776355 2.9601398,-0.498166 0.4416703,0.216761 1.6682446,0.3639 1.6529583,0.601941 l -1.3879215,1.383539 c -0.7385797,-0.502111 -1.5118619,0.0527 -1.9901253,0.625328 -0.5087615,0.568011 -1.1635382,1.025896 -1.5505475,1.687065 -0.44607206,0.952772 0.6576964,2.056499 1.6104671,1.610525 0.7016688,-0.428027 1.2006595,-1.122907 1.8093513,-1.672458 0.6218606,-0.429609 0.7776032,-1.247655 0.6297837,-1.861615 l 2.0038313,-1.999046 c 0.9553792,-0.44042 2.0942104,-0.606317 3.0559286,-0.07975 0.9566571,0.429062 1.9815791,0.09313 2.8934291,-0.263824 1.038124,-0.339466 1.95525,0.382078 2.955963,0.461915 1.087184,0.105691 2.019821,-0.784126 3.121615,-0.493622 0.55673,-0.01509 1.943197,1.034046 1.977683,0.148315 v -1.471252 c -0.878739,0.05089 -1.592095,-0.588897 -2.468753,-0.540124 z"
sodipodi:nodetypes="sccccccccccccccccccccccss" /><path
id="path4"
style="fill:#ffffff;stroke-width:0.20758"
d="M 19.135498,4.6094374 C 19.777729,3.9239548 20.518106,3.3189058 21.090567,2.5760423 21.630775,1.6723294 20.631775,0.46345938 19.642408,0.81572972 18.952145,1.1177762 18.525406,1.7968856 17.966518,2.2833866 17.44088,2.7777653 16.748383,3.4079168 17.123672,4.2079927 17.316456,4.5791871 16.670933,4.8230987 16.479267,5.1359209 L 15.065885,6.5509368 C 13.459538,5.9569743 11.849312,5.3583867 10.246781,4.7652258 9.2045735,4.5310231 8.2546176,5.3827898 8.1060604,6.3824119 7.7002321,7.7689937 7.2645314,9.1479847 6.8776693,10.539386 h -5.851069 v 2.366209 c 1.0341353,-0.06583 1.9719234,-0.790016 3.0482038,-0.491433 0.8514673,0.182595 1.7032854,0.742921 2.5975093,0.377297 1.0893234,-0.553582 2.4097276,-0.562618 3.4983496,-0.0022 0.895986,0.368665 1.751474,-0.190407 2.60469,-0.37517 1.078334,-0.302717 2.015803,0.427862 3.051521,0.490909 0.985146,-0.07753 1.867505,-0.470806 2.884112,-0.579104 0.847652,-0.0903 2.163908,-1.785603 2.163908,-1.785603 0,0 -1.819193,-0.01538 -2.935123,0.0029 -1.990467,-2.45e-4 -3.980934,-0.0021 -5.9714,-0.0044 l 2.483907,-2.4814163 c 0.762424,0.5726687 1.957001,-0.3877847 1.20272,-1.1977389 l 2.032212,-2.039685 c 0.462782,0.2519942 1.079225,0.1706574 1.448288,-0.2104864 z m -8.060346,5.9299486 c -0.486522,0.101516 -0.612149,-0.06543 -0.374498,-0.502616 l 0.909432,-3.029364 2.189559,0.8099787 z"
sodipodi:nodetypes="cccccccccccccccccsccccccccccccc" /><path
id="path851"
style="fill:#ffffff;stroke-width:1.16861"
d="m 12.520186,2.6733336 c 0.05505,1.1807821 -1.406378,2.0132887 -2.394197,1.3656163 -1.0445314,-0.553724 -1.0179219,-2.235304 0.04332,-2.7559413 1.018447,-0.59197226 2.378758,0.20782 2.350878,1.390325 z" /></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

@@ -5,6 +5,6 @@ Funktionen & Features:
* zeichnet Sport und andere Outdooraktivitäten auf
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
* zeichnet deine Herzfrequenz auf (Bluetooth LE)
* nutze beim Radfahren deine Trittfrequenz- und Geschwindigkeitssensoren (Bluetooth LE)
* nutze beim Radfahren deine Trittfrequenz- und Geschwindigkeitssensoren sowie Leistungsmesser (Bluetooth LE)
* exportiert deine Daten als KML oder GPX
* funktioniert ganz ohne Internet
@@ -0,0 +1,10 @@
v3.11.0: OpenTracks
Changes:
- Theming: day/night mode (default: night)
- Theming: use Material Design instead of custom element theming
- Use targetSDK 30 again (F-Droid signs now with V2)
Bugfix:
- Android 11: export all crashed
- TrackDetail not updated in UI after data was changed by user
@@ -0,0 +1,4 @@
v3.11.1: OpenTracks
Bugfix:
- Voice announcements reported wrong statistics
@@ -0,0 +1,22 @@
v3.12.0: OpenTracks
Changes:
- Bluetooth sensor addresses
- Intervals: show elevation gain
- Export: default export directory
- Export: automatic export after finishing a track
- Manual import/export: new user interface
- Manual export: option to overwrite on manual export
Faster:
- Track lists loading
- Deleting multiple tracks
Bugfix:
- Fixed intent filters for import
- KMZ could not be parsed
- Export: fix crash on Android 11
Developer:
- Use ViewBindings
- Database enforces foreign key and uses cascading deletes
@@ -0,0 +1,10 @@
v3.13.0: OpenTracks
Changes:
- Sensor: support for powermeter (BLE)
- Search by track and marker (in lists)
- Combined search removed
Bugfix:
- Import crashed in UI
- Images of markers were not imported
@@ -0,0 +1,5 @@
v3.13.1: OpenTracks
Bugfix:
- Crash on record start: minify/shrink removed a drawable
- Automatic export failed silently (mainly remote destinations)
@@ -0,0 +1,5 @@
v3.13.2: OpenTracks
Bugfix:
- Crash in landscape mode
@@ -0,0 +1,10 @@
v3.14.0: OpenTracks
Changes:
- Properly support for right-to-left
- Import: faster cleanup after import failure
- Text in charts scales according to Android settings
- Heart rate: support for Huami devices (requires Gadgetbridge)
Developer:
- Import is now handled in-memory
@@ -0,0 +1,14 @@
v3.14.1: OpenTracks
Changes:
- Added elevation loss (via barometer)
- Updated translations (cz, gl, it, nb, pl, pt, sp)
- Markers can be shared from list
- Actionbar icon (top left) to go back
-
Bugfix:
- Potential crash on import
Developer:
- Implemented basic EspressoUI test
- Reference resourceIds via if rather than switch/case
@@ -0,0 +1,10 @@
v3.14.2: OpenTracks
Changes:
- Downgrade database from upcoming release v3.15.0
- Import/Export UI improved
- Import offers to show imported track
- Marker list performance improved
Developer:
- Track.Id/Marker.Id: use null
@@ -0,0 +1,4 @@
v3.14.3: OpenTracks
Bugfix:
- Downgrading database from upcoming release v3.15.0
@@ -0,0 +1,22 @@
v3.15.0: OpenTracks
WARNING:
This release applies database changes that CANNOT BE REVERTED.
As KMZ/KML export/import behaviour changes as well, it is recommended to (A) take a backup BEFORE upgrading and (B) afterwards.
Recommended format: KMZ.
BREAKING CHANGES:
- Internal storage of track segments is changed
(user generated events are now considered trackpoints without a location but with timestamp)
- Device time instead of GPS time is stored
(existing data is not modified)
- Dashboard API v2
(please upgrade OSMDashboard 2.10.0)
Changes:
- KML/KMZ can used to backup and restore exactly (incl. statistics)
Developer:
- Reworked track segmentation and it's storage (formerly PAUSE/RESUME marker)
- TrackStatistics are computed solely using TrackPoints
- Replaced long with Instant/Duration
@@ -0,0 +1,7 @@
v3.15.1: OpenTracks
Changes:
- After recording show sensor statistics (e.g., avg HR)
Bugfix:
- Crash on recording start with show latitude/longitude enabled
@@ -0,0 +1,7 @@
v3.15.2: OpenTracks
Changes:
- Updated translations
Bugfix:
- Crash on GPX export
@@ -0,0 +1,8 @@
v3.15.3: OpenTracks
Changes:
- Computation avg/max: heart rate, cadence, and power
Bugfix:
- KML/KMZ do not export #start/#end placemarks anymore
- While recording: Speed/location was sometimes falsly shown as 0.
@@ -0,0 +1,4 @@
v3.15.4: OpenTracks
Bugfix:
- Voice annoucements interval should be in minutes (introduced in v3.15.0)
@@ -0,0 +1,13 @@
v3.16.0: OpenTracks
Changes:
- Cycling: get distance from BLE speed sensor
- Reworked UI presented while recording
- UI performance optimizations
- Exported filenames: <First 32bits UUID>_<track's name>.<extension>
- Import multiple files via Intent (e.g., from Gadgetbridge)
- GPX: export power data
- Updated translations
Bugfix:
- Voice announcement use minutes correctly
@@ -0,0 +1,18 @@
v3.17.0: OpenTracks
Changes:
- Add icon for kayaking
- Charts draw missing sensor data properly
- Default: show coordinates while recording
Bugfixes:
- GPX schema corrected
- GPX exported altitude loss as sensor distance
- Statistics not computed correctly if using a speed/distance sensor
Developer:
- Distance/speed value objects
- Replaced communication via SharedPreferences
- Use non-static service binder
- Use altitude in the code base (instead of elevation)
@@ -0,0 +1,7 @@
v3.17.1: OpenTracks
Bugfix:
- Interval length in meters instead of kilometers (introduced in v3.17.0)
- Elevation gain/loss not recorded (introduced in v3.17.0)
- Announcements not working on Android 11
@@ -0,0 +1,8 @@
v3.17.2: OpenTracks
WARNING: The v3.17.0+ releases contain a memory leak.
This issues is NOT FIXED by this release.
It is recommended to not upgrade if you still use a release before v3.17.0
Bugfix:
- Crash while using voice announcements
@@ -0,0 +1,11 @@
v3.17.3: OpenTracks
WARNING: The v3.17.0+ releases contain a memory leak.
This issues is NOT FIXED by this release.
It is recommended to not upgrade if you still use a release before v3.17.0
Bugfix:
- Crash if using pace for voice announcements (introduced in v3.17.0)
- Statistics showed pace initially (introduced in v3.17.0)
- Rare crash when deleting tracks
@@ -6,5 +6,6 @@ Features:
* Supports Bluetooth LE sensors:
* heart rate
* cycling: speed and cadence
* cycling: power meter
* Export data: export tracks either as KML or GPX
* No Internet access: Internet is not used
Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
-2
View File
@@ -82,7 +82,6 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -130,7 +129,6 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
Vendored
+103 -104
View File
@@ -1,104 +1,103 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 174 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 862 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.6 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 338 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 665 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 248 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 661 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1001 KiB

@@ -0,0 +1,168 @@
package de.dennisguse.opentracks;
import android.view.MotionEvent;
import android.view.View;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import com.google.android.material.tabs.TabLayout;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoUITest {
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant("android.permission.ACCESS_FINE_LOCATION");
@LargeTest
@Test
public void record_pause_resume_stop() {
{
// TrackListActivity: start recording
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
trackControllerRecordButton.perform(click());
}
{
// TrackRecordingActivity
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
// wait; stay recording
trackControllerRecordButton.perform(waitFor(5000));
//pause
trackControllerRecordButton.perform(veryLongTouch(1600));
// wait; stay paused
trackControllerRecordButton.perform(waitFor(1000));
// resume
trackControllerRecordButton.perform(click());
// wait; stay recording
trackControllerRecordButton.perform(waitFor(1000));
// stop;
trackControllerStopButton.perform(veryLongTouch(1600));
}
}
@LargeTest
@Test
public void record_move_through_tabs() {
{
// TrackListActivity: start recording
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
trackControllerRecordButton.perform(click());
}
{
// TrackRecordingActivity
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
tabLayout.perform(selectTabAtIndex(1));
tabLayout.perform(waitFor(1000));
tabLayout.perform(selectTabAtIndex(2));
tabLayout.perform(waitFor(1000));
tabLayout.perform(selectTabAtIndex(3));
tabLayout.perform(waitFor(1000));
tabLayout.perform(selectTabAtIndex(0));
tabLayout.perform(waitFor(1000));
// stop
trackControllerStopButton.perform(veryLongTouch(1600));
}
}
private static ViewAction veryLongTouch(final int duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Perform long touch.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
// Get view absolute position
int[] location = new int[2];
view.getLocationOnScreen(location);
// Offset coordinates by view position
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
// Send down event, pause, and send up
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
uiController.loopMainThreadForAtLeast(duration_ms);
MotionEvents.sendUp(uiController, down, coordinates);
}
};
}
private static ViewAction waitFor(final long duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Wait for milliseconds.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
uiController.loopMainThreadForAtLeast(duration_ms);
}
};
}
private static ViewAction selectTabAtIndex(final int index) {
return new ViewAction() {
@Override
public String getDescription() {
return "Selecting tab.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, View view) {
TabLayout tabLayout = (TabLayout) view;
tabLayout.getTabAt(index).select();
}
};
}
}
@@ -0,0 +1,94 @@
package de.dennisguse.opentracks.chart;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.fragments.TrackStubUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(AndroidJUnit4.class)
public class ChartPointTest {
@Test
public void create_by_time() {
// given
TrackStatistics statistics = new TrackStatistics();
statistics.setTotalTime(Duration.ofSeconds(1000));
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, false, false);
// then
assertEquals(1000000, (long) point.getTimeOrDistance());
}
@Test
public void create_by_distance() {
// given
TrackStatistics statistics = new TrackStatistics();
statistics.setTotalDistance(Distance.of(1000));
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, true, true);
// then
assertEquals(1, (long) point.getTimeOrDistance());
}
@Test
public void create_get_altitude_speed_and_pace() {
// given
TrackStatistics statistics = new TrackStatistics();
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
// then
assertEquals(0.05, point.getAltitude(), 0.01);
assertEquals(36, point.getSpeed(), 0.01);
assertEquals(1.66, point.getPace(), 0.01);
}
@Test
public void create_sensorNotAvailable() {
// given
TrackStatistics statistics = new TrackStatistics();
// when
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
// then
assertNull(point.getHeartRate());
assertNull(point.getCadence());
assertNull(point.getPower());
}
@Test
public void create_sensorAvailable() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setHeartRate_bpm(100f);
trackPoint.setCyclingCadence_rpm(101f);
trackPoint.setPower(102f);
TrackStatistics statistics = new TrackStatistics();
// when
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), 50, false, true);
// then
assertEquals(100.0, point.getHeartRate(), 0.01);
assertEquals(101.0, point.getCadence(), 0.01);
assertEquals(102.0, point.getPower(), 0.01);
}
}
@@ -44,14 +44,16 @@ public class ChartValueSeriesTest {
ApplicationProvider.getApplicationContext(),
Integer.MIN_VALUE,
Integer.MAX_VALUE,
new int[]{100, 1000},
R.string.description_elevation_metric,
R.string.description_elevation_imperial,
R.color.chart_elevation_fill,
R.color.chart_elevation_border) {
new int[]{1, 100, 1000},
R.string.description_altitude_metric,
R.string.description_altitude_imperial,
R.color.chart_altitude_fill,
R.color.chart_altitude_border,
15,
18) {
@Override
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getElevation();
Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getAltitude();
}
@Override
@@ -75,6 +77,17 @@ public class ChartValueSeriesTest {
assertFalse(series.isEnabled());
}
@Test
public void testVerySmallUpdates() {
series.update(new ChartPoint(1f));
series.update(new ChartPoint(2f));
series.update(new ChartPoint(3f));
series.updateDimension();
assertEquals(1, series.getInterval());
assertEquals(1, series.getMinMarkerValue());
assertEquals(6, series.getMaxMarkerValue());
}
@Test
public void testSmallUpdates() {
series.update(new ChartPoint(0));
@@ -25,7 +25,12 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
@@ -40,7 +45,7 @@ import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class DescriptionGeneratorTest {
private static final long START_TIME = 1288721514000L;
private static final Instant START_TIME = Instant.ofEpochMilli(1288721514000L);
private DescriptionGenerator descriptionGenerator;
private final Context context = ApplicationProvider.getApplicationContext();
@@ -57,23 +62,24 @@ public class DescriptionGeneratorTest {
public void testGenerateTrackDescription() {
Track track = new Track();
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setMaxElevation(550);
stats.setMinElevation(-500);
stats.setTotalElevationGain(6000);
stats.setStartTime_ms(START_TIME);
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofMillis(600000));
stats.setMovingTime(Duration.ofMillis(300000));
stats.setMaxSpeed(Speed.of(100));
stats.setMaxAltitude(550);
stats.setMinAltitude(-500);
stats.setTotalAltitudeGain(6000f);
stats.setTotalAltitudeLoss(6000f);
stats.setStartTime(START_TIME);
track.setTrackStatistics(stats);
track.setCategory("hiking");
String expected = //"Created by"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
+ "Name: -<br>"
+ "Activity type: hiking<br>"
+ "Description: -<br>"
+ "Total distance: 20.00 km (12.4 mi)<br>"
+ "Total time: 10:00<br>"
+ "Name: -<br>"
+ "Activity type: hiking<br>"
+ "Description: -<br>"
+ "Total distance: 20.00 km (12.4 mi)<br>"
+ "Total time: 10:00<br>"
+ "Moving time: 05:00<br>"
+ "Average speed: 120.00 km/h (74.6 mi/h)<br>"
+ "Average moving speed: 240.00 km/h (149.1 mi/h)<br>"
@@ -84,23 +90,25 @@ public class DescriptionGeneratorTest {
+ "Max elevation: 550 m (1804 ft)<br>"
+ "Min elevation: -500 m (-1640 ft)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Elevation loss: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
@Test
public void testGenerateTrackDescriptionWithoutMaxMinElevation() {
public void testGenerateTrackDescriptionWithoutMaxMinAltitude() {
Track track = new Track();
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setMaxElevation(Double.POSITIVE_INFINITY);
stats.setMinElevation(Double.NEGATIVE_INFINITY);
stats.setTotalElevationGain(6000);
stats.setStartTime_ms(START_TIME);
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofMillis(600000));
stats.setMovingTime(Duration.ofMillis(300000));
stats.setMaxSpeed(Speed.of(100));
stats.setMaxAltitude(Double.POSITIVE_INFINITY);
stats.setMinAltitude(Double.NEGATIVE_INFINITY);
stats.setTotalAltitudeGain(6000f);
stats.setTotalAltitudeLoss(6000f);
stats.setStartTime(START_TIME);
track.setTrackStatistics(stats);
track.setCategory("hiking");
String expected = //"Created by"
@@ -118,6 +126,7 @@ public class DescriptionGeneratorTest {
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Elevation loss: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
@@ -125,52 +134,52 @@ public class DescriptionGeneratorTest {
/**
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeDistance(Distance, StringBuilder, int, String)}.
*/
@Test
public void testWriteDistance() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeDistance(1100, builder, R.string.description_total_distance, "<br>");
descriptionGenerator.writeDistance(Distance.of(1100), builder, R.string.description_total_distance, "<br>");
assertEquals("Total distance: 1.10 km (0.7 mi)<br>", builder.toString());
}
/**
* Tests {@link DescriptionGenerator#writeTime(long, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeTime(Duration, StringBuilder, int, String)}.
*/
@Test
public void testWriteTime() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeTime(1000, builder, R.string.description_total_time, "<br>");
descriptionGenerator.writeTime(Duration.ofMillis(1000), builder, R.string.description_total_time, "<br>");
assertEquals("Total time: 00:01<br>", builder.toString());
}
/**
* Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeSpeed(Speed, StringBuilder, int, String)}.
*/
@Test
public void testWriteSpeed() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeSpeed(1.1, builder, R.string.description_average_speed, "\n");
descriptionGenerator.writeSpeed(Speed.of(1.1), builder, R.string.description_average_speed, "\n");
assertEquals("Average speed: 3.96 km/h (2.5 mi/h)\n", builder.toString());
}
/**
* Tests {@link DescriptionGenerator#writeElevation(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeAltitude(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteElevation() {
public void testWriteAltitude() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeElevation(4.2, builder, R.string.description_min_elevation, "<br>");
descriptionGenerator.writeAltitude(4.2, builder, R.string.description_min_altitude, "<br>");
assertEquals("Min elevation: 4 m (14 ft)<br>", builder.toString());
}
/**
* Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writePace(Speed, StringBuilder, int, String)}.
*/
@Test
public void testWritePace() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n");
descriptionGenerator.writePace(Speed.of(1.1), builder, R.string.description_average_pace_in_minute, "\n");
assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString());
}
}
@@ -1,322 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
import android.content.ContentUris;
import android.location.Location;
import android.net.Uri;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.provider.ProviderTestRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.CustomSQLiteOpenHelper;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Tests for {@link SearchEngine}.
* These are not meant to be quality tests, but instead feature-by-feature tests.
* In other words, they don't test the mixing of different score boostings, just each boosting separately.
*
* @author Rodrigo Damazio
*/
public class SearchEngineTest {
private static final String TAG = SearchEngineTest.class.getSimpleName();
private static final Location HERE = new Location("gps");
private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000;
@Rule
public ProviderTestRule sqliteContentProviderRule = new ProviderTestRule.Builder(CustomContentProvider.class, ContentProviderUtils.AUTHORITY_PACKAGE).setPrefix(TAG).build();
private ContentProviderUtils providerUtils;
private SearchEngine engine;
@Before
public void setUp() {
providerUtils = new ContentProviderUtils(sqliteContentProviderRule.getResolver());
engine = new SearchEngine(providerUtils);
}
@After
public void tearDown() {
InstrumentationRegistry.getInstrumentation().getTargetContext().deleteDatabase(TAG + CustomSQLiteOpenHelper.DATABASE_NAME);
}
private Track.Id insertTrack(String title, String description, String category, long hoursAgo) {
Track track = new Track();
track.setName(title);
track.setDescription(description);
track.setCategory(category);
TrackStatistics stats = track.getTrackStatistics();
if (hoursAgo > 0) {
// Started twice hoursAgo, so the average time is hoursAgo.
stats.setStartTime_ms(NOW - hoursAgo * 1000L * 60L * 60L * 2);
stats.setStopTime_ms(NOW);
}
Uri uri = providerUtils.insertTrack(track);
return new Track.Id(ContentUris.parseId(uri));
}
private Track.Id insertTrack(String title, String description, String category) {
return insertTrack(title, description, category, -1);
}
private Track.Id insertTrack(String title, long hoursAgo) {
return insertTrack(title, "", "", hoursAgo);
}
private Marker.Id insertWaypoint(String title, String description, String category, double distance, long hoursAgo, Track.Id trackId) {
Location location = new Location(HERE);
location.setLatitude(location.getLatitude() + distance);
location.setLongitude(location.getLongitude() + distance);
if (hoursAgo >= 0) {
location.setTime(NOW - hoursAgo * 1000L * 60L * 60L);
}
Marker waypoint = new Marker(location);
waypoint.setName(title);
waypoint.setDescription(description);
waypoint.setCategory(category);
waypoint.setTrackId(trackId);
Uri uri = providerUtils.insertMarker(waypoint);
return new Marker.Id(ContentUris.parseId(uri));
}
private Marker.Id insertWaypoint(String title, String description, String category) {
return insertWaypoint(title, description, category, 0.0, -1, null);
}
private Marker.Id insertWaypoint(String title, double distance) {
return insertWaypoint(title, "", "", distance, -1, null);
}
private Marker.Id insertWaypoint(String title, long hoursAgo) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, null);
}
private Marker.Id insertWaypoint(String title, long hoursAgo, Track.Id trackId) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId);
}
@Test
public void testSearchText() {
// Insert 7 tracks (purposefully out of result order):
// - one which won't match
// - one which will match the description
// - one which will match the category
// - one which will match the title
// - one which will match in title and category
// - one which will match in title and description
// - one which will match in all fields
insertTrack("bb", "cc", "dd");
Track.Id descriptionMatchId = insertTrack("bb", "aa", "cc");
Track.Id categoryMatchId = insertTrack("bb", "cc", "aa");
Track.Id titleMatchId = insertTrack("aa", "bb", "cc");
Track.Id titleCategoryMatchId = insertTrack("aa", "bb", "ca");
Track.Id titleDescriptionMatchId = insertTrack("aa", "ba", "cc");
Track.Id allMatchId = insertTrack("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
assertTrackResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
}
@Test
public void testSearchWaypointText() {
// Insert 7 waypoints (purposefully out of result order):
// - one which won't match
// - one which will match the description
// - one which will match the category
// - one which will match the title
// - one which will match in title and category
// - one which will match in title and description
// - one which will match in all fields
insertWaypoint("bb", "cc", "dd");
Marker.Id descriptionMatchId = insertWaypoint("bb", "aa", "cc");
Marker.Id categoryMatchId = insertWaypoint("bb", "cc", "aa");
Marker.Id titleMatchId = insertWaypoint("aa", "bb", "cc");
Marker.Id titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
Marker.Id titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
Marker.Id allMatchId = insertWaypoint("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
assertWaypointResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
}
@Test
public void testSearchMixedText() {
// Insert 5 entries (purposefully out of result order):
// - one waypoint which will match by description
// - one waypoint which won't match
// - one waypoint which will match by title
// - one track which won't match
// - one track which will match by title
Marker.Id descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
insertWaypoint("bb", "cc", "dd");
Marker.Id titleWaypointId = insertWaypoint("aa", "bb", "cc");
insertTrack("bb", "cc", "dd");
Track.Id trackId = insertTrack("aa", "bb", "cc");
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
assertEquals(results.toString(), 3, results.size());
assertTrackResult(trackId, results.get(0));
assertWaypointResult(titleWaypointId, results.get(1));
assertWaypointResult(descriptionWaypointId, results.get(2));
}
@Test
public void testSearchWaypointDistance() {
// All results match text, but they're at difference distances from the user.
Marker.Id farFarAwayId = insertWaypoint("aa", 0.3);
Marker.Id nearId = insertWaypoint("ab", 0.1);
Marker.Id farId = insertWaypoint("ac", 0.2);
SearchQuery query = new SearchQuery("a", HERE, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Distance order.
assertWaypointResults(results, nearId, farId, farFarAwayId);
}
public void testSearchTrackRecent() {
// All results match text, but they're were recorded at different times.
Track.Id oldestId = insertTrack("aa", 3);
Track.Id recentId = insertTrack("ab", 1);
Track.Id oldId = insertTrack("ac", 2);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
assertTrackResults(results, recentId, oldId, oldestId);
}
@Test
public void testSearchWaypointRecent() {
// All results match text, but they're were recorded at different times.
Marker.Id oldestId = insertWaypoint("aa", 2);
Marker.Id recentId = insertWaypoint("ab", 0);
Marker.Id oldId = insertWaypoint("ac", 1);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
assertWaypointResults(results, recentId, oldId, oldestId);
}
@Test
public void testSearchCurrentTrack() {
// All results match text, but one of them is the current track.
Track.Id currentId = insertTrack("ab", 1);
Track.Id otherId = insertTrack("aa", 1);
SearchQuery query = new SearchQuery("a", null, currentId, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Current track should be demoted.
assertTrackResults(results, otherId, currentId);
}
@Test
public void testSearchCurrentTrackWaypoint() {
// All results match text, but one of them is in the current track.
Marker.Id otherId = insertWaypoint("aa", 1, new Track.Id(456));
Marker.Id currentId = insertWaypoint("ab", 1, new Track.Id(123));
SearchQuery query = new SearchQuery("a", null, new Track.Id(123), NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Waypoint in current track should be promoted.
assertWaypointResults(results, currentId, otherId);
}
private void assertTrackResult(Track.Id trackId, ScoredResult result) {
assertNotNull("Not a track", result.track);
assertNull("Ambiguous result", result.marker);
assertEquals(trackId, result.track.getId());
}
private void assertTrackResults(List<ScoredResult> results, Track.Id... trackIds) {
long[] longTrackIds = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
longTrackIds[i] = trackIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longTrackIds) + "; results=" + results;
assertEquals(results.size(), trackIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
assertNotNull(errMsg, result.track);
assertNull(errMsg, result.marker);
assertEquals(errMsg, trackIds[i], result.track.getId());
}
}
private void assertWaypointResult(Marker.Id waypointId, ScoredResult result) {
assertNotNull("Not a waypoint", result.marker);
assertNull("Ambiguous result", result.track);
assertEquals(waypointId, result.marker.getId());
}
private void assertWaypointResults(List<ScoredResult> results, Marker.Id... waypointIds) {
long[] longWaypointIds = new long[waypointIds.length];
for (int i = 0; i < waypointIds.length; i++) {
longWaypointIds[i] = waypointIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longWaypointIds) + "; results=" + results;
assertEquals(results.size(), waypointIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
assertNotNull(errMsg, result.marker);
assertNull(errMsg, result.track);
assertEquals(errMsg, waypointIds[i], result.marker.getId());
}
}
}
@@ -1,14 +1,19 @@
package de.dennisguse.opentracks.content.data;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import android.util.Pair;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.FileUtils;
public class TestDataUtil {
@@ -16,6 +21,8 @@ public class TestDataUtil {
public static final double INITIAL_LATITUDE = 37.0;
public static final double INITIAL_LONGITUDE = -57.0;
public static final double ALTITUDE_INTERVAL = 2.5;
public static final float ALTITUDE_GAIN = 3;
public static final float ALTITUDE_LOSS = 3;
/**
* Create a track without any trackPoints.
@@ -34,43 +41,95 @@ public class TestDataUtil {
* @param trackId the trackId of the track
* @param numPoints the trackPoints number in the track
*/
public static Pair<Track, TrackPoint[]> createTrack(Track.Id trackId, int numPoints) {
@Deprecated //TODO Should start with SEGMENT_START_MANUAL and end with SEGMENT_END_MANUAL.
public static Pair<Track, List<TrackPoint>> createTrack(Track.Id trackId, int numPoints) {
Track track = createTrack(trackId);
TrackPoint[] trackPoints = new TrackPoint[numPoints];
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
for (int i = 0; i < numPoints; i++) {
trackPoints[i] = createTrackPoint(i);
trackPoints.add(createTrackPoint(i));
}
return new Pair<>(track, trackPoints);
}
public static TrackData createTestingTrack(Track.Id trackId) {
Track track = createTrack(trackId);
int i = 0;
List<TrackPoint> trackPoints = List.of(
TrackPoint.createSegmentStartManualWithTime(Instant.ofEpochSecond(i++)),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++, TrackPoint.Type.SEGMENT_START_AUTOMATIC),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++, TrackPoint.Type.SEGMENT_END_MANUAL),
TrackPoint.createSegmentStartManualWithTime(Instant.ofEpochSecond(i++)),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i, TrackPoint.Type.SEGMENT_END_MANUAL)
);
//TODO Use TrackStatisticsUpdater
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(0));
stats.setTotalTime(Duration.ofMillis(0));
List<Marker> markers = List.of(
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, stats, trackPoints.get(1), null),
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, stats, trackPoints.get(4), null),
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, stats, trackPoints.get(5), null)
);
return new TrackData(track, trackPoints, markers);
}
public static class TrackData {
public final Track track;
public final List<TrackPoint> trackPoints;
public final List<Marker> markers;
public TrackData(Track track, List<TrackPoint> trackPoints, List<Marker> markers) {
this.track = track;
this.trackPoints = trackPoints;
this.markers = markers;
}
}
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
Pair<Track, List<TrackPoint>> pair = createTrack(trackId, numPoints);
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
return pair.first;
}
/**
* Creates a location.
*
* @param i the index for the TrackPoint.
*/
public static TrackPoint createTrackPoint(int i) {
TrackPoint trackPoint = new TrackPoint();
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
trackPoint.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
trackPoint.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
trackPoint.setAccuracy((float) i / 100.0f);
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
trackPoint.setTime(i + 1);
trackPoint.setSpeed(5f + (i / 10));
trackPoint.setTime(Instant.ofEpochSecond(i + 1));
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
trackPoint.setHeartRate_bpm(100f + i);
trackPoint.setCyclingCadence_rpm(300f + i);
trackPoint.setPower(400f + i);
trackPoint.setElevationGain(500f + i);
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
trackPoint.setAltitudeLoss(ALTITUDE_LOSS);
return trackPoint;
}
public static TrackPoint createTrackPoint(int i, TrackPoint.Type type) {
TrackPoint trackPoint = createTrackPoint(i);
trackPoint.setType(type);
return trackPoint;
}
@@ -80,25 +139,31 @@ public class TestDataUtil {
* @param track track to be inserted
* @param trackPoints trackPoints to be inserted
*/
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, TrackPoint[] trackPoints) {
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, List<TrackPoint> trackPoints) {
contentProviderUtils.insertTrack(track);
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
}
/**
* Creates a Waypoint with a photo.
*
* @param context The context.
* @param trackId The track id.
* @param location The location.
* @return the Waypoint created.
*/
public static Marker createWaypointWithPhoto(Context context, Track.Id trackId, Location location) throws IOException {
public static Marker createMarkerWithPhoto(Context context, Track.Id trackId, TrackPoint trackPoint) throws IOException {
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
dstFile.createNewFile();
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
String photoUrl = photoUri.toString();
return new Marker("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
//TODO Use TrackStatisticsUpdater
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(0));
stats.setTotalTime(Duration.ofMillis(0));
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, stats, trackPoint, photoUrl);
}
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null);
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
while (trackPointIterator.hasNext()) {
trackPoints.add(trackPointIterator.next());
}
return trackPoints;
}
}
@@ -0,0 +1,92 @@
package de.dennisguse.opentracks.content.data;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
public class TestSensorDataUtil {
private final List<TrackPoint> trackPointList = new ArrayList<>();
private final List<SensorData> sensorDataList = new ArrayList<>();
public void add(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type));
TrackPoint tp = new TrackPoint(type);
int i = trackPointList.size() + 1;
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
tp.setHeartRate_bpm(hr);
tp.setCyclingCadence_rpm(cadence);
tp.setPower(power);
tp.setAccuracy(1f);
tp.setAltitude(1f);
tp.setTime(time);
tp.setSpeed(Speed.of(5f + (i / 10f)));
tp.setAltitudeGain(3f);
tp.setAltitudeLoss(3f);
trackPointList.add(tp);
}
public List<TrackPoint> getTrackPointList() {
return this.trackPointList;
}
public SensorDataStats computeStats() {
if (sensorDataList.size() <= 1) {
return null;
}
SensorDataStats stats = new SensorDataStats();
long timeElapsed;
long movingTime = 0;
SensorData dataPrev = sensorDataList.get(0);
stats.maxHr = dataPrev.hr;
stats.maxCadence = dataPrev.cadence;
SensorData dataCurrent;
for (int i = 1; i < sensorDataList.size(); i++) {
dataCurrent = sensorDataList.get(i);
if (dataPrev.type != TrackPoint.Type.SEGMENT_START_MANUAL) {
timeElapsed = dataCurrent.type != TrackPoint.Type.SEGMENT_START_MANUAL ? dataCurrent.time.getEpochSecond() - dataPrev.time.getEpochSecond() : 0;
stats.avgHr += (dataPrev.hr * timeElapsed);
stats.maxHr = Math.max(dataPrev.hr, stats.maxHr);
stats.avgCadence += (dataPrev.cadence * timeElapsed);
stats.maxCadence = Math.max(dataPrev.cadence, stats.maxCadence);
stats.avgPower += (dataPrev.power * timeElapsed);
movingTime += timeElapsed;
}
dataPrev = dataCurrent;
}
stats.avgHr /= movingTime;
stats.avgCadence /= movingTime;
stats.avgPower /= movingTime;
return stats;
}
private static class SensorData {
final Instant time;
final float hr;
final float cadence;
final float power;
final TrackPoint.Type type;
public SensorData(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
this.time = time;
this.hr = hr == null ? 0 : hr;
this.cadence = cadence == null ? 0 : cadence;
this.power = power == null ? 0 : power;
this.type = type;
}
}
public static class SensorDataStats {
public float avgHr = 0f;
public float maxHr = 0f;
public float avgCadence = 0f;
public float maxCadence = 0f;
public float avgPower = 0f;
}
}
@@ -0,0 +1,28 @@
package de.dennisguse.opentracks.content.data;
import org.junit.Test;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class TrackPointTest {
@Test
public void isRecent_true() {
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
tp.setTime(Instant.now());
assertTrue(tp.isRecent());
}
@Test
public void isRecent_false() {
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
tp.setTime(Instant.now().minus(2, ChronoUnit.MINUTES));
assertFalse(tp.isRecent());
}
}
@@ -5,8 +5,8 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import androidx.annotation.VisibleForTesting;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -15,7 +15,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.dennisguse.opentracks.content.data.MarkerColumns;
@@ -38,39 +40,21 @@ public class CustomSQLiteOpenHelperTest {
private final Context context = ApplicationProvider.getApplicationContext();
/**
* Get the SQL create statements for all tables (ordered by name).
* Get the SQL create statements for all SQLite elements of type (ordered by name).
*
* @return Map(TableName, SQL)
* @param type index, table
* @return Map(name, SQL)
*/
@VisibleForTesting
public static Map<String, String> getTableSQL(SQLiteDatabase db) {
HashMap<String, String> tableSQL = new HashMap<>();
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + MarkerColumns.TABLE_NAME + "')", null, null, null, "name")) {
public static Map<String, String> getSQL(SQLiteDatabase db, String type) {
HashMap<String, String> sqlMap = new HashMap<>();
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "type=?", new String[]{type}, null, null, "name")) {
if (cursor != null) {
while (cursor.moveToNext()) {
tableSQL.put(cursor.getString(0), cursor.getString(1));
sqlMap.put(cursor.getString(0), cursor.getString(1));
}
}
}
return tableSQL;
}
/**
* Get the SQL create statements for all indices (ordered by name).
*
* @return Map(tableName, SQL)
*/
@VisibleForTesting
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
HashMap<String, String> indexSQL = new HashMap<>();
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
indexSQL.put(cursor.getString(0), cursor.getString(1));
}
}
}
return indexSQL;
return sqlMap;
}
/**
@@ -109,7 +93,7 @@ public class CustomSQLiteOpenHelperTest {
assertTrue(hasSqlCreate(db, MarkerColumns.CREATE_TABLE));
assertTrue(hasSqlCreate(db, MarkerColumns.CREATE_TABLE_INDEX));
} catch (Exception e) {
fail();
fail("Database could not be created: " + e);
}
}
@@ -121,8 +105,8 @@ public class CustomSQLiteOpenHelperTest {
Map<String, String> tableByUpgrade;
Map<String, String> indicesByUpgrade;
try (SQLiteDatabase dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tableByUpgrade = getTableSQL(dbUpgraded);
indicesByUpgrade = getIndexSQL(dbUpgraded);
tableByUpgrade = getSQL(dbUpgraded, "table");
indicesByUpgrade = getSQL(dbUpgraded, "index");
}
context.deleteDatabase(DATABASE_NAME);
@@ -130,12 +114,14 @@ public class CustomSQLiteOpenHelperTest {
Map<String, String> tablesByCreate;
Map<String, String> indicesByCreate;
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tablesByCreate = getTableSQL(dbCreated);
indicesByCreate = getIndexSQL(dbCreated);
tablesByCreate = getSQL(dbCreated, "table");
indicesByCreate = getSQL(dbCreated, "index");
}
// then - verify table structure
assertEquals(3, tableByUpgrade.size());
int tableCount = 3 + 2; //Three with data tables + two SQLite
assertEquals(tableCount, tableByUpgrade.size());
assertEquals(tableByUpgrade.size(), tablesByCreate.size());
assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
@@ -158,8 +144,8 @@ public class CustomSQLiteOpenHelperTest {
Map<String, String> tablesByDowngrade;
Map<String, String> indicesByDowngrade;
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23).getReadableDatabase()) {
tablesByDowngrade = getTableSQL(db);
indicesByDowngrade = getIndexSQL(db);
tablesByDowngrade = getSQL(db, "table");
indicesByDowngrade = getSQL(db, "index");
}
// then - verify table structure
@@ -182,6 +168,101 @@ public class CustomSQLiteOpenHelperTest {
}
}
@Test
public void upgrade_data_to_30() {
// given: a track in version 29
createVersion23();
try (SQLiteDatabase db29 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 29).getWritableDatabase()) {
db29.beginTransaction();
db29.execSQL("INSERT INTO tracks (_id) VALUES (1)");
// Record -> stop
db29.execSQL("INSERT INTO tracks (_id) VALUES (2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.1, 2.1)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.2, 2.2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.3, 2.3)");
// Record -> pause -> stop
db29.execSQL("INSERT INTO tracks (_id) VALUES (3)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.1, 3.1)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.2, 3.2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 0.0, 100)"); //manual PAUSE
// Record -> segment marker (distance to previous) -> pause -> resume -> stop
db29.execSQL("INSERT INTO tracks (_id) VALUES (4)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.1, 4.1)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.2, 4.2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //SegmentEndMarker; will be deleted
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.3, 4.3)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.4, 4.4)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //manual PAUSE
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 200 * 1E6)"); //manual RESUME
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.5, 4.5)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.6, 4.6)");
db29.setTransactionSuccessful();
db29.endTransaction();
}
// when / then
try (SQLiteDatabase db30 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 30).getWritableDatabase()) {
{
// Track 1
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 1");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(0, cursor.getCount());
}
}
{
// Track 2
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 2");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(3, cursor.getCount());
}
}
{
// Track 3
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 3");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(3, cursor.getCount());
}
}
{
// Track 4
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 4");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(8, cursor.getCount());
List<Integer> types = new ArrayList<>();
List<Double> latitude = new ArrayList<>();
cursor.moveToFirst();
do {
types.add(cursor.getInt(cursor.getColumnIndexOrThrow("type")));
if (!cursor.isNull(cursor.getColumnIndexOrThrow("latitude"))) {
latitude.add(cursor.getDouble(cursor.getColumnIndexOrThrow("latitude")));
} else {
latitude.add(-1.0);
}
} while (cursor.moveToNext());
assertEquals(List.of(0, 0, -1, 0, 1, -2, 0, 0), types);
assertEquals(List.of(4.1, 4.2, 4.3, 4.4, -1.0, -1.0, 4.5, 4.6), latitude);
}
}
}
}
private void createVersion23() {
// Manually create database schema with version 23 (base version)
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
@@ -9,6 +9,7 @@ import de.dennisguse.opentracks.util.UintUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@RunWith(AndroidJUnit4.class)
public class SensorDataCyclingTest {
@@ -23,7 +24,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
assertEquals(60, current.getCadence_rpm(), 0.01);
assertEquals(60, current.getValue(), 0.01);
}
@Test
@@ -36,7 +37,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
assertEquals(33.53, current.getCadence_rpm(), 0.01);
assertEquals(33.53, current.getValue(), 0.01);
}
@Test
@@ -49,7 +50,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
assertEquals(0, current.getCadence_rpm(), 0.01);
assertEquals(0, current.getValue(), 0.01);
}
@@ -63,7 +64,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
assertFalse(current.hasCadence_rpm());
assertFalse(current.hasValue());
}
@Test
@@ -76,7 +77,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
assertEquals(60, current.getCadence_rpm(), 0.01);
assertEquals(60, current.getValue(), 0.01);
}
@Test
@@ -89,32 +90,62 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
assertEquals(60, current.getCadence_rpm(), 0.01);
assertEquals(60, current.getValue(), 0.01);
}
@Test
public void compute_speed() {
// given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016);
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
// when
current.compute(previous, 2150);
// then
assertEquals(1.20, current.getSpeed_mps(), 0.01);
assertEquals(2.15, current.getValue().distance.toM(), 0.01);
assertEquals(1.20, current.getValue().speed.toMPS(), 0.01);
}
@Test
public void compute_speed_rollOverCount() {
// given
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, 2000);
// then
assertEquals(2, current.getSpeed_mps(), 0.01);
assertEquals(2, current.getValue().distance.toM(), 0.01);
assertEquals(2, current.getValue().speed.toMPS(), 0.01);
}
@Test
public void equals_speed_with_no_data() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
@Test
public void equals_cadence_with_no_data() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress");
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
}
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.fragments;
import android.location.Location;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
@@ -28,19 +27,15 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.TrackStubUtils;
import de.dennisguse.opentracks.chart.ChartPoint;
import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.UnitConversions;
import static org.junit.Assert.assertEquals;
import de.dennisguse.opentracks.content.data.Distance;
/**
* Tests {@link ChartFragment}.
*
* @author Youtao Liu
*/
//TODO Add tests that check ChartFragment
@RunWith(AndroidJUnit4.class)
public class ChartFragmentTest {
@@ -62,260 +57,13 @@ public class ChartFragmentTest {
@Before
public void setUp() {
boolean chartByDistance = false;
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
chartFragment = ChartFragment.newInstance(chartByDistance);
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
chartFragment.setTrackStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
chartFragment.setRecordingDistanceInterval(Distance.of(50));
}
/**
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testCreatePendingPoint_sensorIncorrect() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
// when
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
// then
assertEquals(Float.NaN, point.getHeartRate(), 0.01);
assertEquals(Float.NaN, point.getCadence(), 0.01);
assertEquals(Float.NaN, point.getPower(), 0.01);
}
/**
* Tests the logic to get the correct values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testCreatePendingPoint_sensorCorrect() {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setHeartRate_bpm(100f);
trackPoint.setCyclingCadence_rpm(101f);
trackPoint.setPower(102f);
// when
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
// then
assertEquals(100.0, point.getHeartRate(), 0.01);
assertEquals(101.0, point.getCadence(), 0.01);
assertEquals(102.0, point.getPower(), 0.01);
}
/**
* Tests the logic to get the value of metric Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testCreatePendingPoint_distanceMetric() {
chartFragment.setChartByDistance(true);
// Resets last location and writes first location.
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second is a same location, just different time.
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use metric.
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
trackPoint3.setLatitude(23);
point = chartFragment.createPendingPoint(trackPoint3);
// Computes the distance between Latitude 22 and 23.
float[] results = new float[4];
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(),
trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM;
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The fourth location is a new location, and use metric.
TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint();
trackPoint4.setLatitude(24);
point = chartFragment.createPendingPoint(trackPoint4);
// Computes the distance between Latitude 23 and 24.
Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(),
trackPoint4.getLatitude(), trackPoint4.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM;
assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
}
/**
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testCreatePendingPoint_distanceImperial() {
// By distance.
chartFragment.setChartByDistance(true);
// Setups to use imperial.
chartFragment.setMetricUnits(false);
// The first is a same location, just different time.
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second location is a new location, and use imperial.
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setLatitude(23);
point = chartFragment.createPendingPoint(trackPoint2);
/*
* Computes the distance between Latitude 22 and 23.
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
*/
float[] results = new float[4];
Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use imperial.
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
trackPoint3.setLatitude(24);
point = chartFragment.createPendingPoint(trackPoint3);
/*
* Computes the distance between Latitude 23 and 24.
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
*/
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
}
/**
* Tests the logic to get the values of time in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testCreatePendingPoint_time() {
chartFragment.setChartByDistance(false);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
long timeSpan = 222;
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setTime(trackPoint1.getTime() + timeSpan);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals((double) timeSpan, point.getTimeOrDistance(), 0.01);
}
/**
* Tests the logic to get the value of elevation in {@link ChartFragment#createPendingPoint(TrackPoint)} by one and two points.
*/
@Test
public void testCreatePendingPoint_elevation() {
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
/*
* At first, clear old points of elevation, so give true to the second parameter.
* Then only one value INITIAL_ALTITUDE in buffer.
*/
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getElevation(), 0.01);
/*
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
*/
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getElevation(), 0.01);
}
/**
* Tests the logic to get the value of speed in {@link ChartFragment#createPendingPoint(TrackPoint)}.
* In this test, firstly remove all points in memory, and then fill in two points one by one.
* The speed values of these points are 129, 130.
*/
@Test
public void testCreatePendingPoint_speed() {
/*
* At first, clear old points of speed, so give true to the second parameter.
* It will not be filled in to the speed buffer.
*/
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(128.5f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
assertEquals(0.0, point.getSpeed(), 0.01);
/*
* Tests the logic when both metricUnits and reportSpeed are true.
* This location will be filled into speed buffer.
*/
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
/*
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
*/
trackPoint2.setTime(trackPoint1.getTime() + 222);
trackPoint2.setSpeed(130);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
}
/**
* Tests the logic to compute speed when use Imperial.
*/
@Test
public void testCreatePendingPoint_speedImperial() {
chartFragment.setMetricUnits(false);
// First data point is not added to the speed buffer
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
assertEquals(0.0, point.getSpeed(), 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
/*
* Add a time span here to make sure the second point and the speed is valid.
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
*/
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
}
/**
* Tests the logic to get pace value when reportSpeed is false.
*/
@Test
public void testCreatePendingPoint_pace_nonZeroSpeed() {
chartFragment.setReportSpeed(false);
// First data point is not added to the speed buffer
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
assertEquals(0.0, point.getSpeed(), 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
/*
* Add a time span here to make sure the second point and the speed is valid.
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
*/
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
}
/**
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
*/
@Test
public void testCreatePendingPoint_pace_zeroSpeed() {
chartFragment.setReportSpeed(false);
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setSpeed(0);
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
assertEquals(0.0, point.getPace(), 0.01);
public void nothing() {
//TODO
}
}
@@ -14,7 +14,7 @@
* the License.
*/
package de.dennisguse.opentracks;
package de.dennisguse.opentracks.fragments;
import android.location.Location;
@@ -57,7 +57,7 @@ public class TrackStubUtils {
location.setAltitude(altitude);
location.setAccuracy(INITIAL_ACCURACY);
location.setSpeed(INITIAL_SPEED);
location.setTime(INITIAL_TIME);
location.setTime(INITIAL_TIME); //TODO This is nowadays ignored as the constructor will replace the time.
location.setBearing(INITIAL_BEARING);
return new TrackPoint(location);
@@ -0,0 +1,53 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.ByteArrayOutputStream;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
@RunWith(JUnit4.class)
public class KmlTrackExporterTest {
private final Context context = ApplicationProvider.getApplicationContext();
/**
* Sensor data by type should only be created if present in at least on TrackPoint.
*/
@Test
public void writeCloseSegment_only_write_sensordata_if_present() {
String expected = "<when>1970-01-01T00:00:00Z</when>\n" +
"<gx:coord/>\n" +
"<ExtendedData>\n" +
"<SchemaData schemaUrl=\"#schema\">\n" +
"</SchemaData>\n" +
"</ExtendedData>\n" +
"</gx:Track>\n";
// given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
kmlTrackWriter.prepare(outputStream);
kmlTrackWriter.writeTrackPoint(trackPoint);
// when
kmlTrackWriter.writeCloseSegment();
kmlTrackWriter.close();
// then
assertEquals(expected, outputStream.toString());
}
}
@@ -1,131 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.io.file.importer;
import android.content.ContentUris;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
/**
* Abstract class for testing file track importers.
*
* @author Jimmy Shih.
*/
public abstract class AbstractTestFileTrackImporter {
static final String TRACK_NAME_0 = "blablub";
static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
static final double TRACK_LATITUDE = 48.768364;
static final double TRACK_LONGITUDE = 9.177886;
static final double TRACK_ELEVATION = 324.0;
static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123Z";
static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123Z";
static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123Z";
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
static final long TRACK_POINT_ID_0 = 1;
static final long TRACK_POINT_ID_1 = 2;
static final long TRACK_POINT_ID_3 = 4;
private static final long WAYPOINT_ID_0 = 1;
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(MarkerColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
protected final Context context = ApplicationProvider.getApplicationContext();
static {
// We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is.
TimeZone utc = SimpleTimeZone.getTimeZone(ZoneOffset.UTC);
DATE_FORMAT_0.setTimeZone(utc);
DATE_FORMAT_1.setTimeZone(utc);
}
@Mock
public ContentProviderUtils contentProviderUtils;
TrackPoint createTrackPoint(int index, long time) {
Location trackPoint = new Location(LocationManager.GPS_PROVIDER);
trackPoint.setLatitude(TRACK_LATITUDE + index);
trackPoint.setLongitude(TRACK_LONGITUDE + index);
trackPoint.setAltitude(TRACK_ELEVATION + index);
trackPoint.setTime(time);
return new TrackPoint(trackPoint);
}
/**
* Expects the first track point to be added.
*
* @param trackPoint the trackPoint
* @param trackId the track id
* @param trackPointId the track point id
*/
protected void expectFirstTrackPoint(TrackPoint trackPoint, Track.Id trackId, long trackPointId) {
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1);
}
/**
* Expects the track to be updated.
*
* @param trackCaptor the track
* @param lastTrack true if it is the last track in the gpx
* @param trackId the track id
*/
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, Track.Id trackId) {
contentProviderUtils.updateTrack(trackCaptor.capture());
when(contentProviderUtils.insertMarker(any())).thenReturn(WAYPOINT_ID_O_URI);
if (lastTrack) {
// Return null to not add waypoints
when(contentProviderUtils.getTrack(trackId)).thenReturn(null);
}
}
protected void verifyTrack(Track track, String name, String description, long time) {
assertEquals(name, track.getName());
assertEquals(description, track.getDescription());
if (time != -1L) {
assertEquals(time, track.getTrackStatistics().getStartTime_ms());
}
}
}
@@ -1,14 +1,20 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Looper;
import android.util.Log;
import android.util.Pair;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -16,36 +22,56 @@ import org.junit.runners.JUnit4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
* <p>
* TODO: test ignores {@link TrackStatistics} for now.
*/
@RunWith(JUnit4.class)
public class ExportImportTest {
private static final String TAG = ExportImportTest.class.getSimpleName();
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
@@ -53,60 +79,72 @@ public class ExportImportTest {
private static final String TRACK_CATEGORY = "the category";
private static final String TRACK_DESCRIPTION = "the description";
private final List<Marker> markers = new ArrayList<>();
private final List<TrackPoint> trackPoints = new ArrayList<>();
private Track track;
private List<Marker> markers = new ArrayList<>();
private List<TrackPoint> trackPoints = new ArrayList<>();
private Track.Id trackId;
private Track.Id importTrackId;
private final Track.Id trackId = new Track.Id(System.currentTimeMillis());
@Before
public void setUp() {
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
track.first.setIcon(TRACK_ICON);
track.first.setCategory(TRACK_CATEGORY);
track.first.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.insertTrack(track.first);
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
public void setUp(boolean hasSensorDistance) throws TimeoutException {
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
trackPoints.clear();
trackPoints.addAll(Arrays.asList(track.second));
for (int i = 0; i < 3; i++) {
Marker marker = new Marker(track.second[i].getLocation());
marker.setName("the marker " + i);
marker.setDescription("the marker description " + i);
marker.setCategory("the marker category" + i);
marker.setIcon("the waypoing icon" + i);
marker.setPhotoUrl("the photo url" + i);
marker.setTrackId(trackId);
contentProviderUtils.insertMarker(marker);
trackId = service.startNewTrack();
//TODO Workaround as those managers overwrite input data; We need to refactor TrackRecordingService to make it actually testable
service.setAltitudeSumManager(null);
service.setRemoteSensorManager(null);
markers.add(marker);
}
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
assertEquals(markers.size(), contentProviderUtils.getMarkerCount(trackId));
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance), 0);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
service.pauseCurrentTrack();
service.resumeCurrentTrack();
//TODO Workaround as those managers overwrite input data; We need to refactor TrackRecordingService to make it actually testable
service.setAltitudeSumManager(null);
service.setRemoteSensorManager(null);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
service.endCurrentTrack();
track = contentProviderUtils.getTrack(trackId);
track.setIcon(TRACK_ICON);
track.setCategory(TRACK_CATEGORY);
track.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.updateTrack(track);
track = contentProviderUtils.getTrack(trackId);
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
markers = contentProviderUtils.getMarkers(trackId);
assertEquals(10, trackPoints.size());
assertEquals(2, markers.size());
}
@After
public void tearDown() {
contentProviderUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, importTrackId);
if (importTrackId != null) {
contentProviderUtils.deleteTrack(context, importTrackId);
}
}
@LargeTest
@Test
public void kml_only_track() {
// TODO
Log.e(TAG, "Test not implemented.");
}
public void kml_with_trackdetail() throws TimeoutException {
setUp(false);
@LargeTest
@Test
public void kml_with_trackdetail() {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.createTrackExporter(context);
// when
// 1. export
@@ -117,7 +155,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -129,20 +167,25 @@ public class ExportImportTest {
assertEquals(track.getIcon(), importedTrack.getIcon());
assertEquals(track.getUuid(), importedTrack.getUuid());
// 2. markers
assertMarkers();
// 2. trackpoints
assertTrackpoints(trackPoints, false, false, false, false, false, false);
// 3. trackpoints
assertTrackpoints(false, false, false, false);
// 3. trackstatistics
assertTrackStatistics(false, false, true);
// 4. markers
assertMarkers();
}
@LargeTest
@Test
public void kml_with_trackdetail_and_sensordata() {
public void kml_with_trackdetail_and_sensordata() throws TimeoutException {
setUp(true);
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
// when
// 1. export
@@ -153,7 +196,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -164,21 +207,28 @@ public class ExportImportTest {
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.getIcon(), importedTrack.getIcon());
// 2. markers
assertMarkers();
// 2. trackpoints
assertTrackpoints(trackPoints, true, true, true, true, true, true);
// 3. trackpoints
assertTrackpoints(true, true, true, true);
// 2. trackstatistics
assertTrackStatistics(false, true, true);
// 4. markers
assertMarkers();
}
@LargeTest
@Test
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
@Test(expected = ImportAlreadyExistsException.class)
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException {
setUp(false);
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true);
editor.commit();
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
// when
// 1. export
@@ -188,7 +238,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
@@ -197,39 +247,13 @@ public class ExportImportTest {
@LargeTest
@Test
public void kmz_only_track() {
// TODO
Log.e(TAG, "Test not implemented.");
}
public void gpx() throws TimeoutException {
setUp(true);
@LargeTest
@Test
public void kmz_with_trackdetail() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kmz_with_trackdetail_and_sensordata() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kmz_with_trackdetail_and_sensordata_and_pictures() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void gpx() {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
// when
// 1. export
@@ -240,7 +264,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -253,21 +277,34 @@ public class ExportImportTest {
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
//assertEquals(track.getIcon(), trackImported.getIcon());
// 2. markers
assertMarkers();
// 2. trackpoints
// The GPX exporter does not support exporting TrackPoints without lat/lng.
// Therefore, the track segmentation is changes.
List<TrackPoint> trackPointsWithCoordinates = trackPoints.stream().filter(it -> TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(it.getType()) || TrackPoint.Type.TRACKPOINT.equals(it.getType())).collect(Collectors.toList());
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
// 3. trackpoints
assertTrackpoints(false, true, true, false);
assertTrackpoints(trackPointsWithCoordinates, true, true, true, true, true, false);
// 3. trackstatistics
assertTrackStatistics(true, true, false);
// 4. markers
assertMarkers();
}
@LargeTest
@Test
public void gpx_duplicate_trackUUID() {
@Test(expected = ImportAlreadyExistsException.class)
public void gpx_duplicate_trackUUID() throws TimeoutException {
setUp(false);
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true);
editor.commit();
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
// when
// 1. export
@@ -277,7 +314,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
@@ -304,35 +341,112 @@ public class ExportImportTest {
}
}
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain) {
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyAltitudeGain, boolean verifyAltitudeLoss, boolean verifyDistance) {
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(trackPoints.size(), importedTrackPoints.size());
for (int i = 0; i < trackPoints.size(); i++) {
TrackPoint trackPoint = trackPoints.get(i);
TrackPoint importedTrackPoint = importedTrackPoints.get(i);
assertEquals(trackPoint.getTime(), importedTrackPoint.getTime(), 0.01);
assertEquals(trackPoint.getTime(), importedTrackPoint.getTime());
TrackPoint.Type type = trackPoint.getType();
Log.e(TAG, "" + importedTrackPoint.getType().equals(type));
assertEquals("" + i, type, importedTrackPoint.getType());
// TODO Not exported for GPX/KML
// assertEquals(trackPoint.getAccuracy(), importedTrackPoint.getAccuracy(), 0.01);
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
assertEquals(trackPoint.hasLocation(), importedTrackPoint.hasLocation());
if (trackPoint.hasLocation()) {
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
}
assertEquals(trackPoint.hasSpeed(), importedTrackPoint.hasSpeed());
if (trackPoint.hasSpeed()) {
assertEquals(trackPoint.getSpeed().toMPS(), importedTrackPoint.getSpeed().toMPS(), 0.001);
}
assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
if (trackPoint.hasAltitude()) {
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
}
if (type.equals(TrackPoint.Type.SEGMENT_START_MANUAL) || type.equals(TrackPoint.Type.SEGMENT_END_MANUAL)) {
//TODO REMOVE
continue;
}
if (verifyHeartrate) {
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
assertEquals("" + trackPoint, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
}
if (verifyCadence) {
assertEquals(trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
assertEquals("" + trackPoint, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
}
if (verifyPower) {
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
assertEquals("" + trackPoint, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
}
if (verifyElevationGain) {
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
if (verifyAltitudeGain) {
assertEquals(trackPoint.getAltitudeGain(), importedTrackPoint.getAltitudeGain(), 0.01);
}
if (verifyAltitudeLoss) {
assertEquals(trackPoint.getAltitudeLoss(), importedTrackPoint.getAltitudeLoss(), 0.01);
}
if (verifyDistance) {
assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance());
}
}
}
private void assertTrackStatistics(boolean isGpx, boolean verifyAltitudeGainAndLoss, boolean verifyDistance) {
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack.getTrackStatistics());
TrackStatistics trackStatistics = track.getTrackStatistics();
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
// Time
assertTrue(trackStatistics.getStartTime().isBefore(trackStatistics.getStopTime())); //Just to be sure.
if (!isGpx) {
assertEquals(trackStatistics.getStartTime(), importedTrackStatistics.getStartTime());
assertEquals(trackStatistics.getStopTime(), importedTrackStatistics.getStopTime());
assertEquals(trackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime());
assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
// Distance
if (verifyDistance) {
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance());
}
// Speed
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed());
assertEquals(trackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed());
assertEquals(trackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed());
}
// Altitude
assertEquals(trackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(trackStatistics.getMaxAltitude(), importedTrackStatistics.getMaxAltitude(), 0.01);
if (verifyAltitudeGainAndLoss) {
assertEquals(trackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(trackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
} else {
assertFalse(importedTrackStatistics.hasTotalAltitudeGain());
assertFalse(importedTrackStatistics.hasTotalAltitudeLoss());
}
}
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time));
tp.setAccuracy(accuracy);
tp.setSpeed(Speed.of(speed));
tp.setHeartRate_bpm(heartRate);
tp.setCyclingCadence_rpm(cyclingCadence);
tp.setPower(power);
tp.setAltitudeGain(altitudeGain);
tp.setAltitudeLoss(altitudeGain); //TODO
tp.setSensorDistance(distance);
return tp;
}
}
@@ -0,0 +1,158 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.InputStream;
import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.StringUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
/**
* Test that legacy KML/GPX formats can still be imported.
*/
@RunWith(JUnit4.class)
public class LegacyImportTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
private Track.Id importTrackId;
@After
public void tearDown() {
if (importTrackId != null) {
contentProviderUtils.deleteTrack(context, importTrackId);
}
}
/**
* Check that data with statistics markers is imported and those ignored.
* Statistics marker were created to avoid recomputing the track statistics (e.g., distance until a certain time).
*/
@LargeTest
@Test
public void kml_with_statistics_marker() {
// given
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_statistics_marker);
// when
// 1. import
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("unknown", importedTrack.getCategory());
assertEquals("Test Track", importedTrack.getDescription());
assertEquals("2020-11-28 18:06", importedTrack.getName());
assertEquals("UNKNOWN", importedTrack.getIcon());
// 2. markers
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
// first 3 trackpoints
assertTrackpoint(importedTrackPoints.get(0), TrackPoint.Type.SEGMENT_START_AUTOMATIC, "2020-11-28T17:06:22.401Z", 1.234156, 12.340097, 469.286376953125);
assertTrackpoint(importedTrackPoints.get(1), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:06:25.448Z", 1.23415, 12.340036, 439.1626281738281);
assertTrackpoint(importedTrackPoints.get(2), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:06:47.888Z", 1.23405, 12.340057, 421.8070983886719);
// created resume trackpoint with time of next valid trackpoint
assertTrackpoint(importedTrackPoints.get(3), TrackPoint.Type.SEGMENT_START_AUTOMATIC, "2020-11-28T17:06:55.861Z", 1.23405, 12.340057, 419.93902587890625);
assertTrackpoint(importedTrackPoints.get(4), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:06:56.905Z", 1.23405, 12.340057, 419.9036560058594);
assertTrackpoint(importedTrackPoints.get(5), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:07:20.870Z", 1.234046, 12.340082, 417.99432373046875);
}
/**
* At least one valid location is required.
* Before v3.15.0, Tracks without TrackPoints could be created; such tracks cannot be imported as we cannot restore the TrackStatistics (especially startTime and stopTime).
*/
@LargeTest
@Test(expected = ImportParserException.class)
public void kml_without_locations() {
// given
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
// when
importTrackId = trackImporter.importFile(inputStream).get(0);
}
/**
* Check that data that contains pause (lat=100, lng=0) and resume (lat=200, lng=0) locations are restored to a segment break.
*/
@LargeTest
@Test
public void gpx_with_pause_resume() {
// given
GpxFileTrackImporter trackImporter = new GpxFileTrackImporter(context);
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_gpx_pause_resume);
// when
// 1. import
importTrackId = trackImporter.importFile(inputStream).get(0);
// then
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("the category", importedTrack.getCategory());
assertEquals("the description", importedTrack.getDescription());
assertEquals("2021-01-07 22:51", importedTrack.getName());
assertEquals("UNKNOWN", importedTrack.getIcon());
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
// first segment
assertTrackpoint(importedTrackPoints.get(0), TrackPoint.Type.SEGMENT_START_AUTOMATIC, "2021-01-07T21:51:59.179Z", 14.0, 3.0, 10.0);
assertTrackpoint(importedTrackPoints.get(1), TrackPoint.Type.TRACKPOINT, "2021-01-07T21:52:00.653Z", 14.001, 3.0, 10.0);
assertTrackpoint(importedTrackPoints.get(2), TrackPoint.Type.TRACKPOINT, "2021-01-07T21:52:01.010Z", 14.002, 3.0, 10.0);
assertTrackpoint(importedTrackPoints.get(3), TrackPoint.Type.SEGMENT_END_MANUAL, "2021-01-07T21:52:02.658Z", null, null, null);
// created resume trackpoint with time of next valid trackpoint
assertTrackpoint(importedTrackPoints.get(4), TrackPoint.Type.SEGMENT_START_MANUAL, "2021-01-07T21:52:03.873Z", null, null, null);
assertTrackpoint(importedTrackPoints.get(5), TrackPoint.Type.TRACKPOINT, "2021-01-07T21:52:04.103Z", 14.003, 3.0, 10.0);
}
private void assertTrackpoint(final TrackPoint trackPoint, final TrackPoint.Type type, final String when, final Double longitude, final Double latitude, final Double altitude) {
assertEquals(StringUtils.parseTime(when), trackPoint.getTime());
assertEquals(type, trackPoint.getType());
if (longitude == null) {
assertFalse(trackPoint.hasLocation());
} else {
assertEquals(latitude, (Double) trackPoint.getLatitude());
assertEquals(longitude, (Double) trackPoint.getLongitude());
}
if (altitude == null) {
assertFalse(trackPoint.hasAltitude());
} else {
assertEquals(altitude, (Double) trackPoint.getAltitude());
}
}
}
@@ -20,6 +20,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.time.Duration;
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
import static org.junit.Assert.assertEquals;
@@ -32,13 +34,13 @@ import static org.junit.Assert.assertEquals;
@RunWith(JUnit4.class)
public class AdaptiveLocationListenerPolicyTest {
private static final long MIN = 1000;
private static final long MAX = 3000;
private static final Duration MIN = Duration.ofMillis(1000);
private static final Duration MAX = Duration.ofMillis(3000);
private static final int MIN_DISTANCE = 10;
private static final long NEW_IDLE_TIME_BIG = 10000;
private static final long NEW_IDLE_TIME_NORMAL = 5000;
private static final long NEW_IDLE_TIME_SMALL = 2000;
private static final long NEW_IDLE_TIME_LESS_THAN_MIN = 500;
private static final Duration NEW_IDLE_TIME_BIG = Duration.ofMillis(10000);
private static final Duration NEW_IDLE_TIME_NORMAL = Duration.ofMillis(5000);
private static final Duration NEW_IDLE_TIME_SMALL = Duration.ofMillis(2000);
private static final Duration NEW_IDLE_TIME_LESS_THAN_MIN = Duration.ofMillis(500);
private AdaptiveLocationListenerPolicy adaptiveLocationListenerPolicy;
@Before
@@ -62,7 +64,7 @@ public class AdaptiveLocationListenerPolicyTest {
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
assertEquals(NEW_IDLE_TIME_NORMAL.dividedBy(2).getSeconds(), adaptiveLocationListenerPolicy.getDesiredPollingInterval().getSeconds());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
@@ -11,10 +11,12 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -25,7 +27,10 @@ public class TrackRecordingServiceNotificationManagerTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Mock
private TrackPoint locationMock;
private TrackPoint trackPointMock;
@Mock
private TrackStatistics trackStatisticsMock;
@Mock
private NotificationCompat.Builder notificationCompatBuilder;
@@ -35,18 +40,20 @@ public class TrackRecordingServiceNotificationManagerTest {
@Test
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
when(locationMock.hasAccuracy()).thenReturn(true);
when(locationMock.getAccuracy()).thenReturn(999f);
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
when(trackPointMock.hasAccuracy()).thenReturn(true);
when(trackPointMock.getAccuracy()).thenReturn(999f);
when(trackPointMock.getSpeed()).thenReturn(Speed.of(0));
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
subject.setMetricUnits(true);
// when
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, locationMock, 1000);
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 1000);
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
// then
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
@@ -33,11 +33,11 @@ import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@@ -45,9 +45,9 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -85,6 +85,7 @@ 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()));
@@ -103,7 +104,6 @@ public class TrackRecordingServiceTest {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
// Ensure that the database is empty before every test
@@ -113,7 +113,8 @@ public class TrackRecordingServiceTest {
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
@@ -138,7 +139,7 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testRecording_noTracks() throws Exception {
public void testRecording_noTracks() throws TimeoutException {
// given
List<Track> tracks = contentProviderUtils.getTracks();
assertTrue(tracks.isEmpty());
@@ -146,36 +147,38 @@ public class TrackRecordingServiceTest {
// when
Intent startIntent = createStartIntent(context);
mServiceRule.startService(startIntent);
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
.getService();
// then
// Test if we start in no-recording mode by default.
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_oldTracks() throws Exception {
public void testRecording_oldTracks() throws TimeoutException {
// given
createDummyTrack(trackId, -1L, false);
createDummyTrack(trackId);
// when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
// then
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_serviceRestart_whileRecording() throws Exception {
public void testRecording_serviceRestart_whileRecording() throws TimeoutException {
// given
createDummyTrack(trackId, -1L, true);
createDummyTrack(trackId);
//when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
service.resumeTrack(trackId);
// then
assertTrue(service.isRecording());
@@ -183,43 +186,78 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testRecording_pauseAndResume() throws Exception {
public void testRecording_start() throws TimeoutException {
// given
createDummyTrack(trackId, -1L, true);
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
newTrackPoint(service);
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
// when
Track.Id trackId = service.startNewTrack();
// then
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(1, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
}
@MediumTest
@Test
public void testRecording_stop() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack();
// when
service.endCurrentTrack();
// then
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(2, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
}
@MediumTest
@Test
public void testRecording_pauseAndResume() throws TimeoutException {
// given
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack();
// when
service.pauseCurrentTrack();
// then
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
//when
service.resumeTrack(trackId);
newTrackPoint(service);
// then
assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(5, trackPoints.size());
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(3).getLatitude(), 0.01);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(3, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(2).getType());
}
@MediumTest
@Test
public void testRecording_resumeStoppedTrack() throws Exception {
public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException {
// given
createDummyTrack(trackId, -1L, true);
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
newTrackPoint(service);
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
service.endCurrentTrack();
assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
// when
service.resumeTrack(trackId);
@@ -227,56 +265,62 @@ public class TrackRecordingServiceTest {
// then
assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(4, trackPoints.size());
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(2).getType());
assertEquals(TrackPoint.Type.TRACKPOINT, trackPoints.get(3).getType());
}
@Ignore("Sometimes fails on CI.")
@MediumTest
@Test
public void testRecording_orphanedRecordingTrack() throws Exception {
public void testRecording_stopPausedTrack() throws TimeoutException {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
service.pauseCurrentTrack();
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
// when
// Just set recording track to a bogus value.
// Make sure that the service will not start recording and will clear the bogus track.
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(2, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
}
@MediumTest
@Test
public void testStartNewTrack_alreadyRecording() throws Exception {
public void testStartNewTrack_alreadyRecording() throws TimeoutException {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
Track.Id trackId = service.getRecordingTrackId();
// when
Track.Id newTrackId = service.startNewTrack();
// then
assertNotNull(trackId);
assertNull(newTrackId);
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
assertEquals(trackId, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testEndCurrentTrack_noRecording() throws Exception {
public void testEndCurrentTrack_noRecording() throws TimeoutException {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
assertFalse(service.isRecording());
// when
@@ -284,81 +328,81 @@ public class TrackRecordingServiceTest {
service.endCurrentTrack();
// then
assertFalse(PreferencesUtils.isRecording(context));
assertNull(service.getRecordingTrackId());
assertFalse(service.isRecording());
}
@MediumTest
@Test
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
public void testInsertWaypointMarker_noRecordingTrack() throws TimeoutException {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
assertFalse(service.isRecording());
// when
Marker.Id waypointId = service.insertMarker(null, null, null, null);
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNull(waypointId);
assertNull(markerId);
}
@MediumTest
@Test
public void testInsertWaypointMarker_validWaypoint() throws Exception {
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
newTrackPoint(service);
Track.Id trackId = service.getRecordingTrackId();
// when
Marker.Id waypointId = service.insertMarker(null, null, null, null);
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNotEquals(-1L, waypointId);
Marker wpt = contentProviderUtils.getMarker(waypointId);
assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
assertNotEquals(new Marker.Id(-1L), markerId);
Marker wpt = contentProviderUtils.getMarker(markerId);
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
assertEquals(trackId, wpt.getTrackId());
assertEquals(0.0, wpt.getLength(), 0.01);
assertEquals(0.0, wpt.getLength().toM(), 0.01);
assertNotNull(wpt.getLocation());
service.endCurrentTrack();
}
private void addTrack(Track track, boolean isRecording) {
assertTrue(track.getId().isValid());
private void addTrack(Track track) {
assertNotNull(track.getId());
contentProviderUtils.insertTrack(track);
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
}
// NOTE: Do not use to create a track that is currently recording.
private void createDummyTrack(Track.Id id, long stopTime, boolean isRecording) {
private void createDummyTrack(Track.Id id) {
Track dummyTrack = new Track();
dummyTrack.setId(id);
dummyTrack.setName("Dummy Track");
TrackStatistics trackStatistics = new TrackStatistics();
trackStatistics.setStopTime_ms(stopTime);
Instant now = Instant.now();
trackStatistics.setStartTime(now.minusSeconds(5L));
trackStatistics.setStopTime(now.minusSeconds(1L));
dummyTrack.setTrackStatistics(trackStatistics);
addTrack(dummyTrack, isRecording);
addTrack(dummyTrack);
}
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
private static void newTrackPoint(TrackRecordingService trackRecordingService) throws InterruptedException {
newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
}
static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
}
/**
* Inserts a location and waits for 200ms.
*/
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
Location location = new Location("gps");
private static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
Location location = new Location("");
location.setLongitude(longitude);
location.setLatitude(latitude);
location.setAccuracy(accuracy);
@@ -366,10 +410,6 @@ public class TrackRecordingServiceTest {
location.setTime(time);
location.setBearing(3.0f);
TrackPoint trackPoint = new TrackPoint(location);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
//TODO Needed?
Thread.sleep(200);
trackRecordingService.newTrackPoint(trackPoint, 50);
}
}
@@ -4,6 +4,7 @@ import android.content.ContentProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -22,9 +23,9 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
@@ -34,6 +35,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
/**
* Tests insert location.
@@ -53,7 +55,7 @@ public class TrackRecordingServiceTestLocation {
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingServiceInterface service;
private TrackRecordingService service;
@BeforeClass
public static void preSetUp() {
@@ -69,21 +71,18 @@ public class TrackRecordingServiceTestLocation {
customContentProvider.attachInfo(context, null);
contentProviderUtils = new ContentProviderUtils(context);
tearDown();
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
}
@After
public void tearDown() {
// Reset service (if some previous test failed)
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@@ -95,21 +94,30 @@ public class TrackRecordingServiceTestLocation {
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 2, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 3, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 4, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 6, 15);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(6, trackPoints.size());
assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(8, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
new Pair<>(TrackPoint.Type.TRACKPOINT, 3),
new Pair<>(TrackPoint.Type.TRACKPOINT, 4),
new Pair<>(TrackPoint.Type.TRACKPOINT, 5),
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
), trackPoints);
}
@MediumTest
@@ -117,23 +125,29 @@ public class TrackRecordingServiceTestLocation {
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
// given
Track.Id trackId = service.startNewTrack();
assertNotNull(trackId);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000001, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000001, 35.0, 2, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 3, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 5, 15);
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 6, 15);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(2, trackPoints.size());
assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(4, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
), trackPoints);
}
// @MediumTest
@@ -173,11 +187,14 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(3, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(6, trackPoints.get(2).getAccuracy(), 0.01);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
), trackPoints);
}
@MediumTest
@@ -199,15 +216,18 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(4, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(2).getAccuracy(), 0.01); //TODO Check why this trackPoint is inserted.
assertEquals(6, trackPoints.get(3).getAccuracy(), 0.01);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(6, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
new Pair<>(TrackPoint.Type.TRACKPOINT, 5), //TODO Check why this trackPoint is inserted.
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
@@ -230,26 +250,30 @@ public class TrackRecordingServiceTestLocation {
});
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 0, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 0);
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(6, trackPoints.size());
assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(4, trackPoints.get(4).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(5).getAccuracy(), 0.01);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(8, trackPoints.size());
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
new Pair<>(TrackPoint.Type.TRACKPOINT, 3),
new Pair<>(TrackPoint.Type.TRACKPOINT, 4),
new Pair<>(TrackPoint.Type.TRACKPOINT, 5),
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
), trackPoints);
}
@MediumTest
@@ -270,14 +294,28 @@ public class TrackRecordingServiceTestLocation {
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(7, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(4).getLatitude(), 0.01);
assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
assertTrackPoints(List.of(
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
new Pair<>(TrackPoint.Type.SEGMENT_START_AUTOMATIC, 2),
new Pair<>(TrackPoint.Type.TRACKPOINT, 3),
new Pair<>(TrackPoint.Type.SEGMENT_START_AUTOMATIC, 4),
new Pair<>(TrackPoint.Type.TRACKPOINT, 5),
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
), trackPoints);
}
private void assertTrackPoints(List<Pair<TrackPoint.Type, Object>> typeAndAccuracy, List<TrackPoint> actual) {
assertEquals(typeAndAccuracy.size(), actual.size());
for (int i = 0; i < typeAndAccuracy.size(); i++) {
assertEquals(typeAndAccuracy.get(i).first, actual.get(i).getType());
if (typeAndAccuracy.get(i).second != null) {
assertEquals((Integer) typeAndAccuracy.get(i).second, actual.get(i).getAccuracy(), 0.01);
}
}
}
}
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.location.Location;
import android.os.Looper;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
@@ -19,6 +20,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -34,7 +36,6 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@@ -52,6 +53,7 @@ public class TrackRecordingServiceTestLooper {
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
@@ -76,7 +78,6 @@ public class TrackRecordingServiceTestLooper {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply();
// Ensure that the database is empty before every test
@@ -86,7 +87,8 @@ public class TrackRecordingServiceTestLooper {
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
@@ -98,28 +100,36 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_gps_accuracy_key), 500);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.voice_frequency_key), Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.voice_frequency_key), -1);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.voice_frequency_key), 1);
editor.commit();
fullRecordingSession();
}
@@ -127,65 +137,77 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.max_recording_distance_key), Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.max_recording_distance_key), 50);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_distance_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_distance_interval_key), 2);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.min_recording_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingInterval() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.min_recording_interval_key), 2);
editor.commit();
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putInt(context.getString(R.string.recording_gps_accuracy_key), Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)));
editor.commit();
fullRecordingSession();
}
@@ -198,18 +220,17 @@ public class TrackRecordingServiceTestLooper {
}
private void fullRecordingSession() throws TimeoutException {
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
assertFalse(service.isRecording());
// Start a track.
Track.Id trackId = service.startNewTrack();
assertTrue(trackId.isValid());
assertNotNull(trackId);
assertTrue(service.isRecording());
Track track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
assertEquals(trackId, track.getId());
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
assertEquals(trackId, service.getRecordingTrackId());
// Insert a few points, markers and statistics.
long startTime = System.currentTimeMillis();
@@ -222,7 +243,7 @@ public class TrackRecordingServiceTestLooper {
location.setTime(startTime + i * 10000);
location.setBearing(3.0f);
TrackPoint trackPoint = new TrackPoint(location);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
service.newTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) {
@@ -233,13 +254,12 @@ public class TrackRecordingServiceTestLooper {
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
assertEquals(trackId, track.getId());
TrackStatistics trackStatistics = track.getTrackStatistics();
assertNotNull(trackStatistics);
assertTrue(trackStatistics.getStartTime_ms() > 0);
assertTrue(trackStatistics.getStopTime_ms() >= trackStatistics.getStartTime_ms());
assertTrue(trackStatistics.getStartTime().isAfter(Instant.ofEpochMilli(0)));
assertTrue(trackStatistics.getStopTime().isAfter(trackStatistics.getStartTime()));
}
}
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import org.junit.After;
import org.junit.Before;
@@ -25,6 +26,9 @@ public class HandlerServerTest {
@Mock
private LocationHandler locationHandler;
@Mock
private SharedPreferences sharedPreferences;
private HandlerServer subject;
@Before
@@ -41,16 +45,16 @@ public class HandlerServerTest {
@Test
public void onSharedPreferenceChanged() {
// when
subject.onSharedPreferenceChanged(null, null, null);
subject.onSharedPreferenceChanged(context, sharedPreferences, null);
// then
verify(locationHandler).onSharedPreferenceChanged(null, null, null);
verify(locationHandler).onSharedPreferenceChanged(context, sharedPreferences, null);
}
@Test
public void sendTrackPoint() throws InterruptedException {
// given
TrackPoint trackPoint = new TrackPoint();
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
int accuracy = 50;
// when
@@ -3,10 +3,12 @@ package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
@@ -25,6 +27,7 @@ import static org.mockito.Mockito.verify;
public class LocationHandlerTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@Mock
private HandlerServer handlerServer;
@@ -32,14 +35,19 @@ public class LocationHandlerTest {
@InjectMocks
private LocationHandler locationHandler;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@Before
public void setUp() {
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key));
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.min_recording_interval_key));
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
locationHandler.onStart(context);
}
@@ -77,10 +85,9 @@ public class LocationHandlerTest {
@Test
public void testOnLocationChanged_poorAccuracy() {
// given
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
// when
// poor latitude
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
// then
@@ -0,0 +1,50 @@
package de.dennisguse.opentracks.services.sensors;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* NOTE: Test data is completely artificial.
*/
public class AltitudeSumManagerTest {
private final AltitudeSumManager subject = new AltitudeSumManager();
private static void addSensorValue(AltitudeSumManager altitudeSumManager, float[] values) {
for (float f : values) {
altitudeSumManager.onSensorValueChanged(f);
}
}
@Before
public void setUp() {
subject.reset();
}
@Test
public void getAltitudeGainLoss_downhill() {
// given
subject.setConnected(true);
// then
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1015.14f, 1015.15f});
// then
Assert.assertEquals(0f, subject.getAltitudeGain_m(), 0.01);
Assert.assertEquals(48.0, subject.getAltitudeLoss_m(), 0.01);
}
@Test
public void sensorUnavailable() {
// given
subject.setConnected(false);
// then
subject.onSensorValueChanged(999f);
// then
Assert.assertNull(subject.getAltitudeGain_m());
Assert.assertNull(subject.getAltitudeLoss_m());
}
}
@@ -1,50 +0,0 @@
package de.dennisguse.opentracks.services.sensors;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* NOTE: Test data is completely artificial.
*/
public class ElevationSumManagerTest {
private ElevationSumManager elevationSumManager = new ElevationSumManager();
private static void addSensorValue(ElevationSumManager elevationSumManager, float[] values) {
for (float f : values) {
elevationSumManager.onSensorValueChanged(f);
}
}
@Before
public void setUp() {
elevationSumManager.reset();
}
@Test
public void getElevationGainLoss_downhill() {
// given
elevationSumManager.setConnected(true);
// then
addSensorValue(elevationSumManager, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1015.14f, 1015.15f});
// then
Assert.assertEquals(0f, elevationSumManager.getElevationGain_m(), 0.01);
Assert.assertEquals(-48.0, elevationSumManager.getElevationLoss_m(), 0.01);
}
@Test
public void sensorUnavailable() {
// given
elevationSumManager.setConnected(false);
// then
elevationSumManager.onSensorValueChanged(999f);
// then
Assert.assertNull(elevationSumManager.getElevationGain_m());
Assert.assertNull(elevationSumManager.getElevationLoss_m());
}
}
@@ -15,12 +15,20 @@
*/
package de.dennisguse.opentracks.stats;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Tests for {@link TrackStatistics}.
@@ -28,7 +36,7 @@ import static org.junit.Assert.assertEquals;
*
* @author Rodrigo Damazio
*/
@RunWith(JUnit4.class)
@RunWith(AndroidJUnit4.class)
public class TrackStatisticsTest {
private TrackStatistics statistics;
@@ -39,51 +47,77 @@ public class TrackStatisticsTest {
}
@Test
public void testMerge() {
public void testMerge_no_data() {
// given
TrackStatistics statistics2 = new TrackStatistics();
statistics.setStartTime_ms(1000L); // Resulting start time
statistics.setStopTime_ms(2500L);
statistics2.setStartTime_ms(3000L);
statistics2.setStopTime_ms(4000L); // Resulting stop time
statistics.setTotalTime(1500L);
statistics2.setTotalTime(1000L); // Result: 1500+1000
statistics.setMovingTime(700L);
statistics2.setMovingTime(600L); // Result: 700+600
statistics.setTotalDistance(750.0);
statistics2.setTotalDistance(350.0); // Result: 750+350
statistics.setTotalElevationGain(50.0f);
statistics2.setTotalElevationGain(850.0f); // Result: 850+50
statistics.setMaxSpeed(60.0); // Resulting max speed
statistics2.setMaxSpeed(30.0);
statistics.setMaxElevation(1250.0);
statistics.setMinElevation(1200.0); // Resulting min elevation
statistics2.setMaxElevation(3575.0); // Resulting max elevation
statistics2.setMinElevation(2800.0);
// when
statistics.merge(statistics2);
assertEquals(1000L, statistics.getStartTime_ms());
assertEquals(4000L, statistics.getStopTime_ms());
assertEquals(2500L, statistics.getTotalTime());
assertEquals(1300L, statistics.getMovingTime());
assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
assertEquals(900.0, statistics.getTotalElevationGain(), 0.001);
assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed(), 0.001);
assertEquals(1200.0, statistics.getMinElevation(), 0.001);
assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
// then
assertNull(statistics.getStartTime());
assertNull(statistics.getStopTime());
assertEquals(Duration.ofSeconds(0), statistics.getMovingTime());
assertEquals(Duration.ofSeconds(0), statistics.getTotalTime());
assertNull(statistics.getTotalAltitudeGain());
assertNull(statistics.getTotalAltitudeLoss());
assertEquals(Double.NEGATIVE_INFINITY, statistics.getMaxAltitude(), 0.0);
assertEquals(Double.POSITIVE_INFINITY, statistics.getMinAltitude(), 0.0);
assertEquals(0.0, statistics.getMaxSpeed().toMPS(), 0.0);
assertEquals(0.0, statistics.getAverageSpeed().toMPS(), 0.0);
assertEquals(0.0, statistics.getAverageMovingSpeed().toMPS(), 0.0);
}
@Test
public void testMerge() {
// given
TrackStatistics statistics2 = new TrackStatistics();
statistics.setStartTime(Instant.ofEpochMilli(1000)); // Resulting start time
statistics.setStopTime(Instant.ofEpochMilli(2500));
statistics2.setStartTime(Instant.ofEpochMilli(3000));
statistics2.setStopTime(Instant.ofEpochMilli(4000)); // Resulting stop time
statistics.setTotalTime(Duration.ofMillis(1500));
statistics2.setTotalTime(Duration.ofMillis(1000)); // Result: 1500+1000
statistics.setMovingTime(Duration.ofMillis(700));
statistics2.setMovingTime(Duration.ofMillis(600)); // Result: 700+600
statistics.setTotalDistance(Distance.of(750.0));
statistics2.setTotalDistance(Distance.of(350.0)); // Result: 750+350
statistics.setTotalAltitudeGain(50.0f);
statistics2.setTotalAltitudeGain(850.0f); // Result: 850+50
statistics.setMaxSpeed(Speed.of(60.0)); // Resulting max speed
statistics2.setMaxSpeed(Speed.of(30.0));
statistics.setMaxAltitude(1250.0);
statistics.setMinAltitude(1200.0); // Resulting min altitude
statistics2.setMaxAltitude(3575.0); // Resulting max altitude
statistics2.setMinAltitude(2800.0);
// when
statistics.merge(statistics2);
// then
assertEquals(Instant.ofEpochMilli(1000), statistics.getStartTime());
assertEquals(Instant.ofEpochMilli(4000), statistics.getStopTime());
assertEquals(Duration.ofMillis(2500), statistics.getTotalTime());
assertEquals(Duration.ofMillis(1300), statistics.getMovingTime());
assertEquals(1100.0, statistics.getTotalDistance().toM(), 0.001);
assertEquals(900.0, statistics.getTotalAltitudeGain(), 0.001);
assertEquals(Speed.of(statistics.getTotalDistance(), statistics.getMovingTime()).toMPS(), statistics.getMaxSpeed().toMPS(), 0.001);
assertEquals(1200.0, statistics.getMinAltitude(), 0.001);
assertEquals(3575.0, statistics.getMaxAltitude(), 0.001);
}
@Test
public void testGetAverageSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setTotalTime(50000); // in milliseconds
assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
statistics.setTotalDistance(Distance.of(1000.0));
statistics.setTotalTime(Duration.ofMillis(50000));
assertEquals(20.0, statistics.getAverageSpeed().toMPS(), 0.001);
}
@Test
public void testGetAverageMovingSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setMovingTime(20000); // in milliseconds
assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
statistics.setTotalDistance(Distance.of(1000.0));
statistics.setMovingTime(Duration.ofMillis(20000));
assertEquals(50.0, statistics.getAverageMovingSpeed().toMPS(), 0.001);
}
}
@@ -0,0 +1,167 @@
package de.dennisguse.opentracks.stats;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class TrackStatisticsUpdaterTest {
private static final Distance GPS_DISTANCE = Distance.of(50);
@Test
public void addTrackPoint_TestingTrack() {
// given
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
// when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE));
// 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(2.5, statistics.getMinAltitude(), 0.01);
assertEquals(27.5, statistics.getMaxAltitude(), 0.01);
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(7.11, statistics.getAverageSpeed().toMPS(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_not_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
tp3.setSpeed(Speed.of(5f));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000));
tp2.setSpeed(Speed.of(5f));
TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000));
tp2.setSpeed(Speed.of(5f));
tp4.setSensorDistance(Distance.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
tp5.setSensorDistance(Distance.of(10f));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(110.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
// when
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp5, GPS_DISTANCE);
// then
assertEquals(125.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
}
@Test
public void addTrackPoint_distance_from_GPS_not_moving_and_sensor_moving() {
// given
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
TrackPoint tp4 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(4000));
tp4.setSensorDistance(Distance.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
tp5.setSensorDistance(Distance.of(10f));
// when
subject.addTrackPoint(tp1, GPS_DISTANCE);
subject.addTrackPoint(tp2, GPS_DISTANCE);
subject.addTrackPoint(tp3, GPS_DISTANCE);
// then
assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
// when
subject.addTrackPoint(tp4, GPS_DISTANCE);
subject.addTrackPoint(tp5, GPS_DISTANCE);
// then
assertEquals(15, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_not_moving() {
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_moving() {
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_not_moving_and_sensor_speed() {
}
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
@Test
public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() {
}
}
@@ -8,10 +8,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.time.Duration;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
@@ -26,59 +27,146 @@ public class AnnouncementUtilsTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Test
public void getAnnouncement() {
public void getAnnouncement_metric_speed() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null);
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
// then
assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
}
@Test
public void getAnnouncement_withInterval() {
public void getAnnouncement_withInterval_metric_speed() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
IntervalStatistics intervalStatistics = new IntervalStatistics();
List<TrackPoint> trackPoints = Arrays.asList(TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second);
intervalStatistics.build(trackPoints, 1000);
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
int speedId = R.plurals.voiceSpeedKilometersPerHour;
double kmPerHour = lastInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH;
String firstPartMsg = "OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour";
String rateMsg = " Lap speed of " + context.getResources().getQuantityString(speedId, getQuantityCount(kmPerHour), kmPerHour);
String msg = firstPartMsg + rateMsg;
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", lastInterval);
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
// then
assertEquals(msg, announcement);
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour Lap speed of 51.2 kilometers per hour", announcement);
}
private int getQuantityCount(double d) {
if (d == 0) {
return 0;
} else if (d == 1) {
return 1;
} else if (d == 2) {
return 2;
} else {
int count = (int) d;
return Math.max(count, 3);
}
@Test
public void getAnnouncement_metric_pace() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, null);
// then
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer", announcement);
}
@Test
public void getAnnouncement_withInterval_metric_pace() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
// then
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer Lap time of 1 minute 10 seconds per kilometer", announcement);
}
@Test
public void getAnnouncement_imperial_speed() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, null);
// then
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour", announcement);
}
@Test
public void getAnnouncement_withInterval_imperial_speed() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
// then
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour Lap speed of 31.8 miles per hour", announcement);
}
@Test
public void getAnnouncement_imperial_pace() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, null);
// then
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile", announcement);
}
@Test
public void getAnnouncement_withInterval_imperial_pace() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(Distance.of(20000));
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
stats.setMaxSpeed(Speed.of(100));
stats.setTotalAltitudeGain(6000f);
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
// then
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
}
}
@@ -47,7 +47,7 @@ public class BluetoothUtilsTest {
// then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertNull(sensor.getSpeed());
assertNull(sensor.getDistanceSpeed());
}
@Test
@@ -60,7 +60,7 @@ public class BluetoothUtilsTest {
// then
assertNull(sensor.getCadence());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
}
@Test
@@ -73,6 +73,18 @@ public class BluetoothUtilsTest {
// then
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
}
@Test
public void parseCyclingPower_power() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, 0, 0);
characteristic.setValue(new byte[]{0, 0, 40, 0});
// when
int power_w = BluetoothUtils.parseCyclingPower(characteristic);
// then
assertEquals(40, power_w);
}
}
@@ -0,0 +1,63 @@
package de.dennisguse.opentracks.util;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@RunWith(JUnit4.class)
public class ExportUtilsTest extends TestCase {
@Test
public void testIsExportFileExists_exists() {
// given
UUID uuid = UUID.randomUUID();
String format = "kmz";
List<String> fileNames = new ArrayList<>();
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
fileNames.add(uuid.toString().substring(0, 8) + "_other name name.kmz");
// when
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
// then
assertTrue(exists);
}
@Test
public void testIsExportFileExists_not_exists1() {
// given
UUID uuid = UUID.randomUUID();
String format = "gpx";
List<String> fileNames = new ArrayList<>();
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_name.gpx");
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
// when
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
// then
assertFalse(exists);
}
@Test
public void testIsExportFileExists_not_exists2() {
// given
UUID uuid = UUID.randomUUID();
String format = "kmz";
List<String> fileNames = new ArrayList<>();
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
// when
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
// then
assertFalse(exists);
}
}
@@ -22,12 +22,14 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_ok() {
// given
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
@@ -36,12 +38,13 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_invalid() {
// given
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -50,12 +53,13 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_noValue() {
// given
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -9,7 +9,7 @@ public class PressureSensorUtilsTest {
// real data at 500Hz stationary
@Test
public void elevationChanges_none() {
public void altitudeChanges_none() {
// given
float[] sensorValues_hPa = new float[]{1015.6876f, 1015.699f, 1015.70905f, 1015.71075f, 1015.7224f, 1015.72f, 1015.7373f, 1015.7481f, 1015.74133f, 1015.73553f, 1015.7462f, 1015.74896f, 1015.752f, 1015.754f, 1015.7456f, 1015.73303f, 1015.7288f, 1015.7239f, 1015.7298f, 1015.7387f, 1015.72534f, 1015.72577f, 1015.72797f, 1015.71796f, 1015.7113f, 1015.7097f, 1015.7096f, 1015.7013f, 1015.7072f, 1015.71063f, 1015.7088f, 1015.7115f, 1015.713f, 1015.7121f, 1015.7062f, 1015.7062f, 1015.6994f, 1015.69617f, 1015.68945f, 1015.698f, 1015.6913f, 1015.69696f, 1015.69293f, 1015.6955f, 1015.6928f, 1015.6939f, 1015.68787f, 1015.6881f, 1015.68536f, 1015.68726f, 1015.6879f, 1015.6904f, 1015.6937f, 1015.69476f, 1015.7046f, 1015.7019f, 1015.7079f, 1015.71124f, 1015.71216f, 1015.71436f, 1015.7171f, 1015.7186f, 1015.7186f, 1015.7246f, 1015.72046f, 1015.7211f, 1015.72375f, 1015.7194f, 1015.72125f, 1015.7161f, 1015.71185f, 1015.7188f, 1015.721f, 1015.7252f, 1015.72266f, 1015.7236f, 1015.72614f, 1015.7266f, 1015.7344f, 1015.7376f, 1015.7337f, 1015.7253f, 1015.71875f, 1015.7219f, 1015.7219f, 1015.7317f, 1015.7284f, 1015.73444f, 1015.73584f, 1015.7369f, 1015.73254f, 1015.7369f, 1015.7336f, 1015.73254f, 1015.73676f, 1015.74176f, 1015.74506f, 1015.7478f, 1015.755f, 1015.7515f, 1015.7543f, 1015.7459f, 1015.7468f, 1015.7492f, 1015.74585f, 1015.74756f, 1015.74756f, 1015.75085f, 1015.754f, 1015.75336f, 1015.7568f, 1015.75165f, 1015.761f, 1015.7527f, 1015.75684f, 1015.7635f, 1015.7585f, 1015.7552f, 1015.7475f, 1015.74506f, 1015.7542f, 1015.75586f, 1015.74176f, 1015.74414f, 1015.7501f, 1015.751f, 1015.7478f, 1015.755f, 1015.75836f, 1015.7592f, 1015.75256f, 1015.7559f, 1015.76654f, 1015.76154f, 1015.766f, 1015.77014f, 1015.76904f, 1015.76013f, 1015.7674f, 1015.76514f, 1015.7674f, 1015.7635f, 1015.76404f, 1015.7607f, 1015.754f, 1015.7574f, 1015.7607f, 1015.7607f, 1015.7607f, 1015.754f, 1015.7567f, 1015.7616f, 1015.7643f, 1015.7666f, 1015.7609f, 1015.766f, 1015.76764f, 1015.755f, 1015.75757f, 1015.74927f, 1015.74927f, 1015.7426f, 1015.745f, 1015.745f, 1015.7409f, 1015.7376f, 1015.7326f, 1015.74164f, 1015.74664f, 1015.745f, 1015.7459f, 1015.7502f, 1015.7493f, 1015.7476f, 1015.7493f, 1015.751f, 1015.7601f, 1015.76434f, 1015.766f, 1015.756f, 1015.7617f, 1015.7623f, 1015.76404f, 1015.76733f, 1015.7607f, 1015.7623f, 1015.76337f, 1015.7584f, 1015.7573f, 1015.7502f, 1015.7534f, 1015.7534f, 1015.7601f, 1015.75684f, 1015.7518f, 1015.75574f, 1015.7524f, 1015.7507f, 1015.7484f, 1015.7502f, 1015.7534f, 1015.7623f, 1015.75903f, 1015.75574f, 1015.75574f, 1015.7573f, 1015.7524f, 1015.75574f, 1015.75574f, 1015.7607f, 1015.7573f, 1015.754f, 1015.74677f, 1015.754f, 1015.75574f, 1015.75903f, 1015.7573f, 1015.7551f, 1015.7573f, 1015.754f, 1015.7507f, 1015.7374f, 1015.739f, 1015.73566f, 1015.73566f, 1015.7367f, 1015.7417f, 1015.7417f, 1015.739f, 1015.74835f, 1015.74567f, 1015.7517f, 1015.74066f, 1015.7374f, 1015.739f, 1015.7417f, 1015.73065f, 1015.72894f, 1015.73505f, 1015.73334f, 1015.74005f, 1015.74005f, 1015.7367f, 1015.7434f, 1015.744f, 1015.7423f, 1015.7384f, 1015.739f, 1015.7374f, 1015.7374f, 1015.7374f, 1015.7341f, 1015.73505f, 1015.7341f, 1015.7267f, 1015.72833f, 1015.7234f, 1015.7167f, 1015.725f, 1015.7217f, 1015.7234f, 1015.73004f, 1015.7324f, 1015.7423f, 1015.74005f, 1015.73505f, 1015.73004f, 1015.739f, 1015.7417f, 1015.74567f, 1015.74066f, 1015.74146f, 1015.74146f, 1015.73816f, 1015.7274f, 1015.72906f, 1015.7274f, 1015.7341f, 1015.739f, 1015.744f, 1015.7417f, 1015.7374f, 1015.7324f, 1015.73816f, 1015.74146f, 1015.7374f, 1015.7341f, 1015.7365f, 1015.7365f, 1015.74146f, 1015.74414f, 1015.7475f, 1015.7448f, 1015.74146f, 1015.74585f, 1015.7475f, 1015.75146f, 1015.75146f, 1015.7591f, 1015.75476f, 1015.7614f, 1015.75476f, 1015.7492f, 1015.74817f, 1015.7498f, 1015.7515f, 1015.75586f, 1015.7509f, 1015.7448f, 1015.7525f, 1015.7581f, 1015.7592f};
float firstSensorValue = sensorValues_hPa[0];
@@ -20,7 +20,7 @@ public class PressureSensorUtilsTest {
}
}
// Simulate a sudden drop of elevation.
// Simulate a sudden drop of altitude.
// test data
@Test
public void computeChanges_downhill() {
@@ -28,22 +28,22 @@ public class PressureSensorUtilsTest {
float firstSensorValue = sensorValues_hPa[0];
// when
float elevationGain_m = 0;
float elevationLoss_m = 0;
float altitudeGain_m = 0;
float altitudeLoss = 0;
float lastUsedPressureValue_hPa = firstSensorValue;
for (float v : sensorValues_hPa) {
PressureSensorUtils.ElevationChange elevationChange = PressureSensorUtils.computeChanges_m(lastUsedPressureValue_hPa, v);
if (elevationChange != null) {
elevationGain_m += elevationChange.getElevationGain_m();
elevationLoss_m += elevationChange.getElevationLoss_m();
lastUsedPressureValue_hPa = elevationChange.getCurrentSensorValue_hPa();
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChanges_m(lastUsedPressureValue_hPa, v);
if (altitudeChange != null) {
altitudeGain_m += altitudeChange.getAltitudeGain_m();
altitudeLoss += altitudeChange.getAltitudeLoss_m();
lastUsedPressureValue_hPa = altitudeChange.getCurrentSensorValue_hPa();
}
}
// then
assertEquals(0f, elevationGain_m, 0.01);
assertEquals(-15.0f, elevationLoss_m, 0.01);
assertEquals(0f, altitudeGain_m, 0.01);
assertEquals(15.0f, altitudeLoss, 0.01);
assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing
}
}

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