Compare commits

...

296 Commits

Author SHA1 Message Date
Dennis Guse 6133ef121a Release v3.3.0 2020-01-01 14:46:28 +01:00
Dennis Guse c6d24e9534 Pictures: removed MediaScanner. 2020-01-01 14:44:23 +01:00
Dennis Guse 441d3f721f Debug version: use a black logo. 2020-01-01 14:39:16 +01:00
Dennis Guse 3c212e4bcc Bugfix: for taking pictures; provide access to external/pictures. 2020-01-01 14:28:58 +01:00
Dennis Guse 50eb71bc1f Cleanup. 2020-01-01 14:16:02 +01:00
Dennis Guse 0806821b5f Fixed import/export message to show Storage Access Framework selected path. 2019-12-29 23:33:10 +01:00
Dennis Guse 36d0f17953 Removed french Congo translation (identical to values-fr). 2019-12-29 21:57:39 +01:00
Dennis Guse 700adb7972 Export dialog: removed "from folder" text. 2019-12-29 21:56:48 +01:00
Dennis Guse a156369f8b Import dialog: removed "from folder" text. 2019-12-29 21:32:01 +01:00
Dennis Guse c0f6dc511b Removed values-ar-rXB/strings.xml as it was for testing only. 2019-12-29 21:28:36 +01:00
Dennis Guse 63e1461eba Removed values-en-rXA/strings.xml as it was for testing only. 2019-12-29 20:59:30 +01:00
Dennis Guse 804e5f10c8 Removed values-bn/strings.xml as translations were invalid. 2019-12-29 20:53:37 +01:00
Dennis Guse 6cc7271a41 Build: debug version uses different icon and appLabel. 2019-12-29 20:46:46 +01:00
Dennis Guse f66194189b Removed translation description from values/strings.xml 2019-12-29 20:15:37 +01:00
Dennis Guse 5d97d6d492 Use Android 7.0 localization: merge latin american and present en-rGB as default for en. 2019-12-29 19:13:26 +01:00
Dennis Guse f3a308e669 BREAKING CHANGE: pictures are now stored in getExternalFilesDir(); no migration takes place! 2019-12-29 15:43:06 +01:00
Dennis Guse a230504925 Export & import via Storage Access Framework. 2019-12-29 15:41:28 +01:00
Dennis Guse 90e19e282c Upgraded tests to Junit4. 2019-12-29 15:31:53 +01:00
Dennis Guse b585bfe44c Moved to Github organization: OpenTracksApp. 2019-12-27 23:54:29 +01:00
Dennis Guse 475e04eeb4 Add PlantUML model for data structures. 2019-12-27 18:07:29 +01:00
Dennis Guse f89b2b0efb Removed TripStatists min/max latitude/longitude.
This also removed: search does not account for closer tracks (to current position).
2019-12-27 18:01:11 +01:00
Dennis Guse b94f3c176b Include unit in variable names. 2019-12-27 18:01:10 +01:00
Dennis Guse d1d729d287 Removed unused ContentProvider.getFirstTrackPointId(). 2019-12-27 18:01:07 +01:00
Dennis Guse e23bbcc405 Removed unnecessary Track.startId and Track.stopId. 2019-12-27 18:01:05 +01:00
Dennis Guse 4129985b0f Renamed SensorDataSetLocation to TrackPoint (similar to SQLite table). 2019-12-27 18:00:51 +01:00
Dennis Guse 5cb7b623d9 Removed TripStatistics from Waypoints. 2019-12-26 21:15:42 +01:00
Dennis Guse 3c52efadd6 Removed unused Waypoint.TripStatistics. 2019-12-26 21:15:41 +01:00
Dennis Guse 73b4977051 Removed unused Waypoint.startId and Waypoint.stopId. 2019-12-26 21:15:41 +01:00
Dennis Guse 422c2c02ce Assume GPS provides always time (removed workaround). 2019-12-26 21:15:41 +01:00
Dennis Guse d692329975 Settings: hide advanced recording settings.
And use switches rather than checkboxes.
2019-12-25 23:22:20 +01:00
Dennis Guse 931574bc11 Release v3.2.11 2019-12-25 10:19:22 +01:00
Dennis Guse caddbd8501 MarkerDetailActivity: removed leftover statistic functionality. 2019-12-23 21:44:22 +01:00
Dennis Guse 8ece2c8612 Removed WaypointCreationRequest. 2019-12-23 21:39:37 +01:00
Dennis Guse 3a219321d1 Removed country-specific translations that are identical. 2019-12-23 07:43:05 +01:00
Dennis Guse 35cb924a17 StatsFragment: show elevation without decimal digits. 2019-12-22 20:39:43 +01:00
Dennis Guse b67e5bbd20 StatsFragment: left align current elevation. 2019-12-22 20:33:23 +01:00
Dennis Guse 1db275230e Bugfix: crash on opening heart rate sensor dialog (settings). 2019-12-22 19:09:01 +01:00
Dennis Guse 5ac72092fb Test: export and import tracks via KML and GPX; now includes waypoints.
TripStatistics is not tested.
2019-12-22 18:59:23 +01:00
Dennis Guse bdd9ded1e8 Test: export and import tracks via KML and GPX.
KML now exports track icon (name; not the url).
2019-12-22 18:59:23 +01:00
Dennis Guse 759aeb6ed6 Removed DescriptionGenerator interface. 2019-12-22 18:59:23 +01:00
Dennis Guse 12023d51ae Move data classes into separate package. 2019-12-22 18:59:23 +01:00
Dennis Guse c426785b90 Cleanup. 2019-12-22 18:59:23 +01:00
Dennis Guse 09d960c7a1 First waypoint of a track does not store the statistics of the track. 2019-12-22 18:59:23 +01:00
Dennis Guse f37fe4a503 Removed WaypointType. 2019-12-22 18:59:23 +01:00
Dennis Guse 013e56bef0 Replaced TabHost by Material design TabsLayout. 2019-12-22 18:59:23 +01:00
Dennis Guse d6835ba766 TrackRecordingService does not save the context. 2019-12-22 18:59:23 +01:00
Dennis Guse d7890cbc81 Shortened fr-FR short description.
Thanks to Edern.
2019-12-17 20:35:41 +01:00
Dennis Guse 2b6886c534 Update release procedure. 2019-12-16 22:41:16 +01:00
Dennis Guse e77590a125 Release v3.2.10 2019-12-15 22:11:19 +01:00
Dennis Guse ca330b2d80 Cleanup. 2019-12-15 21:28:03 +01:00
Dennis Guse 20f1524701 Cleanup. 2019-12-15 17:09:44 +01:00
Dennis Guse 96b2a15643 Bugfix: import and export on Android 10 requires requesting (privacy-unfriendly) legacy behavior. 2019-12-15 16:50:13 +01:00
Dennis Guse ad59392e02 Fix travisCI. 2019-12-14 23:58:43 +01:00
Dennis Guse 29f92b887c Fixed TrackRecordingService.insertWaypoint() to allow deprecated behavior. 2019-12-14 23:55:04 +01:00
Dennis Guse 46328211c2 Added promotion sticker. 2019-12-14 23:55:04 +01:00
Dennis Guse 3760130d1f Github: add donation. 2019-12-14 23:42:07 +01:00
Dennis Guse 1094827fac TrackListActivity: fixed landscape layout. 2019-12-11 23:05:31 +01:00
Dennis Guse 36fcb44af5 TripRecordingService: removed unused trip statistics marker updater. 2019-12-11 23:05:31 +01:00
Dennis Guse a683fbac4b Device reboot: do not restart a not successfully ended track recording. 2019-12-11 23:05:31 +01:00
Dennis Guse 64ad9019d1 ChartView: is not configurable anymore. 2019-12-11 23:05:31 +01:00
Dennis Guse 1e80d5c8a8 ChartView: draw waypoints (again).
Functionality was (accidentally) removed in 5a264c5c53
2019-12-11 23:05:31 +01:00
Matthias Lisin ac765b1ee8 Revert duration hint to MAY_DUCK.
Pass OnAudioFocusChangeListener to abandonAudioFocus
2019-12-10 12:58:57 +01:00
Dennis Guse 850c4f0a9a TravisCI: disabled non-functioning installation of Android emulator. 2019-12-10 06:53:22 +01:00
Dennis Guse 4c32999752 Add Google Play link. 2019-12-10 06:50:58 +01:00
Dennis Guse 76823b167a TrackListActivity: removed delete all from action menu. 2019-12-08 13:12:15 +01:00
Dennis Guse 9994cfd260 Bugfix: NPE. 2019-12-07 13:37:06 +01:00
Dennis Guse 1750236474 Added automation for Google Play publication (using fastlane). 2019-12-06 22:26:40 +01:00
Dennis Guse fed6e31f9e Cleanup. 2019-12-06 08:00:20 +01:00
Dennis Guse 9bafc7d4a1 Release v3.2.9 2019-12-04 19:57:27 +01:00
Dennis Guse be58b48d20 Bugfix: StatsFragment (onResume and track was recording) overwrote in the total time UI until next UI update. 2019-12-03 22:21:45 +01:00
Dennis Guse 303abac980 Removed READ_PHONE_STATE permission and use AudioManager.MODE_IN_CALL instead. 2019-12-03 22:01:01 +01:00
Dennis Guse d1a459efc2 Get audio focus onStart. 2019-12-03 20:28:22 +01:00
Dennis Guse 96069e6f09 Cleanup. 2019-12-03 20:24:57 +01:00
Dennis Guse 301a721329 Settings: localize no heart rate sensor selected. 2019-12-03 19:53:14 +01:00
Dennis Guse e8c6801a32 Refactor StatsFragment. 2019-12-03 19:19:19 +01:00
Dennis Guse 355f3ef614 Added "screen always on"-feature to TrackDetailActivity.
And the "show on lockscreen"-feature is only used while recording.
2019-12-03 16:57:56 +01:00
Dennis Guse eaa6083586 Bugfix: Heart rate was not shown in stats. 2019-12-03 07:31:29 +01:00
Dennis Guse a0c5d02c51 Fixed and removed device features (AndroidManifest). 2019-12-03 07:31:29 +01:00
Dennis Guse 76f9f5fc82 MarkerDetailFragment now shows the blue pin as default image. 2019-12-03 07:31:29 +01:00
Dennis Guse 8e2598e93b Removed split markers. 2019-12-03 07:31:24 +01:00
Dennis Guse c0672c41df Bugfix: crash if heart rate sensor was null (i.e., preference not initialized). 2019-11-29 23:18:49 +01:00
Dennis Guse 4c6601fbc5 Release v3.2.8 2019-11-29 22:58:22 +01:00
edern-appere b99c87d1bd Added French translation for Fdroid. 2019-11-29 22:46:43 +01:00
Dennis Guse 66ffbfe951 Fixed ChartFragment tests.
Initialization of looper was missing.
2019-11-29 22:43:51 +01:00
Dennis Guse 8cb01a1027 Bugfix: sharing a track was not adding the file(s).
While migrating to enable sharing multiple tracks a configuration option was missed.
2019-11-29 22:39:36 +01:00
Dennis Guse 193e5eccbb Cleanup. 2019-11-29 22:39:36 +01:00
Dennis Guse bf7fc28bb0 Bugfix: picture name suffixes were malformed on export and thus could not be re-imported. 2019-11-29 22:39:31 +01:00
Dennis Guse e35cbc343a Add pull request template. 2019-11-29 07:54:39 +01:00
Dennis Guse 1c7b053c8c Fixed MarkerDetailActivity.
The orientation of the layout was not set and thus adding the toolbar broke the UI.
2019-11-28 18:09:11 +01:00
Dennis Guse 88cdcd4b47 Use GestureDetector rather than doing it on our own. 2019-11-27 21:42:05 +01:00
Dennis Guse 1432fed8f9 Removed zoom controls for ChartFragment. 2019-11-27 21:41:53 +01:00
Dennis Guse a14307b9a4 Moved Chart* into own package. 2019-11-27 20:19:12 +01:00
Dennis Guse 3bdce5aa08 Cleanup: use autocloseable. 2019-11-25 23:09:08 +01:00
Dennis Guse d7526f93b8 Cleanup: removed ContentProviderUtils interface. 2019-11-25 23:02:48 +01:00
Dennis Guse 8932578b90 Release v3.2.7 2019-11-25 22:03:40 +01:00
Dennis Guse 631ac9881d Wrap help into PreferenceCategory. 2019-11-25 22:03:02 +01:00
Dennis Guse 78334ed797 Fixed Spanish project description (short); was still German. 2019-11-24 21:12:07 +01:00
Dennis Guse b06da89654 Show chart for distance as well as time.
Also removed configuration option.
2019-11-24 19:55:00 +01:00
Dennis Guse d6c2e78993 Cleanup. 2019-11-24 18:48:31 +01:00
Dennis Guse 67d1ac551d Show GPS accuracy while recording in the notification of the TrackRecordingService and notify if accuracy goes beyond configured threshold. 2019-11-24 18:44:04 +01:00
Dennis Guse dcb6be717d Cleanup. 2019-11-23 22:46:39 +01:00
Dennis Guse f2c04eec1a Add help activity.
Can be accessed from Settings.
2019-11-23 21:43:07 +01:00
Dennis Guse a08311cf43 Temporary bugfix: disable landscape mode for TrackListActivity. 2019-11-22 07:44:01 +01:00
Dennis Guse 1e28f3aa86 Add TTSUtils.
(required by previous commit)
2019-11-22 07:43:19 +01:00
Dennis Guse 426af41f2c Move TTS stream definition into one place. 2019-11-20 19:27:41 +01:00
Dennis Guse fd08a219f9 Add Spanish store description. 2019-11-20 19:13:21 +01:00
Dennis Guse 25b6aec0e9 Release v3.2.6 2019-11-19 22:09:10 +01:00
Dennis Guse 3e40209d15 Refactoring. 2019-11-19 08:00:12 +01:00
Dennis Guse 54ff94f905 Settings: renamed Bluetooth sensor to "Heart rate". 2019-11-18 17:16:50 +01:00
Dennis Guse 7a4faf6b0d Fixed SettingsActivity. 2019-11-18 17:13:42 +01:00
Dennis Guse 8922b00a5f README: add screenshots. 2019-11-17 18:45:29 +01:00
Dennis Guse 702efa8693 Removed shortcut to Android's Bluetooth settings. 2019-11-17 18:25:01 +01:00
Dennis Guse e7a8c897a3 Settings: scan for Bluetooth sensors rather than using bonded devices.
Bonded devices do no provide information what services (e.g., heart rate) they support.
2019-11-17 18:24:02 +01:00
Dennis Guse c59d1d87bd Bluetooth: move getting adapter to BluetoothUtils. 2019-11-17 18:23:08 +01:00
Dennis Guse a706efb8b3 Bugfix. 2019-11-14 18:30:57 +01:00
Dennis Guse e391e83a00 Build: show version and tag in about page. 2019-11-14 18:04:50 +01:00
Dennis Guse 2940bf20d8 Settings: remove all defaults from code. 2019-11-12 21:33:04 +01:00
Dennis Guse 081db5a58b Settings: remove default setting for default activity. 2019-11-12 21:33:04 +01:00
Dennis Guse b34805272f Bugfix: not connecting to heart rate sensor - need to start BluetoothRemoteSensorManager.
Introduced in bc5ab0d7e5
2019-11-09 00:02:20 +01:00
Dennis Guse 98443dba99 Cleanup. 2019-11-08 22:45:34 +01:00
Dennis Guse dd6e733d8c Release v3.2.5 2019-11-08 22:31:49 +01:00
Dennis Guse 92d3439c3b Re-enable SummaryProvider for default_activity. 2019-11-08 07:58:30 +01:00
Dennis Guse bd155cd600 Use SummaryProvider.
Fixed #46.
2019-11-08 07:36:45 +01:00
Dennis Guse e14e53c0ae Fix hack to update summary of statsRate if statsUnit change. 2019-11-07 23:32:28 +01:00
Dennis Guse 77a6a63174 Re-initialize PreferenceFragment, when sharedpreferences were reset. 2019-11-07 22:24:15 +01:00
Dennis Guse 40f2463f09 Fail safety: if settings cannot loaded (invalid data in sharedpreferences), reset it and try again. 2019-11-07 19:15:17 +01:00
Dennis Guse e5d1048c8d Bugfix reset settings: really clear sharedPreferences before settings default values. 2019-11-07 19:15:17 +01:00
Dennis Guse bf3b529fb2 Add link to Transifex for localization.
Fixes #27.
2019-11-07 19:15:17 +01:00
Dennis Guse d0fde46027 Android does not allow referencing integer in string-arrays: store everything as string and parse it later (also for tests). 2019-11-06 18:40:58 +01:00
Dennis Guse 0f348dff10 Android does not allow referencing integer in string-arrays: store everything as string and parse it later. 2019-11-06 18:16:40 +01:00
Dennis Guse d3f5080e73 Fixed issues introduced in 353c958ae0 and 2a60aa8f59. 2019-11-06 07:42:15 +01:00
Dennis Guse a453c8ec1e TrackDetailActivity: disable activity type changer in view mode.
Fix #3.
2019-11-05 22:45:29 +01:00
Dennis Guse 2a60aa8f59 Settings: removed some default definitions from code into xml. 2019-11-05 22:02:23 +01:00
Dennis Guse 41e7870509 Settings: removed some default definitions from code into xml. 2019-11-05 21:13:51 +01:00
Dennis Guse 57f66b77ab Reorganized settings (sharedpreferences and keys); xml only. 2019-11-05 21:13:51 +01:00
Dennis Guse 53572406e9 Mark foreground service to use location and connection to other devices, so it can run properly on Android 10+ when app is in the background.
Might fix #61.
2019-11-05 21:13:51 +01:00
Dennis Guse a288255ab1 Added issue and feature templates. 2019-11-05 19:33:57 +01:00
Dennis Guse 353c958ae0 Moved accessing sharedPreferences into PreferencesUtils. 2019-11-03 01:44:46 +01:00
Dennis Guse bc5ab0d7e5 Removed unnecessary RemoteSensorManagerFactory.
TrackRecordingService is only using BluetoothRemoteSensorManager and thus factory pattern is not required.
2019-11-03 00:21:51 +01:00
Dennis Guse 9d915b5640 Cleanup. 2019-11-03 00:13:14 +01:00
Dennis Guse 3aaee270eb Release v3.2.4 2019-11-01 23:54:34 +01:00
Dennis Guse d53a7156e2 Bugfix: removed custom dialogs to set voice/split frequency preferences from the context menu directly.
Fixed #49.
2019-11-01 23:38:07 +01:00
Dennis Guse 3af24ea3d0 Release v3.2.3 2019-10-31 22:07:21 +01:00
Dennis Guse 5483212b06 Fixed StringUtils.formatDecimal to work with decimals of 0. 2019-10-30 23:02:02 +01:00
Dennis Guse 419279fe5e Cleanup. 2019-10-30 22:36:22 +01:00
Dennis Guse 501e899b27 StatsFragment: need to reconnect to TrackRecordingService in onResume as it is unbound in onStop.
Fixed #50.
2019-10-30 22:36:16 +01:00
Dennis Guse 3bb9edb009 Connect to new sensor if settings are changed.
Fixed #52.
2019-10-30 07:48:29 +01:00
Dennis Guse e52b1188a3 Cleanup. 2019-10-29 23:05:17 +01:00
Dennis Guse 9e877e4762 Removed stale verify. 2019-10-29 22:39:51 +01:00
Dennis Guse 5711757110 If sharing multiple tracks, share them as separate files. 2019-10-29 17:43:23 +01:00
Dennis Guse e1c33dad43 Move to Android ShareSheet. 2019-10-29 17:34:21 +01:00
Dennis Guse 4b2400774c TravisCI: start emulator, which is required for testing. 2019-10-28 23:25:07 +01:00
Dennis Guse afd4a59622 CI: enabled tests.
Fixed #6.
2019-10-27 16:50:28 +01:00
Dennis Guse bdce437470 Removed AnnoucementPeriodTaskTest as these were broken beyond repair. 2019-10-27 16:46:56 +01:00
Dennis Guse 33479cda11 Fixed TrackRecordingService tests: start service properly if needed. 2019-10-27 15:58:50 +01:00
Dennis Guse 385cee3dc7 Fixed Date parsing test (account for
rounding up microseconds).
2019-10-27 13:45:43 +01:00
Dennis Guse d0bbd7655b Ignore KML waypoints that do not have a type. 2019-10-27 13:40:06 +01:00
Dennis Guse a83b8f62fe Repaired Track importer tests. 2019-10-27 13:24:20 +01:00
Dennis Guse 0cd9176703 Heart rate must be larger than zero to be shown while tracking. 2019-10-26 14:35:03 +02:00
Dennis Guse d8e526923d Removed RemoteSensorManager as abstraction was not used anymore. 2019-10-26 14:26:16 +02:00
Dennis Guse 3a6f417662 Added disconnect toast message. 2019-10-26 14:23:38 +02:00
Dennis Guse 7d807a7f11 Refactor Bluetooth LE stack to use autoReconnect.
Removed additional timerTask to reconnect (was not working anymore).

Fixed #41.
2019-10-26 14:11:55 +02:00
Dennis Guse e9d5718495 Cleanup. 2019-10-23 18:47:32 +02:00
Dennis Guse 0dd7f74a11 Removed temporary sensor manager (was not used anymore). 2019-10-22 21:15:11 +02:00
Dennis Guse 9bd4ba489c Release: v3.2.2 2019-10-21 23:00:13 +02:00
Dennis Guse dd55b50dad Re-added actionbar (actually toolbar) to SettingsActivity. 2019-10-21 22:54:23 +02:00
Dennis Guse 1719d21a5b Bugfix: export now really includes sensor data (as double) and import is done as float. 2019-10-21 22:34:33 +02:00
Dennis Guse 75ef8d99d4 SensorDataSet: data unavailable is now NaN. 2019-10-21 22:34:28 +02:00
Dennis Guse ae06fd1680 Bugfix: export sensor data if available and allowed. 2019-10-20 18:39:31 +02:00
Dennis Guse 2b03f7dea8 Use LinearLayout for SearchActivity (show toolbar correctly). 2019-10-20 17:05:09 +02:00
Dennis Guse 7591b012b4 Extracted ShareActionProvider setup code into a separate class. 2019-10-20 16:55:28 +02:00
Dennis Guse 0e089f49f3 Re-added application logo to material design toolbar. 2019-10-19 12:27:11 +02:00
Dennis Guse 5ad1856b13 Removed context menu from ListActivities. 2019-10-17 21:32:55 +02:00
Dennis Guse 965dbbe829 Removed custom action bar style. 2019-10-17 21:26:31 +02:00
Dennis Guse 262c9886df Removed aggregated stats activity. 2019-10-17 21:22:07 +02:00
Dennis Guse ac5a9ea65d Use Androidx compat's toolbar instead of actionbars.
ListView's multiselect still uses actionbar.
2019-10-17 21:19:14 +02:00
Dennis Guse ed9b4c8f3d Add workaround for Compat's ShareActionProvider, which is not available when inflating in a AbsListView.MultiChoiceModeListener. 2019-10-17 18:33:45 +02:00
Dennis Guse 209aa6a8a5 Hide share action from SearchActivity as it is not (yet) functional. 2019-10-17 07:58:11 +02:00
Dennis Guse 81ee6ac1fa Do not use custom style for AlertDialogs, so PreferenceDialogs and AlertDialogs look the same. 2019-10-17 06:38:05 +02:00
Dennis Guse 024dd603cb Migration to Androidx PreferenceCompat. 2019-10-17 06:00:47 +02:00
Dennis Guse 50654c6baf Release v3.2.1 2019-10-15 18:36:40 +02:00
Dennis Guse 8c9bf60fc6 Update Settings if the units (metric/imperial) are changed. 2019-10-15 17:29:59 +02:00
Dennis Guse a83eb0b983 Added de-DE description. 2019-10-13 12:19:27 +02:00
Dennis Guse ab3cbfea0f Metadata: adjust to fastlane layout. 2019-10-13 12:19:27 +02:00
Dennis Guse c93e5ca1a6 Settings: moved to Androidx Compat and use one screen only. 2019-10-12 18:00:51 +02:00
Dennis Guse c2841092da Settings: moved setup into helper. 2019-09-19 22:30:28 +02:00
Dennis Guse 8b11e1d611 DialogFragments: do not save state. 2019-09-19 22:30:28 +02:00
Dennis Guse 6375a851b2 Moved ChooseActivityTypeDialog to be a real DialogFragment.
Note: This breaks RecordingsSettingsActivity.
2019-09-19 22:28:04 +02:00
Dennis Guse f21200a7ec UnitConversation: always use double for division.
Fixed wrong computation (00:00) of total time.
2019-09-19 19:29:49 +02:00
Dennis Guse 5752cebdec Released 3.2.0 2019-09-19 19:14:34 +02:00
Dennis Guse fa6329c157 Fdroid: downgraded to Gradle 5.5 2019-09-19 19:08:04 +02:00
Dennis Guse 98a9884798 Fdroid: added link in README and moved metadata to fastlane folder. 2019-09-19 19:04:17 +02:00
Dennis Guse 1f7f615411 Import/Export: first option is KMZ containing all data. 2019-09-16 23:43:30 +02:00
Dennis Guse a79345ccf9 Grant ACCESS_FINE_LOCATION for TrackRecordingServiceTest. 2019-09-16 22:47:37 +02:00
Dennis Guse 732e0e76f9 Removed custom styling of title divider of used dialogs (just too much code). 2019-09-16 22:37:37 +02:00
Dennis Guse 63d29f2097 Cleanup. 2019-09-16 19:44:34 +02:00
Dennis Guse 9dd2f7d1c1 KML/KMZ export and share: add configuration to export with trackdetail without the rest.
Fixed some issues in TRACK_ONLY mode as too much data was exposed.
2019-09-15 09:47:48 +02:00
Dennis Guse afd25363b3 For export and import: KMZ first as it contains all data incl. photos. 2019-09-15 09:22:10 +02:00
Dennis Guse 2eb5a42099 When it should be KMZ, make it KMZ. 2019-09-15 08:45:11 +02:00
Dennis Guse 9c753034b4 Update title in MarkerDetailActivity when viewpager changes. 2019-09-15 00:31:53 +02:00
Dennis Guse 95b7c701a2 Cleanup: do not assume first waypoint contains (always) TripStatistics. 2019-09-15 00:14:15 +02:00
Dennis Guse 56685a9dec Bugfix: export of images via KMZ is now working. 2019-09-14 23:35:28 +02:00
Dennis Guse 45b13ea813 Removed tab icons as they were not shown anyhow. 2019-09-14 22:43:26 +02:00
Dennis Guse b0feed84bb Removed voice frequency menu icon as it not shown anyhow. 2019-09-14 22:34:58 +02:00
Dennis Guse 0e2da83e1f Removed snowboarding and skiing due to lack of icons. 2019-09-14 22:20:29 +02:00
Dennis Guse fa364a150d Styling for progress bar via tint. 2019-09-14 21:50:10 +02:00
Dennis Guse 6ec78d77a9 Changed gps menu item icon to represent actual state of the gps (and not the action to be executed). 2019-09-14 13:25:23 +02:00
Dennis Guse 8853bb5408 Replaced drawables with vectors (SVG) for TrackController.
- ic_button_pause
- ic_button_record
- ic_button_stop
2019-09-14 02:58:05 +02:00
Dennis Guse e4d6355875 Replaced drawables with vectors (SVG) for TrackListActivity.
- ic_pause
- ic_record
- ic_arrow_long
2019-09-14 01:34:41 +02:00
Dennis Guse 1e08d72cc9 Removed resources of indetermined progressbar (unused). 2019-09-14 00:22:35 +02:00
Dennis Guse ea82d2df94 Fixed tabhost resource id. 2019-09-13 23:55:39 +02:00
Dennis Guse 71c0114f8f Fixed lint error. 2019-09-13 23:14:14 +02:00
Dennis Guse a3955c7c37 Made generic activity type selectable.
Decreased icon size.
2019-09-13 23:00:12 +02:00
Dennis Guse e610b70590 Use logo (white) for generic track (replaces similar a png with gradient). 2019-09-13 22:55:25 +02:00
Dennis Guse 7f4d47d7b5 Foreground service notification now uses the logo directly (vector). 2019-09-13 22:38:39 +02:00
Dennis Guse fd27ec11a2 Replaced chartViews current position marker with logo. 2019-09-12 23:52:04 +02:00
Dennis Guse 2aa036f3aa Material design icons for dialogs. 2019-09-12 23:44:21 +02:00
Dennis Guse 2c49a4f3f1 Introduced material design icons. 2019-09-12 23:24:19 +02:00
Dennis Guse 10e4a52947 Fixed stats_content. 2019-09-12 20:39:13 +02:00
Dennis Guse 9f3936e180 Removed unused icons. 2019-09-12 20:17:29 +02:00
Dennis Guse a3a76ce0c3 Disabled lint for AppCompatResource. 2019-09-12 20:15:03 +02:00
Dennis Guse 545db52f94 Cleanup. 2019-09-12 18:33:45 +02:00
Dennis Guse bfa02a97ee Upgrade appcompat library. 2019-09-12 17:57:33 +02:00
Dennis Guse 3a2eeaa204 Fixed lint warnings in stats_content layout. 2019-09-12 17:57:33 +02:00
Dennis Guse 42b3a5dfc9 Refactored stats_content layout to contain all content (not use include). 2019-09-12 17:57:28 +02:00
Dennis Guse b5cb47a0c7 Refactored TrackRecordingServiceConnection: made static methods object methods. 2019-09-10 22:45:20 +02:00
Dennis Guse 443e069a13 Refactored TrackRecordingServiceConnection.
- implemented interfaces directly
- moved static methods from TrackRecordingServiceConnectionUtil
2019-09-10 22:37:54 +02:00
Dennis Guse 9c432c3110 Removed unused menu from SearchListActivity. 2019-09-10 21:20:41 +02:00
Dennis Guse 79934d51ac Revert "Moved from onTouch to onClick listeners."
This reverts commit 025dee68
2019-09-10 21:20:41 +02:00
Dennis Guse 496cf9145b Removed usage of Broadcast (were used by Widget). 2019-09-10 21:20:40 +02:00
Dennis Guse 264bb3746f Removed ControlRecordingService.
It was implemented to enable other apps to start and stop track recordings.
2019-09-10 21:19:11 +02:00
Dennis Guse 7b1ccf4507 Cleanup AndroidManifest. 2019-09-10 21:18:55 +02:00
Dennis Guse a326265dba Fixed searchview, which did not trigger SearchListActivity. 2019-09-10 21:18:25 +02:00
Dennis Guse 0864bc8104 Cleaned up ContentProviderUtils's factory pattern. 2019-09-10 21:18:03 +02:00
Dennis Guse f5656470a9 Removed Parcelable from all data classes as it not needed anymore. 2019-09-10 21:17:45 +02:00
Dennis Guse 5bc773f1aa Removed modifiedtime from Track - was likely a Google Drive leftover. 2019-09-10 21:17:18 +02:00
Dennis Guse 6bfd8c8db8 Cleanup: Use Pair instead of String[2]. 2019-09-10 21:16:58 +02:00
Dennis Guse df2865456e Removed obsolete workaround for DateUtils.formatElapsedTime(). 2019-09-10 21:16:38 +02:00
Dennis Guse fc6d5f1f23 Removed Widget. 2019-09-10 21:16:16 +02:00
Dennis Guse 96dab8c81b Build: enabled abort on lint errors. 2019-09-10 21:16:00 +02:00
Dennis Guse 1d13d51f0c Cleanup. 2019-09-10 21:12:56 +02:00
Dennis Guse 87d92d25f9 Cleanup: mainly code formatting and made LocationIterator implement autoclosable. 2019-09-08 12:39:40 +02:00
Dennis Guse 025dee6821 Moved from onTouch to onClick listeners. 2019-09-08 12:39:40 +02:00
Dennis Guse 796d086615 Cleanup.
Cleanup.
2019-09-08 12:39:37 +02:00
Dennis Guse bb1ef08474 Splitted TrackRecordingServiceTest into two files and fixed issue about calling Looper.prepare() only once for all tests. 2019-09-05 22:44:27 +02:00
Dennis Guse cc770d8bb4 Fixed (some) AnnoucementPeriodicTask[Factory] tests. 2019-09-05 07:41:16 +02:00
Dennis Guse 8a56c262cb Cleanup. 2019-09-04 22:09:22 +02:00
Dennis Guse c0a9958b5b Fixed (some) TrackRecordingServiceTests. 2019-09-04 21:33:30 +02:00
Dennis Guse 7351cd3377 TrackDataHubTest is beyond repair and thus removed. 2019-09-04 20:41:39 +02:00
Dennis Guse 7b3d5848b9 Fixed ControlRecordingService: only unbind if connected. 2019-09-04 17:43:24 +02:00
Dennis Guse 0f311e062e Fixed ContentProviderUtils tests.
Account for using a real (non-empty) database and thus non-predictable object ids.
2019-09-03 21:32:35 +02:00
Dennis Guse 57930a8d86 Added changelog for 2991/v3.1.0 2019-09-03 21:32:35 +02:00
Dennis Guse 7c0069394f Release v3.1.0 2019-09-02 19:42:40 +02:00
Dennis Guse 379913fb3e Cleanup. 2019-09-02 19:39:58 +02:00
Dennis Guse 542d9ccb77 Request storage permission on import all and export all. 2019-09-02 19:37:55 +02:00
Dennis Guse c8ae3b83b5 Added option to show statistics while recording without unlocking the device. 2019-09-02 18:36:31 +02:00
Dennis Guse 0a046fc100 Cleanup. 2019-09-02 18:02:43 +02:00
Dennis Guse c551a38a8b Show full version name based upon git describe --tags on startup and also in AboutActivity. 2019-09-02 16:05:24 +02:00
Dennis Guse 72b1ca82ed Moved debug warnings into Startup. 2019-09-02 15:50:35 +02:00
Dennis Guse 1ef81c45db Build: do not minify code for release.
The amount of space saved is too small to be of a real benefit while stack traces become unreadable.
2019-09-02 15:50:35 +02:00
Dennis Guse 08e6ae76f2 Fix TravisCI for Android-29 (#34) 2019-09-02 15:44:21 +02:00
Dennis Guse 6e410e049d Bugfix: creating waypoint from camera works now.
Introduced in commit 677b691ef1
2019-08-26 22:57:00 +02:00
Dennis Guse 22b7962940 Upgraded to Gradle 5.6 and AndroidStudio 3.5.0. 2019-08-26 21:55:40 +02:00
Dennis Guse 5788f99b20 Catch ActivityNotFound exception for starting geo-data intents. 2019-08-25 22:20:08 +02:00
Dennis Guse 8685e43dc2 Fixed ShareContentProvider and KMZ mime type. 2019-08-25 21:37:47 +02:00
Dennis Guse b8bea27155 Added AndroidStudio coding style configuration. 2019-08-25 16:21:20 +02:00
Dennis Guse c9226d3b79 Fixed CustomContentProvider tests. 2019-08-25 16:20:27 +02:00
Dennis Guse 9701feff04 Removed duplicate registration of images at MediaScanner. 2019-08-25 08:42:36 +02:00
Dennis Guse b8b40a88ef Show Marker menu entry at defined position. 2019-08-25 08:41:11 +02:00
Dennis Guse adcf7b388b Finish MarkerDetailActivity after storing marker. 2019-08-25 00:26:04 +02:00
Dennis Guse 3a7562d96c Modified "show on map" functionality to only share locations and no other track data. 2019-08-25 00:21:36 +02:00
Dennis Guse 2753cf7d0b Removed search suggestion configuration. 2019-08-24 18:48:50 +02:00
Dennis Guse 5841415a0b Refactored TrackWriter instantiation. 2019-08-24 18:14:32 +02:00
Dennis Guse d79fa07834 Share multiple tracks from TrackListActivity. 2019-08-24 17:51:57 +02:00
Dennis Guse ff6caf706e Added show on map to TrackDetailActivity. 2019-08-24 17:51:57 +02:00
Dennis Guse c953fbc67a Implemented "Share" from TrackListActivity's context menu. 2019-08-24 09:37:38 +02:00
Dennis Guse 55870cca85 Bugfix: import/export all now react to back button. 2019-08-24 08:49:59 +02:00
Dennis Guse 0123242be6 Bugfix: use correct entries/values for track name. 2019-08-24 08:44:43 +02:00
Dennis Guse 81a6d7b53a Added release recipe for FDroid. 2019-08-24 08:39:13 +02:00
Dennis Guse f423b07cee Added Changelogs for v3.0.0, v3.0.1, and the upcoming release. 2019-08-24 08:27:31 +02:00
Dennis Guse 33231bdbb1 Renaming to AbstractListActivity. 2019-08-23 07:37:03 +02:00
Dennis Guse 677b691ef1 Moved special intent creation to IntentUtils. 2019-08-23 07:33:58 +02:00
Dennis Guse 5e74e80a9a Use correct mime type for KML and KMZ. 2019-08-23 07:19:40 +02:00
Dennis Guse 70b5d799a2 Share marker works everywhere and share track from TrackDetailActivity. 2019-08-23 07:01:14 +02:00
Dennis Guse 16f18fe16f Removed search suggestion provider as it only provided recent searches. 2019-08-21 22:25:16 +02:00
Dennis Guse 8538c79dd8 Extracted content uri definition. 2019-08-21 22:20:18 +02:00
Dennis Guse f8e3b28e51 Removed landscape mode for TrackDetailActivity as it was broken and portrait mode needs heavy refactoring first. 2019-08-21 07:18:57 +02:00
Dennis Guse 9f665e07fd Show on map: everything is handled by one content provider alone.
To avoid issues forwarding a requests from (temporarily) exported content provider to internal content provider only one content provider is used.
2019-08-20 22:55:32 +02:00
Dennis Guse b7c010c086 Show on map: draft via virtual file (with two content providers).
Exported content provider that temporary needs to grant access to the internal content provider.
2019-08-20 22:49:54 +02:00
Dennis Guse 8296c0194d Show on map: draft via sharing temporary file. 2019-08-20 22:48:05 +02:00
Dennis Guse f537cc7321 Cleanup: automatic code inspection via Android Studio. 2019-08-18 14:46:43 +02:00
Dennis Guse 4fa2401b27 Removed CustomContentProvider.openFile() as it was used for Google Earth integration. 2019-08-18 13:22:41 +02:00
Dennis Guse f30e3cd5cd Removed contentDescription for logo (was still MyTracks). 2019-08-18 13:13:11 +02:00
Dennis Guse b73353120f PreferenceCategory import/export all: now uses Tracks as title. 2019-08-18 11:00:04 +02:00
Dennis Guse 7fa9df1f99 Cleanup. 2019-08-17 00:58:52 +02:00
Dennis Guse ea3de8a987 Renamed SensorManager to RemoteSensorManager. 2019-08-16 21:20:00 +02:00
568 changed files with 12282 additions and 30370 deletions
+4
View File
@@ -0,0 +1,4 @@
# These are supported funding model platforms
liberapay: dennis.guse
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+26
View File
@@ -0,0 +1,26 @@
---
name: Bug report
about: Report issues of OpenTracks features
title: ''
labels: potential bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
If applicable:
* add screenshots
* add logcat output
**Technical information**
- Device: [e.g. Nexus 7 (2013)]
- OS: [e.g. LineageOS 15.1]
- Version [e.g. v3.2.4]
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for OpenTracks
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+10
View File
@@ -0,0 +1,10 @@
# Thanks for your contribution.
**Describe the pull request**
A clear and concise description of what the pull request changes/adds.
**Link to the the issue**
(If available): The link to the issue that this pull request solves.
**License agreement**
By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)).
+6 -2
View File
@@ -73,8 +73,12 @@ captures/
.idea/workspace.xml
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Fastlane
Appfile
releasePlayStore/*
Gemfile.lock
+116
View File
@@ -0,0 +1,116 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>
+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_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="showDecorations" value="true" />
</component>
</project>
+39 -5
View File
@@ -1,12 +1,46 @@
#See https://docs.travis-ci.com/user/languages/android/
#See https://github.com/ankidroid/Anki-Android/blob/master/.travis.yml
language: android
dist: trusty
env:
global:
- ADB_INSTALL_TIMEOUT=8
- ABI=armeabi-v7a
- EMU_FLAVOR=default
# PATH order is incredibly important. e.g. the 'emulator' script exists in more than one place!
- ANDROID_HOME=/usr/local/android-sdk
- TOOLS=${ANDROID_HOME}/tools
- PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH}
matrix:
- API=27 ABI=x86_64
android:
components:
# installing tools to start, then use `sdkmanager` below to get the rest
- tools
- build-tools-28.0.3
- android-28
- android-29
# Emulator Management: Create, Start and Wait
#install:
# - echo y | sdkmanager "platform-tools"
# - echo y | sdkmanager --channel=4 "emulator"
# - echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
# - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
# - emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
# - android-wait-for-emulator
# - adb shell input keyevent 82 &
script:
- ./gradlew build
#TODO: When all tests are passing, CI should also execute them.
#- ./gradlew connectedCheck
- ./gradlew clean build
# - ./gradlew connectedCheck
sbefore_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
- $HOME/.android/build-cache
+3
View File
@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"
+38 -8
View File
@@ -1,21 +1,42 @@
# OpenTracks: a sport tracker
![OpenTracks logo](LOGO.svg)
# <img src="drawable-svg/LOGO.svg" alt="OpenTracks logo" height="40"></img> OpenTracks: a sport tracker
_OpenTracks_ is a sport tracking application that completely respects your privacy.
<a href="https://liberapay.com/dennis.guse/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
<table>
<tr>
<th>Free</th>
<th>Paid</th>
<th>Donations</th>
</tr>
<tr>
<td>
<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>
<a href="https://liberapay.com/dennis.guse/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
</a>
</td>
</tr>
</table>
## Features:
* __Tracking:__ track your sport and outdoor activities
* __Photos and Markers:__ mark interesting locations while tracking
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
* __Heart rate:__ supports bluetooth LE sensors
* __Export data:__ Export tracks either as KML or GPX
* __Voice annoucements__
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
* __No Internet access:__ Internet is not used
* __Voice annoucements__
* __No advertising__
* __Privacy:__
* _contains in-app analytics_
* _does not contain any in-app analytics_
* _does not have Google Play Services_
* _has no cloud integration_
@@ -26,6 +47,15 @@ _Please note:_
_Tracks_ does not contain a _map_ (as this would required Internet access).
To show your tracks on a map, a 3rd party application (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)) needs to be installed.
## Screenshots
<div>
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
</div>
## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
+37
View File
@@ -0,0 +1,37 @@
# OpenTracks: Developer information
## Craft new releases
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
2. Decide on _version name_ (semantic versioning)
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
4. Create changelog (`_version code_.txt`)
5. Create commit with all changes
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
7. Push commits and tags to public repository
8. Add changelog to tag (Github releases)
## F-Droid
Application id: de.dennisguse.opentracks
It if it properly tagged F-Droid takes care of building the app and publishing.
Usually, it takes two days before the update is published (build, signed, and published).
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
## Google Play Store
Application id: de.dennisguse.opentracks.playstore
Requirement: `gem install fastlane`
1. Build signed apk
(requires keystore)
2. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
3. Update store meta data (e.g., description and screenshots)
`fastlane supply --version_code VERSION_CODE --skip_upload_apk`
(use the version code from the manifest)
(requires API key)
## Translations (localization)
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
+31 -17
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
@@ -27,7 +27,7 @@ def getVersionCode = { ->
return Integer.parseInt(stdout.toString().trim())
}
catch (ignored) {
return -1;
return -1
}
}
@@ -35,27 +35,37 @@ def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--dirty'
commandLine 'git', 'describe', '--tags'
standardOutput = stdout
}
return stdout.toString().trim()
exec {
commandLine 'git', 'log', '--format="%h"', '-n 1'
standardOutput = stdout
}
return stdout.toString().replace('\n', '-').replace('"', '').trim()
}
catch (ignored) {
return null;
return null
}
}
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
applicationId "de.dennisguse.opentracks"
manifestPlaceholders = [
applicationLogo: "@drawable/ic_logo_color_24dp",
applicationName: "@strings/app_name"
]
//Useful for debugging.
//versionCode getVersionCode()
//versionName getVersionName()
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdkVersion 21
targetSdkVersion 28
targetSdkVersion 29
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -64,25 +74,30 @@ android {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
manifestPlaceholders = [
applicationLogo: "@drawable/ic_logo_black_24dp",
applicationName: "OpenTracks (Debug)"
]
}
release {
minifyEnabled true
minifyEnabled false
}
releasePlayStore {
applicationIdSuffix ".playstore"
versionNameSuffix "-PlayStore"
}
}
applicationVariants.all { variant ->
variant.resValue "string", "applicationId", variant.applicationId
}
//TODO: Should be removed (when LINT does not throw any errors).
lintOptions {
abortOnError false
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
@@ -94,6 +109,5 @@ dependencies {
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.mockito:mockito-core:3.0.0'
implementation 'com.google.android.material:material:1.0.0'
}
+52
View File
@@ -0,0 +1,52 @@
@startuml
class Track {
-id
-name
-description
-category
-numberOfPoints
-icon
}
Track o-- TrackPoint
Track o-- Waypoint
Track "1" - "1" TripStatistics
class TripStatistics {
- startTime
- stopTime
- totalDistance
- totalTime
- movingTime
- maxSpeed
- totalElevationGain
}
class TrackPoint extends android.location.Location {
- id
- trackId
- longitude
- latitude
- time
- altitude
- accuracy
- speed
- bearing
- sensor_heartrate
- sensor_cadence
- sensor_power
}
class Waypoint {
- id
- trackId
- name
- description
- category
- icon
- length
- duration
- location
- tripStatistics
- photoUrl
}
@enduml
View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="188"
id="svg2"
version="1.1"
viewBox="0 0 78 188"
width="78"
xmlns="http://www.w3.org/2000/svg">
<defs id="defs6" />
<g id="layer1" />
<g
style="display:inline"
id="layer2" />
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
d="m 1,150 38,37 39,-37 H 50 V 97 L 29,81 v 69 z"
id="path31161" />
<path
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
d="M 29,51 V 70 L 50,86 V 67 Z"
id="path31169" />
<path
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
d="M 29,23 V 40 L 50,56 V 39 Z"
id="path31192" />
<path
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
d="M 29,0 V 14 L 50,29 V 16 Z"
id="path31194" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
height="125"
id="svg2"
version="1.1"
viewBox="0 0 125 125"
width="125"
xmlns="http://www.w3.org/2000/svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="ic_button_pause.svg">
<defs id="defs6" />
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<circle
style="fill:none;fill-opacity:1;stroke:#a31a1f;stroke-width:5.03185225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.5"
cy="62.5"
id="path12"
r="59.984074" />
<rect
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273912;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
height="17.940611"
id="rect837"
width="5"
x="54"
y="54.059391" />
<rect
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273865;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
height="17.940611"
id="rect837-3"
width="5"
x="66"
y="54" />
</g>
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
bordercolor="#666666"
borderopacity="1"
gridtolerance="10"
guidetolerance="10"
id="namedview4"
objecttolerance="10"
pagecolor="#ffffff"
showgrid="false"
inkscape:current-layer="layer1"
inkscape:cx="75.030293"
inkscape:cy="72.881196"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-height="1152"
inkscape:window-maximized="0"
inkscape:window-width="2048"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:zoom="5.3400704" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="125"
id="svg2"
version="1.1"
viewBox="0 0 125 125"
width="125"
xmlns="http://www.w3.org/2000/svg">
<defs id="defs6" />
<g id="layer1">
<circle
style="fill:none;fill-opacity:1;stroke:#a31a1f;stroke-width:5.03185225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.5"
cy="62.5"
id="path12"
r="59.984074" />
<circle
style="fill:#414141;fill-opacity:0.4;stroke:none;stroke-width:6.9172821;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.5"
cy="62.5"
id="path835"
r="50" />
<rect
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273912;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
height="17.940611"
id="rect837"
width="5"
x="54"
y="54.059391" />
<rect
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273865;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
height="17.940611"
id="rect837-3"
width="5"
x="66"
y="54" />
</g>
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="125"
id="svg2"
version="1.1"
viewBox="0 0 125 125"
width="125"
xmlns="http://www.w3.org/2000/svg">
<defs id="defs6" />
<g id="layer1">
<circle
style="fill:none;fill-opacity:1;stroke:#a31a1f;stroke-width:5.03185225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.5"
cy="62.5"
id="path12"
r="59.984074" />
<circle
style="fill:#9a191d;fill-opacity:1;stroke:none;stroke-width:6.9172821;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.5"
cy="62.5"
id="path835"
r="50" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:#7e3b3d;stroke-width:1.62162137;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.5"
cy="61.500004"
id="path831"
r="9.189189" />
</g>
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="125"
version="1.1"
viewBox="0 0 125 125"
width="125"
xmlns="http://www.w3.org/2000/svg">
<circle
cx="62.5"
cy="62.5"
fill="none"
r="59.984"
stroke="#a31a1f"
stroke-width="5.0319" />
<circle
cx="62.5"
cy="-62.5"
fill="url(#linearGradient934)"
r="50"
transform="rotate(90)" />
<circle
cx="62.5"
cy="61.5"
fill="#fff"
r="9.1892"
stroke="#7e3b3d"
stroke-width="1.6216" />
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="linearGradient934"
x1="12.5"
x2="112.5"
y1="-62.5"
y2="-62.5">
<stop
offset="0"
stop-color="#721212" />
<stop
offset="1"
stop-color="#a21a1f" />
</linearGradient>
</defs>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="125"
id="svg2"
version="1.1"
viewBox="0 0 125 125"
width="125"
xmlns="http://www.w3.org/2000/svg">
<defs id="defs6">
<linearGradient
id="linearGradient884"
osb:paint="solid">
<stop
style="stop-color:#969696;stop-opacity:1;"
id="stop882"
offset="0" />
</linearGradient>
</defs>
<g id="layer1">
<circle
style="fill:#484848;fill-opacity:1;stroke:none;stroke-width:6.9172821;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.5"
cy="62.5"
id="path835"
r="50" />
<ellipse
style="fill:none;fill-opacity:1;stroke:#555555;stroke-width:5.03194237;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.502243"
cy="63.029659"
id="path12"
rx="59.986229"
ry="59.984074"
transform="matrix(0.99996409,-0.00847427,0,1,0,0)" />
</g>
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#414141;stroke-width:1.04023623;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
height="17.459764"
id="rect844"
width="17.459761"
x="53.020119"
y="54.020119" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="125"
version="1.1"
viewBox="0 0 125 125"
width="125"
xmlns="http://www.w3.org/2000/svg">
<circle
cx="62.5"
cy="62.5"
fill="#2a2a2a"
r="50" />
<ellipse
cx="62.502"
cy="63.03"
fill="none"
rx="59.986"
ry="59.984"
stroke="#2a2a2a"
stroke-width="5.0319"
transform="matrix(.99996 -.0084743 0 1 0 0)" />
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
fill="#555"
height="15"
width="15"
x="54"
y="56" />
</svg>

After

Width:  |  Height:  |  Size: 1022 B

+59
View File
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="125"
version="1.1"
viewBox="0 0 125 125"
width="125"
xmlns="http://www.w3.org/2000/svg">
<circle
cx="62.5"
cy="-62.5"
fill="url(#linearGradient1053)"
r="50"
transform="rotate(90)" />
<defs>
<linearGradient
gradientTransform="translate(0,-125)"
gradientUnits="userSpaceOnUse"
id="linearGradient1053"
x1="12.5"
x2="112.5"
y1="62.5"
y2="62.5">
<stop
offset="0"
stop-color="#404040" />
<stop
offset="1"
stop-color="#555" />
</linearGradient>
</defs>
<ellipse
cx="62.502"
cy="63.03"
fill="none"
rx="59.986"
ry="59.984"
stroke="#555"
stroke-width="5.0319"
transform="matrix(.99996 -.0084743 0 1 0 0)" />
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
fill="#dbdbdb"
height="17.46"
stroke="#414141"
stroke-width="1.0402"
width="17.46"
x="53.02"
y="54.02" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
height="48"
id="svg2"
version="1.1"
viewBox="0 0 48 48"
width="48"
xmlns="http://www.w3.org/2000/svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="ic_track_paused.svg">
<defs id="defs6" />
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="fill:#a31a1f;fill-opacity:1;stroke-width:1.01154423"
height="20"
id="rect12"
width="5"
x="15"
y="14" />
<rect
style="fill:#a31a1f;fill-opacity:1;stroke-width:1.01154423"
height="20"
id="rect12-3"
width="5"
x="27"
y="14" />
<sodipodi:namedview
bordercolor="#666666"
borderopacity="1"
gridtolerance="10"
guidetolerance="10"
id="namedview4"
objecttolerance="10"
pagecolor="#ffffff"
showgrid="true"
inkscape:current-layer="svg2"
inkscape:cx="21.262539"
inkscape:cy="30.645051"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-height="1152"
inkscape:window-maximized="0"
inkscape:window-width="2048"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:zoom="9.8333333">
<inkscape:grid
id="grid16"
type="xygrid" />
</sodipodi:namedview>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
height="48"
id="svg2"
version="1.1"
viewBox="0 0 48 48"
width="48"
xmlns="http://www.w3.org/2000/svg">
<defs id="defs6" />
<g
style="display:inline"
id="layer1">
<path
style="display:inline;fill:#a31a1f;fill-opacity:1;stroke-width:0.03607465"
d="m 22.92317,31.9906 c -0.06948,-0.0081 -0.277422,-0.03235 -0.46209,-0.05384 -2.147889,-0.249888 -4.249982,-1.419727 -5.59965,-3.116269 -0.961167,-1.208194 -1.554689,-2.572853 -1.795388,-4.128058 -0.08907,-0.575498 -0.08782,-1.835731 0.0024,-2.415668 0.223761,-1.438345 0.765805,-2.731082 1.632716,-3.893914 0.353204,-0.473771 1.18254,-1.30549 1.649602,-1.654344 0.947391,-0.707615 1.928391,-1.18098 3.033216,-1.463629 0.649731,-0.166221 1.126538,-0.233843 1.827948,-0.259242 1.94015,-0.07026 3.814111,0.517516 5.397189,1.692835 0.484944,0.360035 1.31879,1.186156 1.663737,1.648325 0.890877,1.193617 1.438199,2.499896 1.661736,3.966023 0.07887,0.517263 0.08854,1.773309 0.01737,2.253423 -0.23912,1.612873 -0.846098,3.039393 -1.810255,4.254461 -0.293648,0.370069 -0.946591,1.022292 -1.317069,1.315618 -1.186276,0.939234 -2.594384,1.549011 -4.122093,1.785059 -0.343493,0.05308 -1.524586,0.09902 -1.779366,0.06922 z"
id="path85" />
</g>
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+38
View File
@@ -0,0 +1,38 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:android)
platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
# sh "your_script.sh"
# You can also use other beta testing services here
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
end
@@ -0,0 +1,9 @@
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
Funktionen & Features:
* zeichnet Sport und andere Outdooraktivitäten auf
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
* zeichnet deine Herzfrequenz auf (unterstützt Bluetooth-LE-Sensoren)
* exportiert deine Daten als KML oder GPX
* funktioniert ganz ohne Internet
@@ -0,0 +1 @@
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
@@ -0,0 +1,15 @@
v3.0.0: OpenTracks
First release of OpenTracks (i.e., hard fork from MyTracks)
Changes:
- Rebranded from MyTracks to OpenTracks
- Added Bluetooth LE heart rate sensor
- Removed all features requiring Internet access
- Removed Google Maps integration
- Removed use of Google Play services
- Removed ANT+ sensors
- Removed Bluetooth classic sensors
- Removed Protobuf
- Removed calorie estimation and activity estimation
- Removed MyTracks Service API (i.e., no integration with other apps)
@@ -0,0 +1,17 @@
v3.0.1: OpenTracks
Changes:
- Share markers
- Restructured settings
- Removed actionbar back button
- Export: removed photo size settings option
- Export: KML as format is now selectable
Bugfixes:
- export directory is now /OpenTracks/
- landscape mode is not crashing
- fixed KML/KMZ export
Developer:
- Test are executable (but many failing)
- Enabled continuous integration with TravisCI (no tests)
@@ -0,0 +1,14 @@
v3.1.0: OpenTracks
Changes:
- Added show track in map apps (tested with OsmAnd and Maps.ME)
- Added while tracking, show track statistics without unlocking the device
- Removed landscape mode for track detail view (was buggy)
- Removed search suggestions history
Bugfix:
- Request storage permission for import all/export all
Developer:
- Upgrade to Gradle 5.6
- Do not minify release builds
@@ -0,0 +1,16 @@
v3.2.0: OpenTracks
Changes:
- Removed Widget
- Introduced material design icons
- Removed snowboarding and skiing (need new icons)
Bugfix:
- Fixed search functionality
Developer:
- Fixed (some) test cases
- Removed unused parcelable functionality
- Major cleanup and reformatting
- Do not use broadcast for app to widget communication
- stats_content.xml does not include other files
@@ -0,0 +1,7 @@
v3.2.1: OpenTracks
Changes:
- Settings are now on a single page
Developer:
- Settings moved to Androidx compat (uses fragments)
@@ -0,0 +1,13 @@
v3.2.2: OpenTracks
Changes:
- Removed aggregated stats
- Migrated to Material Design
Bugfix:
- Fixed export of sensor data
- Fixed import of incomplete sensor data
Developer:
- Replaced actionbar with toolbar
- Migrated to androidx appcompat (incl. Material Design)
@@ -0,0 +1,14 @@
v3.2.3: OpenTracks
Changes:
- Sharing: use Android Sharesheet
- Sharing: share multiple tracks as individual files
- Use Android ShareSheet
- Heart rate sensor (Bluetooth LE): automatic reconnect
Bugfix:
- Heart rate sensor (Bluetooth LE): disconnect properly
- Heart rate wasn't always shown while recording
Developer:
- Enabled TravisCI instrumentation tests
@@ -0,0 +1,5 @@
v3.2.4: OpenTracks
Bugfix:
- Settings activity crashes
Removed possibility to change voice/split frequency from the context menu
@@ -0,0 +1,15 @@
v3.2.5: OpenTracks
Changes:
- Disable activity change
- Add permission for Android 10: recording location data
Bugfix:
- Fix reset settings
Developer:
- Preferences
- if Settings cannot be loaded reset all preferences
- moved access to preferences to PreferencesUtil
- moved default of preferences from code to xml
- use SummaryProvider
@@ -0,0 +1,8 @@
v3.2.6: OpenTracks
Changes:
- Heart rate monitor: scan for available devices
- Remove shortcut to Bluetooth settings
Developer:
- Preferences: moved default of preferences from code to xml
@@ -0,0 +1,10 @@
v3.2.7: OpenTracks
Changes:
- Notification show GPS accuracy
- Show heads-up notification if GPS accuracy drops beyond threshold
- Always show chart by time as well as by distance
- Added help (in Settings)
Bugfix:
- TrackDetailActivity: disabled non-working landscape mode (temporary)
@@ -0,0 +1,10 @@
v3.2.8: OpenTracks
Changes:
- Chart: zoom via gestures
Bugfixes:
- Export via KMZ did not export picture filenames correctly
- Sharing multiple tracks did not include KMZ files
- MarkerDetailActivity's UI was not shown
- Settings crashed if preference of heart rate was not initialized
@@ -0,0 +1,10 @@
v3.2.9: OpenTracks
Changes:
- Option to keep screen on while recording
- Markers: removed split markers completely
- Voice announcement now work without READ_PHONE_STATE permission
Bugfixes:
- Statistics: heart rate was not shown
- Statistics: time was briefly reset to 0s
@@ -0,0 +1,11 @@
v3.2.10: OpenTracks
Changes:
- ChartView is not configurable anymore
- After reboot: removed automatic restart of an unfinished recording
Bugfix:
- Speech announcements use MAY_DUCK (again)
- ChartView shows markers (again)
- Fixed landscape layout of TrackListActivity
- Request legacy file API behavior (necessary for Android 10)
@@ -0,0 +1,12 @@
v3.2.11: OpenTracks
Bug:
- Max speed is identical to avg speed
Bugfix:
- Crash on heart rate sensor selection
Developer:
- Replaced TabHost by TabsLayout
- Removed special first waypoint
- Removed duplicate translations
@@ -0,0 +1,13 @@
v3.3.0: OpenTracks
Breaking changes:
- Pictures are now stored in app-private storage (do not show up other picture apps)
NOTE: Pictures taking with previous version cannot be shown
- Localization for Spanish (Latin America) only works on Android 7.0 or later
Changes:
- Import/Export via Storage Access Framework (avoids READ/WRITE_EXTERNAL_STORAGE permission)
Developer:
- Cleaned internal data structures
- Use separate icons/app names for debug version
@@ -0,0 +1,10 @@
(tbd): OpenTracks
Changes:
-
Bugfix:
-
Developer:
-
@@ -3,6 +3,6 @@ OpenTracks is a sport tracking application that completely respects your privacy
Features:
* Sport tracking: track your sport and outdoor activities
* Photos and markers: mark interesting locations while tracking
* Heart rate: supports bluetooth LE sensors
* Export data: Export tracks either as KML or GPX
* Heart rate: supports Bluetooth LE sensors
* Export data: export tracks either as KML or GPX
* No Internet access: Internet is not used
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

@@ -0,0 +1 @@
A sport tracker that completely respects your privacy.
@@ -0,0 +1 @@
OpenTracks
@@ -0,0 +1,8 @@
OpenTracks es un tracker deportivo que respeta tu privacidad.
Funciones:
* Tracker deportivo: haz un seguimiento de tus actividades al aire libre
* Marca lugares de interés, incluso agrega fotos a los marcadores
* Pulsómetro (compatible con dispositivos Bluetooth LE)
* Exporta tus rutas como KML o GPX
* Completamente offline, no necesitas internet
@@ -0,0 +1 @@
OpenTracks es un tracker deportivo que respeta tu privacidad.
@@ -0,0 +1 @@
OpenTracks
@@ -0,0 +1,9 @@
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
Fonctionnalités :
* Suivi d'activité : enregistre vos activités sportives et de plein air
* Photos et repères : ajout de photos et de repères pendant l'enregistrement de l'activité
* Rythme cardiaque : support des capteurs Bluetooth LE
* Export : export des traces au format KML ou GPX
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
@@ -0,0 +1 @@
OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
Binary file not shown.
+1 -2
View File
@@ -1,6 +1,5 @@
#Wed Jun 19 22:47:37 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
Vendored
+74 -46
View File
@@ -1,4 +1,20 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#
##############################################################################
##
@@ -6,42 +22,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -60,6 +40,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -85,7 +105,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -105,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -150,11 +170,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+20 -10
View File
@@ -1,3 +1,19 @@
@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
@@ -8,14 +24,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@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=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@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
@@ -46,10 +62,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +75,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
-1
View File
@@ -1 +0,0 @@
A sport tracker that completely respects your privacy
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.
Binary file not shown.
@@ -1,101 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.services.TrackRecordingService;
/**
* Tests for the BootReceiver.
*
* @author Youtao Liu
*/
@RunWith(AndroidJUnit4.class)
public class BootReceiverTest {
private static final String SERVICE_NAME = "de.dennisguse.opentracks.services.TrackRecordingService";
private Context context = InstrumentationRegistry.getInstrumentation().getContext();
/**
* Tests the behavior when receive notification which is the phone boot.
*/
@Test
public void testOnReceive_startService() {
// Make sure no TrackRecordingService
Intent stopIntent = new Intent(context, TrackRecordingService.class);
context.stopService(stopIntent);
Assert.assertFalse(isServiceExisted(context));
BootReceiver bootReceiver = new BootReceiver();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_BOOT_COMPLETED);
bootReceiver.onReceive(context, intent);
// Check if the service is started
Assert.assertFalse(isServiceExisted(context));
}
/**
* Tests the behavior when receive notification which is not the phone boot.
*/
@Test
public void testOnReceive_noStartService() {
// Make sure no TrackRecordingService
Intent stopIntent = new Intent(context, TrackRecordingService.class);
context.stopService(stopIntent);
Assert.assertFalse(isServiceExisted(context));
BootReceiver bootReceiver = new BootReceiver();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_BUG_REPORT);
bootReceiver.onReceive(context, intent);
// Check if the service is not started
Assert.assertFalse(isServiceExisted(context));
}
/**
* Checks if a service is started in a context.
*
* @param context the context for checking a service
*/
private boolean isServiceExisted(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
for (int i = 0; i < serviceList.size(); i++) {
RunningServiceInfo serviceInfo = serviceList.get(i);
ComponentName componentName = serviceInfo.service;
if (componentName.getClassName().equals(SERVICE_NAME)) {
return true;
}
}
return false;
}
}
@@ -18,13 +18,12 @@ package de.dennisguse.opentracks;
import android.location.Location;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
* Commons utilities for creating stubs of track, location. The class will be
* enriched if needs more similar stubs for test.
* Commons utilities for creating stubs of track, location.
*
* @author Youtao Liu
*/
@@ -50,8 +49,7 @@ public class TrackStubUtils {
public static Track createTrack(int numberOfLocations) {
Track track = new Track();
for (int i = 0; i < numberOfLocations; i++) {
track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE
+ i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
}
return track;
@@ -62,16 +60,16 @@ public class TrackStubUtils {
*
* @return a track stub.
*/
public static SensorDataSetLocation createSensorDataSetLocation() {
public static TrackPoint createSensorDataSetLocation() {
return createSensorDataSetLocation(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE);
}
/**
* Creates a {@link SensorDataSetLocation} stub with specified values.
* Creates a {@link TrackPoint} stub with specified values.
*
* @return a SensorDataSetLocation stub.
*/
public static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, double altitude) {
private static TrackPoint createSensorDataSetLocation(double latitude, double longitude, double altitude) {
// Initial Location
Location loc = new Location(LOCATION_PROVIDER);
loc.setLatitude(latitude);
@@ -83,7 +81,6 @@ public class TrackStubUtils {
loc.setBearing(INITIAL_BEARING);
SensorDataSet sd = new SensorDataSet(Float.NaN, Float.NaN);
return new SensorDataSetLocation(loc, sd);
return new TrackPoint(loc, sd);
}
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
package de.dennisguse.opentracks.chart;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -23,6 +23,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
/**
* Tests {@link ChartValueSeries}.
*
@@ -27,6 +27,9 @@ import org.junit.Before;
import org.junit.Test;
import de.dennisguse.opentracks.content.CustomContentProvider.DatabaseHelper;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
/**
* Tests {@link CustomContentProvider}.
@@ -45,7 +48,8 @@ public class CustomContentProviderTest {
public void setUp() {
context.deleteDatabase(DATABASE_NAME);
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
customContentProvider = new CustomContentProvider();
customContentProvider = new CustomContentProvider() {
};
}
/**
@@ -16,6 +16,7 @@
package de.dennisguse.opentracks.content;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -32,34 +33,33 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TripStatistics;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* A unit test for {@link ContentProviderUtilsImpl}.
* A unit test for {@link ContentProviderUtils}.
*
* @author Bartlomiej Niechwiej
* @author Youtao Liu
*/
@RunWith(MockitoJUnitRunner.class)
public class CustomContentProviderUtilsImplTest {
public class CustomContentProviderUtilsTest {
private static final String NAME_PREFIX = "test name";
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
private static final String TEST_DESC = "Test Desc!";
private static final String TEST_DESC_NEW = "Test Desc new!";
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils;
private double INITIAL_LATITUDE = 37.0;
private double INITIAL_LONGITUDE = -57.0;
private double ALTITUDE_INTERVAL = 2.5;
private ContentProviderUtils contentProviderUtils;
@Mock
private Cursor cursorMock;
@@ -69,13 +69,13 @@ public class CustomContentProviderUtilsImplTest {
@Before
public void setUp() {
providerUtils = ContentProviderUtils.Factory.get(context);
providerUtils.deleteAllTracks(context);
contentProviderUtils = new ContentProviderUtils(context);
contentProviderUtils.deleteAllTracks(context);
}
@Test
public void testLocationIterator_noPoints() {
testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(1, 0, 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
}
@Test
@@ -105,39 +105,38 @@ public class CustomContentProviderUtilsImplTest {
@Test
public void testLocationIterator_noBatchAscending() {
testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 100, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_noBatchDescending() {
testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 100, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 11, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_batchDescending() {
testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 11, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_largeTrack() {
testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(1, 20000, 2000, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
}
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
long lastPointId = initializeTrack(trackId, numPoints);
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
((ContentProviderUtils) contentProviderUtils).setDefaultCursorBatchSize(batchSize);
List<Location> locations = new ArrayList<Location>(numPoints);
LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory);
try {
try (LocationIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
while (it.hasNext()) {
Location loc = it.next();
Assert.assertNotNull(loc);
@@ -147,8 +146,6 @@ public class CustomContentProviderUtilsImplTest {
: lastPointId - numPoints + locations.size(), it.getLocationId());
}
Assert.assertEquals(numPoints, locations.size());
} finally {
it.close();
}
return locations;
}
@@ -158,8 +155,8 @@ public class CustomContentProviderUtilsImplTest {
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
providerUtils.insertTrack(track);
track = providerUtils.getTrack(id);
contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
Location[] locations = new Location[numPoints];
@@ -171,20 +168,17 @@ public class CustomContentProviderUtilsImplTest {
loc.setAltitude(i * 2.5);
locations[i] = loc;
}
providerUtils.bulkInsertTrackPoint(locations, numPoints, id);
contentProviderUtils.bulkInsertTrackPoint(locations, numPoints, id);
// Load all inserted locations.
long lastPointId = -1;
int counter = 0;
LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
try {
try (LocationIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
while (it.hasNext()) {
it.next();
lastPointId = it.getLocationId();
counter++;
}
} finally {
it.close();
}
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
@@ -195,7 +189,7 @@ public class CustomContentProviderUtilsImplTest {
}
/**
* Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}.
* Tests the method {@link ContentProviderUtils#createTrack(Cursor)}.
*/
@Test
public void testCreateTrack() {
@@ -216,23 +210,22 @@ public class CustomContentProviderUtilsImplTest {
String name = NAME_PREFIX + trackId;
when(cursorMock.getString(columnIndex++)).thenReturn(name);
Track track = providerUtils.createTrack(cursorMock);
Track track = contentProviderUtils.createTrack(cursorMock);
Assert.assertEquals(trackId, track.getId());
Assert.assertEquals(name, track.getName());
verify(cursorMock);
}
/**
* Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)}
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
*/
@Test
public void testDeleteAllTracks() {
// Insert track, points and waypoint at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
Waypoint waypoint = new Waypoint();
providerUtils.insertWaypoint(waypoint);
contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(1, tracksCursor.getCount());
@@ -241,7 +234,7 @@ public class CustomContentProviderUtilsImplTest {
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
// Delete all.
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
// Check whether all have been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(0, tracksCursor.getCount());
@@ -252,104 +245,97 @@ public class CustomContentProviderUtilsImplTest {
}
/**
* Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}.
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
*/
@Test
public void testDeleteTrack() {
// Insert three tracks, points of two tracks and way point of one track.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track);
insertTrackWithLocations(getTrack(trackId + 1, 10));
insertTrackWithLocations(getTrack(trackId + 2, 10));
contentProviderUtils.insertTrack(track);
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 1, 10));
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 2, 10));
Waypoint waypoint = new Waypoint();
waypoint.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint);
contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null,
TracksColumns._ID);
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(3, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null,
null, TrackPointsColumns._ID);
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(20, tracksPointsCursor.getCount());
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null,
null, WaypointsColumns._ID);
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
// Delete one track.
providerUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, trackId);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null,
TracksColumns._ID);
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null,
null, TrackPointsColumns._ID);
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(20, tracksPointsCursor.getCount());
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null,
null, WaypointsColumns._ID);
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(0, waypointCursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getAllTracks()}
* Tests the method {@link ContentProviderUtils#getAllTracks()}
*/
@Test
public void testGetAllTracks() {
int initialTrackNumber = providerUtils.getAllTracks().size();
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
List<Track> allTracks = providerUtils.getAllTracks();
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
List<Track> allTracks = contentProviderUtils.getAllTracks();
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getLastTrack()}
* Tests the method {@link ContentProviderUtils#getLastTrack()}
*/
@Test
public void testGetLastTrack() {
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
Assert.assertEquals(trackId, providerUtils.getLastTrack().getId());
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getTrack(long)}
* Tests the method {@link ContentProviderUtils#getTrack(long)}
*/
@Test
public void testGetTrack() {
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
Assert.assertNotNull(providerUtils.getTrack(trackId));
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
}
/**
* Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)}
* Tests the method {@link ContentProviderUtils#updateTrack(Track)}
*/
@Test
public void testUpdateTrack() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 0);
Track track = TestDataUtil.getTrack(trackId, 0);
String nameOld = "name1";
String nameNew = "name2";
track.setName(nameOld);
providerUtils.insertTrack(track);
Assert.assertEquals(nameOld, providerUtils.getTrack(trackId).getName());
contentProviderUtils.insertTrack(track);
Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
track.setName(nameNew);
providerUtils.updateTrack(track);
Assert.assertEquals(nameNew, providerUtils.getTrack(trackId).getName());
contentProviderUtils.updateTrack(track);
Assert.assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}.
* Tests the method {@link ContentProviderUtils#createContentValues(Waypoint)}.
*/
@Test
public void testCreateContentValues_waypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
// Bottom
long startTime = 1000L;
// AverageSpeed
@@ -366,22 +352,23 @@ public class CustomContentProviderUtilsImplTest {
statistics.setMinElevation(1200.0);
statistics.setMaxGrade(15.0);
statistics.setMinGrade(minGrade);
statistics.setBounds(-10000, 20000, 30000, -40000);
track.setTripStatistics(statistics);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC);
waypoint.setType(WaypointType.STATISTICS);
waypoint.setTripStatistics(statistics);
Location loc = new Location("test");
loc.setLatitude(22);
loc.setLongitude(22);
loc.setAccuracy((float) 1 / 100.0f);
loc.setAltitude(2.5);
waypoint.setLocation(loc);
providerUtils.insertWaypoint(waypoint);
Location location = new Location("test");
location.setLatitude(22);
location.setLongitude(22);
location.setAccuracy((float) 1 / 100.0f);
location.setAltitude(2.5);
waypoint.setLocation(location);
contentProviderUtils.insertWaypoint(waypoint);
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
long waypointId = System.currentTimeMillis();
waypoint.setId(waypointId);
@@ -389,12 +376,10 @@ public class CustomContentProviderUtilsImplTest {
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
Assert.assertEquals(22 * 1000000, contentValues.get(WaypointsColumns.LONGITUDE));
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
Assert.assertEquals(startTime, contentValues.get(WaypointsColumns.STARTTIME));
Assert.assertEquals(minGrade, contentValues.get(WaypointsColumns.MINGRADE));
}
/**
* Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}.
* Tests the method {@link ContentProviderUtils#createWaypoint(Cursor)}.
*/
@Test
public void testCreateWaypoint() {
@@ -421,60 +406,44 @@ public class CustomContentProviderUtilsImplTest {
long trackId = 11L;
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
Waypoint waypoint = providerUtils.createWaypoint(cursorMock);
Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
Assert.assertEquals(id, waypoint.getId());
Assert.assertEquals(name, waypoint.getName());
Assert.assertEquals(trackId, waypoint.getTrackId());
verify(cursorMock);
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* when there is only one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_onlyOneWayPoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint1 = new Waypoint();
waypoint1.setDescription(TEST_DESC);
waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
providerUtils.insertWaypoint(waypoint1);
contentProviderUtils.insertWaypoint(waypoint1);
// Delete
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
contentProviderUtils.deleteWaypoint(context, 1);
@Override
public String generateWaypointDescription(TripStatistics tripStatistics) {
return CustomContentProviderUtilsImplTest.MOCK_DESC;
}
@Override
public String generateTrackDescription(Track aTrack, Vector<Double> distances,
Vector<Double> elevations, boolean html) {
return null;
}
};
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
Assert.assertNull(providerUtils.getWaypoint(1));
Assert.assertNull(contentProviderUtils.getWaypoint(1));
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* when there is more than one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_hasNextWayPoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
TripStatistics statistics = new TripStatistics();
statistics.setStartTime(1000L);
@@ -488,177 +457,139 @@ public class CustomContentProviderUtilsImplTest {
statistics.setMinElevation(1200.0);
statistics.setMaxGrade(15.0);
statistics.setMinGrade(-25.0);
statistics.setBounds(-10000, 20000, 30000, -40000);
track.setTripStatistics(statistics);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint1 = new Waypoint();
waypoint1.setDescription(Long.toString(trackId));
waypoint1.setDescription(MOCK_DESC);
waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setTripStatistics(statistics);
providerUtils.insertWaypoint(waypoint1);
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint waypoint2 = new Waypoint();
waypoint2.setDescription(Long.toString(trackId));
waypoint2.setDescription(MOCK_DESC);
waypoint2.setTrackId(trackId);
waypoint2.setType(WaypointType.STATISTICS);
waypoint2.setTripStatistics(statistics);
providerUtils.insertWaypoint(waypoint2);
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
// Delete
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
@Override
public String generateWaypointDescription(TripStatistics tripStatistics) {
return CustomContentProviderUtilsImplTest.MOCK_DESC;
}
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
@Override
public String generateTrackDescription(Track aTrack, Vector<Double> distances,
Vector<Double> elevations, boolean html) {
return null;
}
};
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
Assert.assertNull(providerUtils.getWaypoint(1));
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(2)
.getDescription());
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}.
*/
@Test
public void testGetFirstWaypointId() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint();
waypoint1.setTrackId(trackId);
Waypoint waypoint2 = new Waypoint();
waypoint2.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint2);
Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1));
Assert.assertEquals(1L, providerUtils.getFirstWaypointId(trackId));
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}.
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}.
*/
@Test
public void testGetNextWaypointNumber() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint();
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setTrackId(trackId);
Waypoint waypoint2 = new Waypoint();
waypoint2.setType(WaypointType.WAYPOINT);
waypoint2.setTrackId(trackId);
Waypoint waypoint3 = new Waypoint();
waypoint3.setType(WaypointType.STATISTICS);
waypoint3.setTrackId(trackId);
Waypoint waypoint4 = new Waypoint();
waypoint4.setType(WaypointType.WAYPOINT);
waypoint4.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint2);
providerUtils.insertWaypoint(waypoint3);
providerUtils.insertWaypoint(waypoint4);
contentProviderUtils.insertWaypoint(waypoint1);
contentProviderUtils.insertWaypoint(waypoint2);
contentProviderUtils.insertWaypoint(waypoint3);
contentProviderUtils.insertWaypoint(waypoint4);
Assert.assertEquals(2, providerUtils.getNextWaypointNumber(trackId, WaypointType.STATISTICS));
Assert.assertEquals(3, providerUtils.getNextWaypointNumber(trackId, WaypointType.WAYPOINT));
Assert.assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
}
/**
* Tests the method
* {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}.
* {@link ContentProviderUtils#getLastWaypoint(long)}.
*/
@Test
public void testGetLastStatisticsWaypoint() {
public void testGetLastWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint();
waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setDescription("Desc1");
Waypoint waypoint2 = new Waypoint();
waypoint2.setTrackId(trackId);
waypoint2.setType(WaypointType.STATISTICS);
waypoint2.setDescription("Desc2");
Waypoint waypoint3 = new Waypoint();
waypoint3.setTrackId(trackId);
waypoint3.setType(WaypointType.WAYPOINT);
waypoint3.setDescription("Desc3");
providerUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint2);
providerUtils.insertWaypoint(waypoint3);
contentProviderUtils.insertWaypoint(waypoint1);
contentProviderUtils.insertWaypoint(waypoint2);
contentProviderUtils.insertWaypoint(waypoint3);
Assert.assertEquals("Desc2", providerUtils.getLastWaypoint(trackId, WaypointType.STATISTICS).getDescription());
Assert.assertEquals("Desc3", contentProviderUtils.getLastWaypoint(trackId).getDescription());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and
* {@link ContentProviderUtilsImpl#getWaypoint(long)}.
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
* {@link ContentProviderUtils#getWaypoint(long)}.
*/
@Test
public void testInsertAndGetWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(1).getDescription());
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}.
* Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}.
*/
@Test
public void testUpdateWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint);
// Update
waypoint = providerUtils.getWaypoint(1);
waypoint.setDescription(TEST_DESC_NEW);
providerUtils.updateWaypoint(waypoint);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(1).getDescription());
// Update
waypoint = contentProviderUtils.getWaypoint(waypointId);
waypoint.setDescription(TEST_DESC_NEW);
contentProviderUtils.updateWaypoint(waypoint);
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], int, long)}.
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(Location[], int, long)}.
*/
@Test
public void testBulkInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
Assert.assertEquals(20, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
Assert.assertEquals(28, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}.
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
*/
@Test
public void testCreateTrackPoint() {
@@ -709,193 +640,146 @@ public class CustomContentProviderUtilsImplTest {
byte[] sensor = "Sensor state".getBytes();
when(cursorMock.getBlob(index++)).thenReturn(sensor);
Location location = providerUtils.createTrackPoint(cursorMock);
Location location = contentProviderUtils.createTrackPoint(cursorMock);
Assert.assertEquals((double) longitude, location.getLongitude(), 0.01);
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
Assert.assertEquals(time, location.getTime(), 0.01);
Assert.assertEquals(speed, location.getSpeed(), 0.01);
verify(cursorMock);
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}.
* {@link ContentProviderUtils#insertTrackPoint(Location, long)}.
*/
@Test
public void testInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
providerUtils.insertTrackPoint(createLocation(22), trackId);
Assert.assertEquals(11, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.insertTrackPoint(TestDataUtil.createLocation(22), trackId);
Assert.assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}.
*/
@Test
public void testGetFirstTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Assert.assertEquals(1, providerUtils.getFirstTrackPointId(trackId));
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}.
*/
@Test
public void testGetLastTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Assert.assertEquals(10, providerUtils.getLastTrackPointId(trackId));
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}.
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}.
*/
@Test
public void testGetLastValidTrackPoint() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
Location lastLocation = providerUtils.getLastValidTrackPoint(trackId);
Location lastLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
checkLocation(9, lastLocation);
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
* in descending.
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending.
*/
@Test
public void testGetTrackPointCursor_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, true);
long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
Assert.assertEquals(2, cursor.getCount());
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
* in ascending.
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending.
*/
@Test
public void testGetTrackPointCursor_asc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, false);
Assert.assertEquals(5, cursor.getCount());
long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
Assert.assertEquals(2, cursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
*/
@Test
public void testGetTrackPointLocationIterator_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long startTrackPointId = 2L;
long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
for (int i = 1; i >= 0; i--) {
long startTrackPointId = trackpointIds[9];
LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next();
Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId());
checkLocation(i, location);
Assert.assertEquals(startTrackPointId - i, locationIterator.getLocationId());
checkLocation((trackpointIds.length - 1) - i, location);
}
Assert.assertFalse(locationIterator.hasNext());
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)}
* in ascending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
*/
@Test
public void testGetTrackPointLocationIterator_asc() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long startTrackPointId = 2L;
long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
long startTrackPointId = trackpointIds[0];
for (int i = 1; i < 10; i++) {
LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next();
Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId());
Assert.assertEquals(startTrackPointId + i, locationIterator.getLocationId());
checkLocation(i, location);
}
Assert.assertFalse(locationIterator.hasNext());
}
/**
* Simulates a track which is used for testing.
*
* @param id the id of the track
* @param numPoints the location number in the track
* @return the simulated track
*/
private Track getTrack(long id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
for (int i = 0; i < numPoints; i++) {
track.addLocation(createLocation(i));
}
return track;
}
/**
* Creates a location.
*
* @param i the index to set the value of location.
* @return created location
*/
private Location createLocation(int i) {
Location loc = new Location("test");
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * ALTITUDE_INTERVAL);
return loc;
}
/**
* Checks the value of a location.
*
* @param i the index of this location which created in the method
* {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
* @param i the index of this location which created in the method {@link TestDataUtil#getTrack(long, int)}
* @param location the location to be checked
*/
private void checkLocation(int i, Location location) {
Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
Assert.assertEquals(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
Assert.assertEquals(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
Assert.assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
}
/**
@@ -904,7 +788,7 @@ public class CustomContentProviderUtilsImplTest {
* @param track track to be inserted
*/
private void insertTrackWithLocations(Track track) {
providerUtils.insertTrack(track);
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
contentProviderUtils.insertTrack(track);
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
}
}
@@ -27,29 +27,30 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
/**
* Tests for {@link DescriptionGeneratorImpl}.
* Tests for {@link DescriptionGenerator}.
*
* @author Jimmy Shih
*/
@RunWith(AndroidJUnit4.class)
public class DescriptionGeneratorImplTest {
public class DescriptionGeneratorTest {
private static final long START_TIME = 1288721514000L;
private DescriptionGeneratorImpl descriptionGenerator;
private DescriptionGenerator descriptionGenerator;
private Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
descriptionGenerator = new DescriptionGeneratorImpl(ApplicationProvider.getApplicationContext());
descriptionGenerator = new DescriptionGenerator(ApplicationProvider.getApplicationContext());
}
/**
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, java.util.Vector, java.util.Vector, boolean)}.
* Tests {@link DescriptionGenerator#generateTrackDescription(Track, boolean)}.
*/
@Test
public void testGenerateTrackDescription() {
@@ -68,7 +69,7 @@ public class DescriptionGeneratorImplTest {
track.setTripStatistics(stats);
track.setCategory("hiking");
String expected = //"Created by"
"<a href='https://github.com/dennisguse/opentracks'>OpenTracks</a><p>"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
+ "Name: -<br>"
+ "Activity type: hiking<br>"
+ "Description: -<br>"
@@ -88,48 +89,12 @@ public class DescriptionGeneratorImplTest {
+ "Min grade: 11 %<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, null, null, true));
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
/**
* Tests {@link DescriptionGeneratorImpl#generateWaypointDescription(TripStatistics)}.
*/
@Test
public void testGenerateWaypointDescription() {
Waypoint waypoint = new Waypoint();
TripStatistics stats = new TripStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setMaxElevation(550);
stats.setMinElevation(-500);
stats.setTotalElevationGain(6000);
stats.setMaxGrade(0.42);
stats.setMinGrade(0.11);
stats.setStartTime(START_TIME);
waypoint.setTripStatistics(stats);
String expected = "Total distance: 20.00 km (12.4 mi)\n"
+ "Total time: 10:00\n"
+ "Moving time: 05:00\n"
+ "Average speed: 120.00 km/h (74.6 mi/h)\n"
+ "Average moving speed: 240.00 km/h (149.1 mi/h)\n"
+ "Max speed: 360.00 km/h (223.7 mi/h)\n"
+ "Average pace: 0:30 min/km (0:48 min/mi)\n"
+ "Average moving pace: 0:15 min/km (0:24 min/mi)\n"
+ "Fastest pace: 0:10 min/km (0:16 min/mi)\n"
+ "Max elevation: 550 m (1804 ft)\n"
+ "Min elevation: -500 m (-1640 ft)\n"
+ "Elevation gain: 6000 m (19685 ft)\n"
+ "Max grade: 42 %\n"
+ "Min grade: 11 %\n"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "\n";
Assert.assertEquals(expected, descriptionGenerator.generateWaypointDescription(waypoint.getTripStatistics()));
}
/**
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder,
* int, String)}.
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteDistance() {
@@ -139,8 +104,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int,
* String)}.
* Tests {@link DescriptionGenerator#writeTime(long, StringBuilder, int, String)}.
*/
@Test
public void testWriteTime() {
@@ -150,8 +114,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder,
* int, String)}.
* Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteSpeed() {
@@ -161,8 +124,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder,
* int, String)}.
* Tests {@link DescriptionGenerator#writeElevation(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteElevation() {
@@ -172,8 +134,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int,
* String)}.
* Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}.
*/
@Test
public void testWritePace() {
@@ -183,8 +144,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
* int, String)}.
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteGrade() {
@@ -194,8 +154,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
* int, String)} with a NaN.
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with a NaN.
*/
@Test
public void testWriteGrade_nan() {
@@ -205,8 +164,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
* int, String)} with an infinite number.
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with an infinite number.
*/
@Test
public void testWriteGrade_infinite() {
@@ -36,13 +36,14 @@ import java.util.List;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* 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)
* 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
*/
@@ -50,7 +51,7 @@ import de.dennisguse.opentracks.stats.TripStatistics;
public class SearchEngineTest {
private static final Location HERE = new Location("gps");
private static final long NOW = 1234567890000L; // After OLDEST_ALLOWED_TIMESTAMP
private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000; // After OLDEST_ALLOWED_TIMESTAMP
private ContentProviderUtils providerUtils;
private SearchEngine engine;
@@ -59,7 +60,7 @@ public class SearchEngineTest {
@Before
public void setUp() {
providerUtils = ContentProviderUtils.Factory.get(context);
providerUtils = new ContentProviderUtils(context);
engine = new SearchEngine(providerUtils);
}
@@ -81,10 +82,6 @@ public class SearchEngineTest {
stats.setStopTime(NOW);
}
int latitude = (int) ((HERE.getLatitude() + distance) * 1E6);
int longitude = (int) ((HERE.getLongitude() + distance) * 1E6);
stats.setBounds(latitude, longitude, latitude, longitude);
Uri uri = providerUtils.insertTrack(track);
return ContentUris.parseId(uri);
}
@@ -210,20 +207,6 @@ public class SearchEngineTest {
assertWaypointResult(descriptionWaypointId, results.get(2));
}
@Test
public void testSearchTrackDistance() {
// All results match text, but they're at difference distances from the user.
long farFarAwayId = insertTrack("aa", 0.3);
long nearId = insertTrack("ab", 0.1);
long farId = insertTrack("ac", 0.2);
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
// Distance order.
assertTrackResults(results, nearId, farId, farFarAwayId);
}
@Test
public void testSearchWaypointDistance() {
// All results match text, but they're at difference distances from the user.
@@ -1,761 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.location.Location;
import androidx.test.core.app.ApplicationProvider;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link TrackDataHub}.
*
* @author Rodrigo Damazio
*/
@RunWith(MockitoJUnitRunner.class)
public class TrackDataHubTest {
private static final long TRACK_ID = 42L;
private static final int TARGET_POINTS = 50;
private Context context = ApplicationProvider.getApplicationContext();
private SharedPreferences sharedPreferences;
@Mock
private ContentProviderUtils contentProviderUtils;
@Mock
private DataSource dataSource;
private TrackDataHub trackDataHub;
@Mock
private TrackDataListener trackDataListener1;
@Mock
private TrackDataListener trackDataListener2;
private ArgumentCaptor<OnSharedPreferenceChangeListener> preferenceChangeListenerCapture = ArgumentCaptor.forClass(OnSharedPreferenceChangeListener.class);
@Before
public void setUp() {
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
trackDataHub = new TrackDataHub(context, new TrackDataManager(), contentProviderUtils, TARGET_POINTS) {
@Override
protected DataSource newDataSource() {
return dataSource;
}
@Override
protected void runInHandlerThread(Runnable runnable) {
// Run everything in the same thread
runnable.run();
}
};
PreferencesUtils.setLong(context, R.string.recording_track_id_key, TRACK_ID);
}
@After
public void tearDown() {
reset(dataSource);
// Expect everything to be unregistered.
dataSource.unregisterContentObserver(isA(ContentObserver.class));
//TODO
// AndroidMock.expectLastCall().times(3);
dataSource.unregisterOnSharedPreferenceChangeListener(isA(OnSharedPreferenceChangeListener.class));
verify(dataSource);
trackDataHub.stop();
trackDataHub = null;
}
/**
* Tests registering for tracks table update.
*/
@Test
public void testTracksTableUpdate() {
// Register two listeners
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
Track track = new Track();
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), contentObserverCapture.capture());
trackDataListener1.onTrackUpdated(track);
trackDataListener2.onTrackUpdated(track);
replay();
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE));
trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.TRACKS_TABLE));
verifyAndReset();
// Causes tracks table update
ContentObserver contentObserver = contentObserverCapture.getValue();
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
trackDataListener1.onTrackUpdated(track);
trackDataListener2.onTrackUpdated(track);
replay();
contentObserver.onChange(false);
verifyAndReset();
// Unregister one listener
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
trackDataListener2.onTrackUpdated(track);
replay();
trackDataHub.unregisterTrackDataListener(trackDataListener1);
contentObserver.onChange(false);
verifyAndReset();
// Unregister the second listener
dataSource.unregisterContentObserver(contentObserver);
replay();
trackDataHub.unregisterTrackDataListener(trackDataListener2);
contentObserver.onChange(false);
verifyAndReset();
}
//TODO
/**
* Tests registering for waypoints table update.
*/
// @Test
// public void testWaypointsTableUpdate() {
// Waypoint waypoint1 = new Waypoint();
// Waypoint waypoint2 = new Waypoint();
// Waypoint waypoint3 = new Waypoint();
// Waypoint waypoint4 = new Waypoint();
// Location location = new Location("gps");
// location.setLatitude(10.0);
// location.setLongitude(8.0);
// waypoint1.setLocation(location);
// waypoint2.setLocation(location);
// waypoint3.setLocation(location);
// waypoint4.setLocation(location);
//
// // Register two listeners
// ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
// when(contentProviderUtils.getWaypointCursor(
// eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
// .andStubAnswer(new FixedSizeCursorAnswer(2));
// when(contentProviderUtils.createWaypoint(isA(Cursor.class)))
// .thenReturn(waypoint1)
// .thenReturn(waypoint2)
// .thenReturn(waypoint1)
// .thenReturn(waypoint2);
// dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), contentObserverCapture.capture());
// trackDataListener1.clearWaypoints();
// trackDataListener2.clearWaypoints();
// trackDataListener1.onNewWaypoint(waypoint1);
// trackDataListener2.onNewWaypoint(waypoint1);
// trackDataListener1.onNewWaypoint(waypoint2);
// trackDataListener2.onNewWaypoint(waypoint2);
// trackDataListener1.onNewWaypointsDone();
// trackDataListener2.onNewWaypointsDone();
// replay();
//
// trackDataHub.start();
// trackDataHub.loadTrack(TRACK_ID);
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
// trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
// verifyAndReset();
//
// // Cause waypoints table update
// ContentObserver contentObserver = contentObserverCapture.getValue();
// when(contentProviderUtils.getWaypointCursor(
// eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
// .andStubAnswer(new FixedSizeCursorAnswer(3));
// when(contentProviderUtils.createWaypoint(isA(Cursor.class)))
// .thenReturn(waypoint1)
// .thenReturn(waypoint2)
// .thenReturn(waypoint3);
// trackDataListener1.clearWaypoints();
// trackDataListener2.clearWaypoints();
// trackDataListener1.onNewWaypoint(waypoint1);
// trackDataListener2.onNewWaypoint(waypoint1);
// trackDataListener1.onNewWaypoint(waypoint2);
// trackDataListener2.onNewWaypoint(waypoint2);
// trackDataListener1.onNewWaypoint(waypoint3);
// trackDataListener2.onNewWaypoint(waypoint3);
// trackDataListener1.onNewWaypointsDone();
// trackDataListener2.onNewWaypointsDone();
// replay();
//
// contentObserver.onChange(false);
// verifyAndReset();
//
// // Unregister one listener
// when(contentProviderUtils.getWaypointCursor(
// eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
// .andStubAnswer(new FixedSizeCursorAnswer(4));
// when(contentProviderUtils.createWaypoint(isA(Cursor.class)))
// .thenReturn(waypoint1)
// .thenReturn(waypoint2)
// .thenReturn(waypoint3)
// .thenReturn(waypoint4);
// trackDataListener2.clearWaypoints();
// trackDataListener2.onNewWaypoint(waypoint1);
// trackDataListener2.onNewWaypoint(waypoint2);
// trackDataListener2.onNewWaypoint(waypoint3);
// trackDataListener2.onNewWaypoint(waypoint4);
// trackDataListener2.onNewWaypointsDone();
// replay();
//
// trackDataHub.unregisterTrackDataListener(trackDataListener1);
// contentObserver.onChange(false);
// verifyAndReset();
//
// // Unregister the second listener
// dataSource.unregisterContentObserver(contentObserver);
// replay();
//
// trackDataHub.unregisterTrackDataListener(trackDataListener2);
// contentObserver.onChange(false);
// verifyAndReset();
// }
/**
* Tests track points table update.
*/
@Test
public void testTrackPointsTableUpdate() {
// Register one listener
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
trackDataListener1.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener1);
trackDataListener1.onNewTrackPointsDone();
replay();
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Register a second listener
locationIterator = new FixedSizeLocationIterator(1, 10, 5);
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
trackDataListener2.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener2);
trackDataListener2.onNewTrackPointsDone();
replay();
trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Deliver more points - should go to both listeners without clearing
ContentObserver contentObserver = contentObserverCapture.getValue();
locationIterator = new FixedSizeLocationIterator(11, 10, 1);
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(11L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(20L);
locationIterator.expectLocationsDelivered(trackDataListener1);
locationIterator.expectLocationsDelivered(trackDataListener2);
trackDataListener1.onNewTrackPointsDone();
trackDataListener2.onNewTrackPointsDone();
replay();
contentObserver.onChange(false);
verifyAndReset();
// Unregister one listener and change track
locationIterator = new FixedSizeLocationIterator(101, 10);
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
.thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).thenReturn(110L);
trackDataListener2.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener2);
trackDataListener2.onNewTrackPointsDone();
replay();
trackDataHub.unregisterTrackDataListener(trackDataListener1);
trackDataHub.loadTrack(TRACK_ID + 1);
verifyAndReset();
}
/**
* Tests track points table update with registering the same listener.
*/
@Test
public void testTrackPointsTableUpdate_reRegister() {
// Register one listener
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
when(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
trackDataListener1.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener1);
trackDataListener1.onNewTrackPointsDone();
replay();
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Unregister the listener
ContentObserver observer = contentObserverCapture.getValue();
dataSource.unregisterContentObserver(observer);
replay();
trackDataHub.unregisterTrackDataListener(trackDataListener1);
verifyAndReset();
// Register again
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
locationIterator = new FixedSizeLocationIterator(1, 10, 5);
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
trackDataListener1.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener1);
trackDataListener1.onNewTrackPointsDone();
replay();
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
}
/**
* Tests tracks point able change. Register a listener after a track change.
*/
@Test
public void testTrackPointsTableUpdate_reRegisterAfterTrackChange() {
// Register one listener
ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
when(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
trackDataListener1.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener1);
trackDataListener1.onNewTrackPointsDone();
replay();
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Unregister the listener
ContentObserver observer = observerCapture.getValue();
dataSource.unregisterContentObserver(observer);
replay();
trackDataHub.unregisterTrackDataListener(trackDataListener1);
verifyAndReset();
// Register the listener after a new track
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
locationIterator = new FixedSizeLocationIterator(1, 10);
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
.thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).thenReturn(10L);
trackDataListener1.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener1);
trackDataListener1.onNewTrackPointsDone();
replay();
trackDataHub.loadTrack(TRACK_ID + 1);
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
}
//TODO
/**
* Tests track points table update with large track sampling.
*/
// @Test
// public void testTrackPointsTableUpdate_largeTrackSampling() {
// ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
// dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
//
// when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(200L);
// AndroidMock.expectLastCall().anyTimes();
// FixedSizeLocationIterator locationIterator1 = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120);
// when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator1);
// FixedSizeLocationIterator locationIterator2 = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120);
// when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator2);
//
// trackDataListener1.clearTrackPoints();
// locationIterator1.expectSampledLocationsDelivered(trackDataListener1, 4, false);
// trackDataListener1.onNewTrackPointsDone();
// trackDataListener2.clearTrackPoints();
// locationIterator2.expectSampledLocationsDelivered(trackDataListener2, 4, true);
// trackDataListener2.onNewTrackPointsDone();
// replay();
//
// trackDataHub.start();
// trackDataHub.loadTrack(TRACK_ID);
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
// trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
// verifyAndReset();
// }
/**
* Tests track points table update with resampling.
*/
@Test
public void testTrackPointsTableUpdate_resampling() {
ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
// Deliver 30 points (no sampling happens)
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5);
when(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(30L);
trackDataListener1.clearTrackPoints();
locationIterator.expectLocationsDelivered(trackDataListener1);
trackDataListener1.onNewTrackPointsDone();
replay();
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Now deliver 30 more (incrementally sampled)
ContentObserver observer = observerCapture.getValue();
locationIterator = new FixedSizeLocationIterator(31, 30);
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(31L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(60L);
locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false);
trackDataListener1.onNewTrackPointsDone();
replay();
observer.onChange(false);
verifyAndReset();
// Now another 30 (triggers resampling)
locationIterator = new FixedSizeLocationIterator(1, 90);
when(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(90L);
trackDataListener1.clearTrackPoints();
locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false);
trackDataListener1.onNewTrackPointsDone();
replay();
observer.onChange(false);
verifyAndReset();
}
/**
* Tests preferences change.
*/
@Test
public void testPreferencesChange() throws Exception {
// Register two listeners
PreferencesUtils.setString(context, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT);
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
dataSource.registerOnSharedPreferenceChangeListener(preferenceChangeListenerCapture.capture());
when(trackDataListener1.onMetricUnitsChanged(true)).thenReturn(false);
when(trackDataListener1.onReportSpeedChanged(true)).thenReturn(false);
when(trackDataListener1.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)).thenReturn(false);
when(trackDataListener1.onRecordingDistanceIntervalChanged(PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).thenReturn(false);
when(trackDataListener2.onMetricUnitsChanged(true)).thenReturn(false);
when(trackDataListener2.onReportSpeedChanged(true)).thenReturn(false);
when(trackDataListener2.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)).thenReturn(false);
when(trackDataListener2.onRecordingDistanceIntervalChanged(PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).thenReturn(false);
replay();
trackDataHub.start();
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.PREFERENCE));
trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.PREFERENCE));
verifyAndReset();
// Change report speed to false
when(trackDataListener1.onReportSpeedChanged(false)).thenReturn(false);
when(trackDataListener2.onReportSpeedChanged(false)).thenReturn(false);
replay();
PreferencesUtils.setString(context, R.string.stats_rate_key, context.getString(R.string.stats_rate_pace));
OnSharedPreferenceChangeListener listener = preferenceChangeListenerCapture.getValue();
listener.onSharedPreferenceChanged(sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_rate_key));
verifyAndReset();
// Change metric units to false
when(trackDataListener1.onMetricUnitsChanged(false)).thenReturn(false);
when(trackDataListener2.onMetricUnitsChanged(false)).thenReturn(false);
replay();
String imperialUnits = context.getString(R.string.stats_units_imperial);
PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits);
listener.onSharedPreferenceChanged(sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_units_key));
verifyAndReset();
}
/**
* Replays mocks.
*/
private void replay() {
// AndroidMock.replay(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
}
/**
* Verifies and resets mocks.
*/
private void verifyAndReset() {
verify(contentProviderUtils);
verify(dataSource);
verify(trackDataListener1);
verify(trackDataListener2);
reset(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
}
//TODO
/**
* Tests the method {@link TrackDataHub#start()}.
*/
// @Test
// public void testRegisterTracksTableListener() {
// ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
// dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), observerCapture.capture());
// Track track = TrackStubUtils.createTrack(1);
//
// when(contentProviderUtils.getTrack(capture(new Capture<Long>())).thenReturn(track);
// // Make the track id is unique.
// PreferencesUtils.setLong(context, R.string.recording_track_id_key, System.currentTimeMillis());
// trackDataListener1.onTrackUpdated(track);
// replay();
// trackDataHub.start();
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE));
// verifyAndReset();
// }
//TODO
/**
* Tests the method {@link TrackDataHub#start()}.
*/
// @Test
// public void testRegisterWaypointsTableListener() {
// ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
// dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), observerCapture.capture());
// when(contentProviderUtils.getWaypointCursor(
// capture(new Capture<Long>()), capture(new Capture<Long>()),
// capture(new Capture<Integer>()))).thenReturn(null);
// trackDataListener1.clearWaypoints();
// trackDataListener1.onNewWaypointsDone();
// replay();
// trackDataHub.start();
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
// verifyAndReset();
// }
/**
* Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when
* the key is R.string.min_required_accuracy_key.
*/
@Test
public void testNotifyPreferenceChanged_minRequiredAccuracy() {
int value = 1;
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value);
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key));
Assert.assertEquals(value, trackDataHub.getRecordingGpsAccuracy());
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value + 1);
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key));
Assert.assertEquals(value + 1, trackDataHub.getRecordingGpsAccuracy());
}
/**
* Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when
* the key is R.string.metric_units_key.
*/
@Test
public void testNotifyPreferenceChanged_metricUnitsNoNotify() {
String imperialUnits = context.getString(R.string.stats_units_imperial);
PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits);
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_units_key));
Assert.assertFalse(trackDataHub.isMetricUnits());
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_units_key));
Assert.assertTrue(trackDataHub.isMetricUnits());
}
/**
* Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when
* the key is R.string.metric_units_key.
*/
@Test
public void testNotifyPreferenceChanged_reportSpeedNoNotify() {
String value = context.getString(R.string.stats_rate_pace);
PreferencesUtils.setString(context, R.string.stats_rate_key, value);
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_rate_key));
Assert.assertFalse(trackDataHub.isReportSpeed());
value = context.getString(R.string.stats_rate_speed);
PreferencesUtils.setString(context, R.string.stats_rate_key, value);
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_rate_key));
Assert.assertTrue(trackDataHub.isReportSpeed());
}
//TODO
/**
* Fixed size cursor answer.
*
* @author Jimmy Shih
*/
private static class FixedSizeCursorAnswer {
private final int size;
public FixedSizeCursorAnswer(int size) {
this.size = size;
}
//
// @Override
// public Cursor answer() throws Throwable {
// MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID});
// for (long i = 0; i < size; i++) {
// cursor.addRow(new Object[]{i});
// }
// return cursor;
// }
}
/**
* Fixed size location iterator.
*
* @author Jimmy Shih
*/
private static class FixedSizeLocationIterator implements LocationIterator {
private final long startId;
private final Location[] locations;
private final Set<Integer> splitIndexSet = new HashSet<Integer>();
private int currentIndex = -1;
public FixedSizeLocationIterator(long startId, int size) {
this(startId, size, null);
}
public FixedSizeLocationIterator(long startId, int size, int... splitIndexes) {
this.startId = startId;
this.locations = new Location[size];
for (int i = 0; i < size; i++) {
Location location = new Location("gps");
location.setLatitude(-15.0 + i / 1000.0);
location.setLongitude(37 + i / 1000.0);
location.setAltitude(i);
locations[i] = location;
}
if (splitIndexes != null) {
for (int splitIndex : splitIndexes) {
splitIndexSet.add(splitIndex);
Location splitLocation = locations[splitIndex];
splitLocation.setLatitude(100.0);
splitLocation.setLongitude(200.0);
}
}
}
public void expectLocationsDelivered(TrackDataListener listener) {
for (int i = 0; i < locations.length; i++) {
if (splitIndexSet.contains(i)) {
listener.onSegmentSplit(locations[i]);
} else {
listener.onSampledInTrackPoint(locations[i]);
}
}
}
public void expectSampledLocationsDelivered(
TrackDataListener listener, int sampleFrequency, boolean includeSampledOut) {
boolean includeNext = false;
for (int i = 0; i < locations.length; i++) {
if (splitIndexSet.contains(i)) {
listener.onSegmentSplit(locations[i]);
includeNext = true;
} else if (includeNext || (i % sampleFrequency == 0)) {
listener.onSampledInTrackPoint(locations[i]);
includeNext = false;
} else if (includeSampledOut) {
listener.onSampledOutTrackPoint(locations[i]);
}
}
}
@Override
public boolean hasNext() {
return currentIndex < locations.length - 1;
}
@Override
public Location next() {
currentIndex++;
return locations[currentIndex];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public long getLocationId() {
return startId + currentIndex;
}
@Override
public void close() {
// Do nothing
}
}
}
@@ -1,65 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
/**
* Tests for the WaypointCreationRequest class.
* {@link WaypointCreationRequest}
*
* @author Sandor Dornbush
*/
@RunWith(AndroidJUnit4.class)
public class WaypointCreationRequestTest {
@Test
public void testTypeParceling() {
WaypointCreationRequest original = WaypointCreationRequest.DEFAULT_WAYPOINT;
Parcel p = Parcel.obtain();
original.writeToParcel(p, 0);
p.setDataPosition(0);
WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p);
Assert.assertEquals(original.getType(), copy.getType());
Assert.assertFalse(copy.isTrackStatistics());
Assert.assertNull(copy.getName());
Assert.assertNull(copy.getDescription());
Assert.assertNull(copy.getIconUrl());
}
@Test
public void testAllAttributesParceling() {
WaypointCreationRequest original = new WaypointCreationRequest(WaypointType.WAYPOINT, false, "name", "category", "description", "img.png", null);
Parcel p = Parcel.obtain();
original.writeToParcel(p, 0);
p.setDataPosition(0);
WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p);
Assert.assertEquals(original.getType(), copy.getType());
Assert.assertFalse(copy.isTrackStatistics());
Assert.assertEquals("name", copy.getName());
Assert.assertEquals("category", copy.getCategory());
Assert.assertEquals("description", copy.getDescription());
Assert.assertEquals("img.png", copy.getIconUrl());
}
}
@@ -0,0 +1,44 @@
package de.dennisguse.opentracks.content.data;
import android.location.Location;
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;
/**
* Simulates a track which is used for testing.
*
* @param id the id of the track
* @param numPoints the location number in the track
* @return the simulated track
*/
public static Track getTrack(long id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
for (int i = 0; i < numPoints; i++) {
track.addLocation(createLocation(i));
}
return track;
}
/**
* Creates a location.
*
* @param i the index to set the value of location.
* @return created location
*/
public static Location createLocation(int i) {
Location loc = new Location("test");
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * ALTITUDE_INTERVAL);
loc.setTime(i + 1);
return loc;
}
}
@@ -17,18 +17,21 @@
package de.dennisguse.opentracks.fragments;
import android.location.Location;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.ChartView;
import de.dennisguse.opentracks.TrackStubUtils;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -44,19 +47,32 @@ public class ChartFragmentTest {
private ChartFragment chartFragment;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
@Before
public void setUp() {
chartFragment = new ChartFragment();
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext()));
boolean chartByDistance = false;
chartFragment = new ChartFragment(chartByDistance) {
};
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
}
/**
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
@Test
public void testFillDataPoint_sensorIncorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
@@ -80,7 +96,7 @@ public class ChartFragmentTest {
*/
@Test
public void testFillDataPoint_sensorCorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
@@ -107,17 +123,17 @@ public class ChartFragmentTest {
// By distance.
chartFragment.setChartByDistance(true);
// Resets last location and writes first location.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
// The second is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(0.0, point[0], 0.01);
// The third location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation3.setLatitude(23);
point = fillDataPointTestHelper(sensorDataSetLocation3);
@@ -129,7 +145,7 @@ public class ChartFragmentTest {
Assert.assertEquals(distance1, point[0], 0.01);
// The fourth location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation4.setLatitude(24);
point = fillDataPointTestHelper(sensorDataSetLocation4);
@@ -151,36 +167,34 @@ public class ChartFragmentTest {
chartFragment.setMetricUnits(false);
// The first is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
// The second location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setLatitude(23);
point = fillDataPointTestHelper(sensorDataSetLocation2);
/*
* Computes the distance between Latitude 22 and 23. And for we set using
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
* 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(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(),
sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(), sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1, point[0], 0.01);
// The third location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation3.setLatitude(24);
point = fillDataPointTestHelper(sensorDataSetLocation3);
/*
* Computes the distance between Latitude 23 and 24. And for we set using
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
* 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(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(),
sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1 + distance2, point[0], 0.01);
}
@@ -192,11 +206,11 @@ public class ChartFragmentTest {
public void testFillDataPoint_time() {
// By time
chartFragment.setChartByDistance(false);
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
long timeSpan = 222;
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((double) timeSpan, point[0], 0.01);
@@ -207,20 +221,19 @@ public class ChartFragmentTest {
*/
@Test
public void testFillDataPoint_elevation() {
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
/*
* At first, clear old points of elevation, so give true to the second
* parameter. Then only one value INITIALLONGTITUDE in buffer.
* At first, clear old points of elevation, so give true to the second parameter.
* Then only one value INITIAL_ALTITUDE in buffer.
*/
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01);
/*
* Send another value to buffer, now there are two values, INITIALALTITUDE
* and INITIALALTITUDE * 2.
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
*/
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
@@ -235,23 +248,22 @@ public class ChartFragmentTest {
@Test
public void testFillDataPoint_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.
* At first, clear old points of speed, so give true to the second parameter.
* It will not be filled in to the speed buffer.
*/
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(128.5f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
/*
* Tests the logic when both metricUnits and reportSpeed are true.This
* location will be filled into speed buffer.
* Tests the logic when both metricUnits and reportSpeed are true.
* This location will be filled into speed buffer.
*/
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
/*
* Add a time span here to make sure the second point is valid, the value
* 222 here is doesn't matter.
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
*/
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222);
sensorDataSetLocation2.setSpeed(130);
@@ -268,17 +280,16 @@ public class ChartFragmentTest {
chartFragment.setMetricUnits(false);
// First data point is not added to the speed buffer
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
/*
* Add a time span here to make sure the second point is valid and the speed
* is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION
* * timeDifference speedDifference = 102 -100 timeDifference = 222
* 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
*/
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
sensorDataSetLocation2.setSpeed(102);
@@ -295,17 +306,16 @@ public class ChartFragmentTest {
chartFragment.setReportSpeed(false);
// First data point is not added to the speed buffer
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
/*
* Add a time span here to make sure the second point is valid and the speed
* is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION
* * timeDifference speedDifference = 102 -100 timeDifference = 222
* 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
*/
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
sensorDataSetLocation2.setSpeed(102);
@@ -314,14 +324,13 @@ public class ChartFragmentTest {
}
/**
* Tests the logic to get pace value when reportSpeed is false and average
* speed is zero.
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
*/
@Test
public void testFillDataPoint_pace_zeroSpeed() {
// Setups reportSpeed to false.
chartFragment.setReportSpeed(false);
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation.setSpeed(0);
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1], 0.01);
@@ -33,10 +33,10 @@ import java.util.Locale;
import java.util.SimpleTimeZone;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.WaypointsColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
@@ -49,40 +49,32 @@ import static org.mockito.Mockito.when;
*/
public abstract class AbstractTestFileTrackImporter {
protected static final String TRACK_NAME_0 = "blablub";
protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
static final String TRACK_NAME_0 = "blablub";
static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
protected static final String TRACK_NAME_1 = "another track";
protected static final String TRACK_DESCRIPTION_1 = "another description";
static final double TRACK_LATITUDE = 48.768364;
static final double TRACK_LONGITUDE = 9.177886;
static final double TRACK_ELEVATION = 324.0;
protected static final double TRACK_LATITUDE = 48.768364;
protected static final double TRACK_LONGITUDE = 9.177886;
protected 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.123";
static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
protected static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
protected static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123";
protected static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
protected static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
protected static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
protected static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
protected static final long TRACK_ID_0 = 1;
protected static final long TRACK_ID_1 = 2;
protected static final long TRACK_POINT_ID_0 = 1;
protected static final long TRACK_POINT_ID_1 = 2;
protected static final long TRACK_POINT_ID_3 = 4;
protected static final long WAYPOINT_ID_0 = 1;
protected static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
protected static final Uri TRACK_ID_1_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build();
protected static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
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_ID_0 = 1;
static final long TRACK_POINT_ID_0 = 1;
static final long TRACK_POINT_ID_1 = 2;
static final long TRACK_POINT_ID_3 = 4;
static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
private static final long WAYPOINT_ID_0 = 1;
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.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.
*/
// 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.
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC");
DATE_FORMAT_0.setTimeZone(utc);
DATE_FORMAT_1.setTimeZone(utc);
@@ -91,7 +83,7 @@ public abstract class AbstractTestFileTrackImporter {
@Mock
public ContentProviderUtils contentProviderUtils;
protected Location createLocation(int index, long time) {
Location createLocation(int index, long time) {
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(TRACK_LATITUDE + index);
location.setLongitude(TRACK_LONGITUDE + index);
@@ -109,8 +101,6 @@ public abstract class AbstractTestFileTrackImporter {
*/
protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) {
when(contentProviderUtils.bulkInsertTrackPoint(location != null ? (Location[]) any() : (Location[]) any(), eq(1), eq(trackId))).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(trackId)).thenReturn(trackPointId);
when(contentProviderUtils.getLastTrackPointId(trackId)).thenReturn(trackPointId);
}
/**
@@ -120,7 +110,7 @@ public abstract class AbstractTestFileTrackImporter {
* @param lastTrack true if it is the last track in the gpx
* @param trackId the track id
*/
protected void expectUpdateTrack(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
contentProviderUtils.updateTrack(trackCaptor.capture());
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
@@ -136,7 +126,5 @@ public abstract class AbstractTestFileTrackImporter {
if (time != -1L) {
Assert.assertEquals(time, track.getTripStatistics().getStartTime());
}
Assert.assertNotSame(-1, track.getStartId());
Assert.assertNotSame(-1, track.getStopId());
}
}
@@ -0,0 +1,219 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.location.Location;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
* <p>
* TODO: test ignores {@link de.dennisguse.opentracks.stats.TripStatistics} for now.
*/
@RunWith(JUnit4.class)
public class ExportImportTest {
private static final String TAG = ExportImportTest.class.getSimpleName();
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
private static final String TRACK_ICON = "the track icon";
private static final String TRACK_CATEGORY = "the category";
private static final String TRACK_DESCRIPTION = "the description";
private final List<Waypoint> waypoints = new ArrayList<>();
private long importTrackId;
private long trackId = System.currentTimeMillis();
@Before
public void setUp() {
Track track = TestDataUtil.getTrack(trackId, 10);
track.setIcon(TRACK_ICON);
track.setCategory(TRACK_CATEGORY);
track.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.insertTrack(track);
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
for (int i = 0; i < 3; i++) {
Waypoint waypoint = new Waypoint();
waypoint.setName("the waypoint " + i);
waypoint.setDescription("the waypoint description " + i);
waypoint.setCategory("the waypoint category" + i);
waypoint.setIcon("the waypoing icon" + i);
waypoint.setPhotoUrl("the photo url" + i);
waypoint.setTrackId(trackId);
waypoint.setLocation(track.getLocations().get(i));
contentProviderUtils.insertWaypoint(waypoint);
waypoints.add(waypoint);
}
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(trackId));
}
@After
public void tearDown() {
contentProviderUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, importTrackId);
}
@LargeTest
@Test
public void kml_only_track() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kml_with_trackdetail() {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
System.out.println(outputStream.toString());
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
importTrackId = trackImporter.importFile(inputStream);
// then
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals(track.getLocations(), importedTrack.getLocations());
assertEquals(track.getCategory(), importedTrack.getCategory());
assertEquals(track.getDescription(), importedTrack.getDescription());
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.getIcon(), importedTrack.getIcon());
// 2. waypoints
assertWaypoints();
//TODO Check absolute time of trackpoints
}
@LargeTest
@Test
public void kml_with_trackdetail_and_sensordata() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kmz_only_track() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@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);
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
// then
// 1. track
Track trackImported = contentProviderUtils.getTrack(importTrackId);
assertNotNull(trackImported);
assertEquals(track.getLocations(), trackImported.getLocations());
assertEquals(track.getCategory(), trackImported.getCategory());
assertEquals(track.getDescription(), trackImported.getDescription());
assertEquals(track.getName(), trackImported.getName());
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
//assertEquals(track.getIcon(), trackImported.getIcon());
// 2. waypoints
assertWaypoints();
//TODO Check absolute time of trackpoints
}
private void assertWaypoints() {
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(importTrackId));
List<Waypoint> importedWaypoints = contentProviderUtils.getWaypoints(importTrackId);
for (int i = 0; i < waypoints.size(); i++) {
Waypoint waypoint = waypoints.get(i);
Waypoint importedWaypoint = importedWaypoints.get(i);
assertEquals(waypoint.getCategory(), importedWaypoint.getCategory());
assertEquals(waypoint.getDescription(), importedWaypoint.getDescription());
// assertEquals(waypoint.getIcon(), importedWaypoint.getIcon()); // TODO for KML
assertEquals(waypoint.getName(), importedWaypoint.getName());
assertEquals("", importedWaypoint.getPhotoUrl());
assertEquals(waypoint.getLocation().getLatitude(), importedWaypoint.getLocation().getLatitude(), 0.001);
assertEquals(waypoint.getLocation().getLongitude(), importedWaypoint.getLocation().getLongitude(), 0.001);
assertEquals(waypoint.getLocation().getAltitude(), importedWaypoint.getLocation().getAltitude(), 0.001);
}
}
}
@@ -27,18 +27,17 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link GpxFileTrackImporter}.
*
@@ -58,6 +57,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
+ getTrackPoint(0, null) + getTrackPoint(1, null) + "</trkseg><trkseg>"
+ getTrackPoint(2, null) + getTrackPoint(3, null) + "</trkseg></trk></gpx>";
private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring(0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50);
private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "1000.0");
private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(TRACK_TIME_0, "invalid");
@@ -82,13 +82,9 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
return buffer.toString();
}
/**
* Tests one track with one segment.
*/
@Test
public void testOneTrackOneSegment() throws Exception {
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
// given
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
@@ -98,28 +94,28 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
Assert.assertEquals(TRACK_ID_0, trackId);
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
verify(contentProviderUtils);
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
/**
* Tests one track with two segments.
*/
@Test
public void testOneTrackTwoSegments() throws Exception {
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
// given
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
@@ -127,12 +123,18 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
@@ -141,79 +143,61 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
verify(contentProviderUtils);
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
/**
* Tests one track with two segments, but no time in the track points.
*/
@Test
public void testOneTrackTwoSegmentsNoTime() {
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
// given
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
Assert.assertEquals(TRACK_ID_0, trackId);
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
verify(contentProviderUtils);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
}
/**
* Test an invalid xml input.
*/
@Test
public void testInvalidXml() {
testInvalidGpx(INVALID_XML_GPX);
}
/**
* Test an invalid location.
*/
@Test
public void testInvalidLocation() {
testInvalidGpx(INVALID_LOCATION_GPX);
}
/**
* Test an invalid time.
*/
@Test
public void testInvalidTime() {
testInvalidGpx(INVALID_TIME_GPX);
}
/**
* Test an invalid altitude.
*/
@Test
public void testInvalidAltitude() {
testInvalidGpx(INVALID_ALTITUDE_GPX);
}
/**
* Test an invalid latitude.
*/
@Test
public void testInvalidLatitude() {
testInvalidGpx(INVALID_LATITUDE_GPX);
}
/**
* Test an invalid longitude.
*/
@Test
public void testInvalidLongitude() {
testInvalidGpx(INVALID_LONGITUDE_GPX);
@@ -222,18 +206,14 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
private void testInvalidGpx(String xml) {
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
// For the following, use StubReturn since we don't care whether they are
// invoked or not.
// For the following, use StubReturn since we don't care whether they are invoked or not.
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
Assert.assertEquals(-1L, trackId);
verify(contentProviderUtils);
}
}
@@ -27,16 +27,15 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link KmlFileTrackImporter}.
*
@@ -45,13 +44,11 @@ import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX =
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1)
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX =
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</gx:Track><gx:Track>"
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3)
@@ -66,18 +63,12 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
String latitude = Double.toString(TRACK_LATITUDE + index);
String longitude = Double.toString(TRACK_LONGITUDE + index);
String altitude = Double.toString(TRACK_ELEVATION + index);
StringBuilder buffer = new StringBuilder();
buffer.append("<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>");
return buffer.toString();
return "<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>";
}
/**
* Tests one track with one segment.
*/
@Test
public void testOneTrackOneSegment() throws Exception {
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
// given
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
@@ -85,30 +76,31 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint(new Location[]{location1}, eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
verify(contentProviderUtils);
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
/**
* Tests one track with two segments.
*/
@Test
public void testOneTrackTwoSegments() throws Exception {
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
// given
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
contentProviderUtils.clearTrack(context, TRACK_ID_0);
@@ -117,12 +109,18 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
@@ -131,7 +129,6 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
verify(contentProviderUtils);
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
}
@@ -15,14 +15,19 @@
*/
package de.dennisguse.opentracks.services;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests the {@link AdaptiveLocationListenerPolicy}.
*
* @author youtaol
*/
public class AdaptiveLocationListenerPolicyTest extends TestCase {
@RunWith(JUnit4.class)
public class AdaptiveLocationListenerPolicyTest {
private static final long MIN = 1000;
private static final long MAX = 3000;
@@ -31,18 +36,15 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
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 AdaptiveLocationListenerPolicy adocationListenerPolicy;
private AdaptiveLocationListenerPolicy adaptiveLocationListenerPolicy;
@Override
protected void setUp() throws Exception {
super.setUp();
adocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE);
@Before
public void setUp() {
adaptiveLocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE);
}
/**
* Tests the
* {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four
* situations.
* Tests the {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four situations.
* <ul>
* <li>The newIdleTime is bigger than max interval.</li>
* <li>The newIdleTime is between min and max interval.</li>
@@ -50,27 +52,27 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
* <li>The newIdleTime is smaller than the smallest interval unit.</li>
* </ul>
*/
@Test
public void testGetDesiredPollingInterval() {
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG);
assertEquals(MAX, adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG);
Assert.assertEquals(MAX, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.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,
adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
Assert.assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
Assert.assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN);
assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN);
Assert.assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
}
/**
* Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance()}.
* Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance_m()}.
*/
@Test
public void testGetMinDistance() {
assertEquals(MIN_DISTANCE, adocationListenerPolicy.getMinDistance());
Assert.assertEquals(MIN_DISTANCE, adaptiveLocationListenerPolicy.getMinDistance_m());
}
}
@@ -1,100 +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.services;
import android.content.Context;
import android.content.Intent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests {@link ControlRecordingService}.
*
* @author Youtao Liu
*/
public class ControlRecordingServiceTest {
@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private Context context = ApplicationProvider.getApplicationContext();
@Mock
private ITrackRecordingService iTrackRecordingServiceMock;
private ControlRecordingService subject;
/**
* Tests the start of {@link ControlRecordingService} and tests the method
* {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)}
* to start a track recording.
*/
@Test
public void testStartRecording() throws TimeoutException {
Assert.assertNull(subject);
Intent intent = startControlRecordingService(context.getString(R.string.track_action_start));
Assert.assertNotNull(subject);
when(iTrackRecordingServiceMock.startNewTrack()).thenReturn(1L);
subject.onHandleIntent(intent, iTrackRecordingServiceMock);
verify(iTrackRecordingServiceMock);
}
/**
* Tests the method
* {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)}
* to stop a track recording.
*/
@Test
public void testStopRecording() throws TimeoutException {
Intent intent = startControlRecordingService(context.getString(R.string.track_action_end));
iTrackRecordingServiceMock.endCurrentTrack();
subject.onHandleIntent(intent, iTrackRecordingServiceMock);
verify(iTrackRecordingServiceMock);
}
/**
* Starts a ControlRecordingService with a specified action.
*
* @param action the action string in the start intent
*/
private Intent startControlRecordingService(String action) throws TimeoutException {
Intent intent = new Intent(context, ControlRecordingService.class);
intent.setAction(action);
mServiceRule.startService(intent);
subject = ((ControlRecordingService) mServiceRule.bindService(intent));
return intent;
}
}
@@ -0,0 +1,54 @@
package de.dennisguse.opentracks.services;
import android.app.NotificationManager;
import android.content.Context;
import android.location.Location;
import androidx.core.app.NotificationCompat;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
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;
@RunWith(MockitoJUnitRunner.class)
public class TrackRecordingServiceNotificationManagerTest {
private Context context = ApplicationProvider.getApplicationContext();
@Mock
private Location locationMock;
@Mock
private NotificationCompat.Builder notificationCompatBuilder;
@Mock
private NotificationManager notificationManager;
@Test
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
when(locationMock.hasAccuracy()).thenReturn(true);
when(locationMock.getAccuracy()).thenReturn(999f);
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
// when
subject.updateLocation(context, locationMock, 100);
subject.updateLocation(context, locationMock, 100);
subject.updateLocation(context, locationMock, 1000);
subject.updateLocation(context, locationMock, 100);
// then
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
verify(notificationCompatBuilder, times(2)).setOnlyAlertOnce(false);
}
}
@@ -15,38 +15,37 @@
*/
package de.dennisguse.opentracks.services;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.IBinder;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -54,163 +53,81 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
* Tests for the track recording service.
*
* @author Bartlomiej Niechwiej
* <p>
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTest {
@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
private ContentProviderUtils providerUtils;
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private final long trackId = Math.abs(new Random().nextLong());
static Intent createStartIntent(Context context) {
return new Intent(context, TrackRecordingService.class);
}
@Before
public void setUp() throws Exception {
public void setUp() {
// Set up the mock content resolver
CustomContentProvider customContentProvider = new CustomContentProvider();
ContentProvider customContentProvider = new CustomContentProvider() {
};
customContentProvider.attachInfo(context, null);
providerUtils = ContentProviderUtils.Factory.get(context);
contentProviderUtils = new ContentProviderUtils(context);
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply();
// Disable auto resume by default.
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
// No recording track.
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
// Ensure that the database is empty before every test
contentProviderUtils.deleteAllTracks(context);
}
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@SmallTest
@Test
public void testStartable() throws TimeoutException {
Intent intent = new Intent(context, TrackRecordingService.class);
mServiceRule.startService(createStartIntent());
Assert.assertNotNull(mServiceRule.bindService(createStartIntent()));
mServiceRule.startService(createStartIntent(context));
Assert.assertNotNull(mServiceRule.bindService(createStartIntent(context)));
}
@MediumTest
@Test
public void testBindable() throws TimeoutException {
IBinder service = mServiceRule.bindService(createStartIntent());
IBinder service = mServiceRule.bindService(createStartIntent(context));
Assert.assertNotNull(service);
}
@MediumTest
@Test
public void testResumeAfterReboot_shouldResume() throws Exception {
// Insert a dummy track and mark it as recording track.
createDummyTrack(123L, System.currentTimeMillis(), true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We expect to resume the previous track.
Assert.assertTrue(service.isRecording());
Assert.assertEquals(123L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_simulateReboot() throws Exception {
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
// Simulate recording a track.
long id = service.startNewTrack();
Assert.assertTrue(service.isRecording());
Assert.assertEquals(id, service.getRecordingTrackId());
mServiceRule.unbindService();
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertNotNull(service);
Assert.assertTrue(service.isRecording());
}
@MediumTest
@Test
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
// Insert a dummy track and mark it as recording track.
createDummyTrack(123L, System.currentTimeMillis(), false);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT,
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because it was stopped.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_expiredTrack() throws Exception {
// Insert a dummy track last updated 20 min ago.
createDummyTrack(123L, System.currentTimeMillis() - 20 * 60 * 1000, true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT,
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because it has expired.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
// Insert a dummy track.
createDummyTrack(123L, System.currentTimeMillis(), true);
// Set the number of attempts to max.
updateAutoResumePrefs(TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS,
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because there were already
// too many attempts.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_noTracks() throws Exception {
List<Track> tracks = providerUtils.getAllTracks();
List<Track> tracks = contentProviderUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Intent startIntent = createStartIntent(context);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// Test if we start in no-recording mode by default.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
@@ -219,312 +136,60 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testRecording_oldTracks() throws Exception {
createDummyTrack(123L, -1L, false);
createDummyTrack(trackId, -1L, false);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_orphanedRecordingTrack() throws Exception {
Intent startIntent = createStartIntent(context);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// Just set recording track to a bogus value.
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 256L);
// 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);
// Make sure that the service will not start recording and will clear
// the bogus track.
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
// then
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testStartNewTrack_noRecording() throws Exception {
// NOTICE: due to the way Android permissions work, if this fails,
// uninstall the test apk then retry - the test must be installed *after* the app (go figure).
// Reference: http://code.google.com/p/android/issues/detail?id=5521
BlockingBroadcastReceiver startReceiver = new BlockingBroadcastReceiver();
String startAction = context.getString(R.string.track_started_broadcast_action);
context.registerReceiver(startReceiver, new IntentFilter(startAction));
List<Track> tracks = providerUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
long id = service.startNewTrack();
Assert.assertTrue(id >= 0);
Assert.assertTrue(service.isRecording());
Track track = providerUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(PreferencesUtils.getString(context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT), track.getCategory());
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertEquals(id, service.getRecordingTrackId());
// Verify that the start broadcast was received.
Assert.assertTrue(startReceiver.waitUntilReceived(1));
List<Intent> receivedIntents = startReceiver.getReceivedIntents();
Assert.assertEquals(1, receivedIntents.size());
Intent broadcastIntent = receivedIntents.get(0);
Assert.assertEquals(startAction, broadcastIntent.getAction());
Assert.assertEquals(id, broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
context.unregisterReceiver(startReceiver);
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testStartNewTrack_alreadyRecording() throws Exception {
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
long trackId = service.getRecordingTrackId();
// Starting a new track when there is a recording should just return -1L.
long newTrack = service.startNewTrack();
Assert.assertEquals(-1L, newTrack);
long newTrackId = service.startNewTrack();
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
Assert.assertEquals(123L, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertEquals(123L, service.getRecordingTrackId());
}
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId());
@MediumTest
@Test
public void testEndCurrentTrack_alreadyRecording() throws Exception {
// See comment above if this fails randomly.
BlockingBroadcastReceiver stopReceiver = new BlockingBroadcastReceiver();
String stopAction = context.getString(R.string.track_stopped_broadcast_action);
context.registerReceiver(stopReceiver, new IntentFilter(stopAction));
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertTrue(service.isRecording());
// End the current track.
service.endCurrentTrack();
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
// Verify that the stop broadcast was received.
Assert.assertTrue(stopReceiver.waitUntilReceived(1));
List<Intent> receivedIntents = stopReceiver.getReceivedIntents();
Assert.assertEquals(1, receivedIntents.size());
Intent broadcastIntent = receivedIntents.get(0);
Assert.assertEquals(stopAction, broadcastIntent.getAction());
Assert.assertEquals(123L, broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
context.unregisterReceiver(stopReceiver);
}
@MediumTest
@Test
public void testEndCurrentTrack_noRecording() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
/*
* Ending the current track when there is no recording should not result in
* any error.
*/
// Ending the current track when there is no recording should not result in any error.
service.endCurrentTrack();
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertFalse(PreferencesUtils.isRecording(context));
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testIntegration_completeRecordingSession() throws Exception {
List<Track> tracks = providerUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
fullRecordingSession();
}
@MediumTest
@Test
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
Assert.assertEquals(-1L, waypointId);
}
@MediumTest
@Test
public void testInsertStatisticsMarker_validLocation() throws Exception {
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertTrue(service.isRecording());
Assert.assertFalse(service.isPaused());
insertLocation(service);
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
Assert.assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
Waypoint wpt = providerUtils.getWaypoint(2);
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 1), wpt.getName());
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
Assert.assertEquals(123L, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
Assert.assertNotNull(wpt.getTripStatistics());
// TODO check the rest of the params.
// TODO: Check waypoint 2.
}
@MediumTest
@Test
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
Assert.assertEquals(-1L, waypointId);
}
@MediumTest
@Test
public void testInsertWaypointMarker_validWaypoint() throws Exception {
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertTrue(service.isRecording());
insertLocation(service);
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT));
Waypoint wpt = providerUtils.getWaypoint(1);
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
Assert.assertEquals(WaypointType.WAYPOINT, wpt.getType());
Assert.assertEquals(123L, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
Assert.assertNull(wpt.getTripStatistics());
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyDefault() throws Exception {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByDistance() throws Exception {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByTime() throws Exception {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistanceDefault() throws Exception {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistance() throws Exception {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistanceDefault() throws Exception {
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistance() throws Exception {
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_splitFrequencyDefault() throws Exception {
PreferencesUtils.setInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_splitFrequencyByDistance() throws Exception {
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_splitFrequencyByTime() throws Exception {
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws Exception {
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDisabled() throws Exception {
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingIntervalDefault() throws Exception {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingInterval() throws Exception {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracyDefault() throws Exception {
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracy() throws Exception {
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
fullRecordingSession();
}
private Track createDummyTrack(long id, long stopTime, boolean isRecording) {
// NOTE: Do not use to create a track that is currently recording.
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
Track dummyTrack = new Track();
dummyTrack.setId(id);
dummyTrack.setName("Dummy Track");
@@ -532,78 +197,18 @@ public class TrackRecordingServiceTest {
tripStatistics.setStopTime(stopTime);
dummyTrack.setTripStatistics(tripStatistics);
addTrack(dummyTrack, isRecording);
return dummyTrack;
}
private void updateAutoResumePrefs(int attempts, int timeoutMins) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins);
}
private Intent createStartIntent() {
Intent startIntent = new Intent();
startIntent.setClass(context, TrackRecordingService.class);
return startIntent;
}
private void addTrack(Track track, boolean isRecording) {
Assert.assertTrue(track.getId() >= 0);
providerUtils.insertTrack(track);
Assert.assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
contentProviderUtils.insertTrack(track);
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
}
private void fullRecordingSession() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
// Start a track.
long id = service.startNewTrack();
Assert.assertTrue(id >= 0);
Assert.assertTrue(service.isRecording());
Track track = providerUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertEquals(id, service.getRecordingTrackId());
// Insert a few points, markers and statistics.
long startTime = System.currentTimeMillis();
for (int i = 0; i < 30; i++) {
Location loc = new Location("gps");
loc.setLongitude(35.0f + i / 10.0f);
loc.setLatitude(45.0f - i / 5.0f);
loc.setAccuracy(5);
loc.setSpeed(10);
loc.setTime(startTime + i * 10000);
loc.setBearing(3.0f);
service.insertTrackPoint(loc);
if (i % 10 == 0) {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
} else if (i % 7 == 0) {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
}
}
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
track = providerUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
TripStatistics tripStatistics = track.getTripStatistics();
Assert.assertNotNull(tripStatistics);
Assert.assertTrue(tripStatistics.getStartTime() > 0);
Assert.assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
}
/**
* Inserts a location and waits for 100ms.
*
* @param trackRecordingService the track recording service
* Inserts a location and waits for 200ms.
*/
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
Location location = new Location("gps");
@@ -614,47 +219,38 @@ public class TrackRecordingServiceTest {
location.setTime(System.currentTimeMillis());
location.setBearing(3.0f);
trackRecordingService.insertTrackPoint(location);
Thread.sleep(200);
}
/**
* Synchronous/waitable broadcast receiver to be used in testing.
*/
private class BlockingBroadcastReceiver extends BroadcastReceiver {
private static final long MAX_WAIT_TIME_MS = 10000;
private final List<Intent> receivedIntents = new ArrayList<>();
@MediumTest
@Test
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
public List<Intent> getReceivedIntents() {
return receivedIntents;
}
long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertEquals(-1L, waypointId);
}
@Override
public void onReceive(Context ctx, Intent intent) {
Log.d("Test", "Got broadcast: " + intent);
synchronized (receivedIntents) {
receivedIntents.add(intent);
receivedIntents.notifyAll();
}
}
@MediumTest
@Test
public void testInsertWaypointMarker_validWaypoint() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
insertLocation(service);
public boolean waitUntilReceived(int receiveCount) {
long deadline = System.currentTimeMillis() + MAX_WAIT_TIME_MS;
synchronized (receivedIntents) {
while (receivedIntents.size() < receiveCount) {
try {
// Wait releases synchronized lock until it returns
receivedIntents.wait(500);
} catch (InterruptedException e) {
// Do nothing
}
long trackId = service.getRecordingTrackId();
long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertNotEquals(-1L, waypointId);
Waypoint wpt = contentProviderUtils.getWaypoint(waypointId);
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
Assert.assertEquals(trackId, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
if (System.currentTimeMillis() > deadline) {
return false;
}
}
}
return true;
}
service.endCurrentTrack();
}
}
@@ -0,0 +1,237 @@
package de.dennisguse.opentracks.services;
import android.content.ContentProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Tests for the track recording service, which require a {@link Looper}.
*
* @author Bartlomiej Niechwiej
* <p>
* ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestLooper {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
@Before
public void setUp() {
// Set up the mock content resolver
ContentProvider customContentProvider = new CustomContentProvider() {
};
customContentProvider.attachInfo(context, null);
providerUtils = 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
providerUtils.deleteAllTracks(context);
}
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
// Ensure that the database is empty after every test
providerUtils.deleteAllTracks(context);
}
@MediumTest
@Test
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
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)));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
fullRecordingSession();
}
@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)));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
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);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingDistance() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
PreferencesUtils.setString(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));
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)));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_minRecordingInterval() throws TimeoutException {
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
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);
fullRecordingSession();
}
@MediumTest
@Test
public void testIntegration_completeRecordingSession() throws TimeoutException {
List<Track> tracks = providerUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
fullRecordingSession();
}
private void fullRecordingSession() throws TimeoutException {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
Assert.assertFalse(service.isRecording());
// Start a track.
long id = service.startNewTrack();
Assert.assertTrue(id >= 0);
Assert.assertTrue(service.isRecording());
Track track = providerUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(id, service.getRecordingTrackId());
// Insert a few points, markers and statistics.
long startTime = System.currentTimeMillis();
for (int i = 0; i < 30; i++) {
Location location = new Location("gps");
location.setLongitude(35.0f + i / 10.0f);
location.setLatitude(45.0f - i / 5.0f);
location.setAccuracy(5);
location.setSpeed(10);
location.setTime(startTime + i * 10000);
location.setBearing(3.0f);
service.insertTrackPoint(location);
if (i % 7 == 0) {
service.insertWaypoint(null, null, null, null);
}
}
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
track = providerUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
TripStatistics tripStatistics = track.getTripStatistics();
Assert.assertNotNull(tripStatistics);
Assert.assertTrue(tripStatistics.getStartTime() > 0);
Assert.assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
}
}
@@ -17,17 +17,19 @@
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for {@link AnnouncementPeriodicTaskFactory}.
* These tests require Donut+ to run.
*
* @author Rodrigo Damazio
*/
@@ -36,6 +38,17 @@ public class AnnouncementPeriodicTaskFactoryTest {
private Context context = ApplicationProvider.getApplicationContext();
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
@Test
public void testCreate() {
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
@@ -1,549 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import androidx.test.core.app.ApplicationProvider;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import de.dennisguse.opentracks.stats.TripStatistics;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link AnnouncementPeriodicTask}.
* WARNING: I'm not responsible if your eyes start bleeding while reading this
* code. You have been warned. It's still better than no test, though.
*
* @author Rodrigo Damazio
*/
@RunWith(MockitoJUnitRunner.class)
public class AnnouncementPeriodicTaskTest {
// Use something other than our hardcoded value
private static final Locale DEFAULT_LOCALE = Locale.KOREAN;
private static final String ANNOUNCEMENT = "I can haz cheeseburger?";
private Locale oldDefaultLocale;
private Context context = ApplicationProvider.getApplicationContext();
private AnnouncementPeriodicTask task;
@Mock
private AnnouncementPeriodicTask mockTask;
private ArgumentCaptor<OnInitListener> initListenerCapture = ArgumentCaptor.forClass(OnInitListener.class);
private ArgumentCaptor<PhoneStateListener> phoneListenerCapture = ArgumentCaptor.forClass(PhoneStateListener.class);
private TextToSpeechDelegate ttsDelegate;
@Mock
private TextToSpeechInterface tts;
// @UsesMocks({
// StringUtils.class,
// })
@Before
public void setUp() throws Exception {
oldDefaultLocale = Locale.getDefault();
Locale.setDefault(DEFAULT_LOCALE);
// Eww, the effort required just to mock TextToSpeech is insane
final AtomicBoolean listenerCalled = new AtomicBoolean();
OnInitListener blockingListener = new OnInitListener() {
@Override
public void onInit(int status) {
synchronized (this) {
listenerCalled.set(true);
notify();
}
}
};
ttsDelegate = new TextToSpeechDelegate(context, blockingListener);
// Wait for all async operations done in the constructor to finish.
synchronized (blockingListener) {
while (!listenerCalled.get()) {
// Releases the synchronized lock until we're woken up.
blockingListener.wait();
}
}
// Create a partial forwarding mock
task = new AnnouncementPeriodicTask(context) {
@Override
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
return mockTask.newTextToSpeech(ctx, onInitListener);
}
@Override
protected String getAnnouncement(TripStatistics stats) {
return mockTask.getAnnouncement(stats);
}
@Override
protected void listenToPhoneState(PhoneStateListener listener, int events) {
mockTask.listenToPhoneState(listener, events);
}
};
}
@After
public void tearDown() {
Locale.setDefault(oldDefaultLocale);
}
@Test
public void testStart() {
doStart();
OnInitListener ttsInitListener = initListenerCapture.getValue();
Assert.assertNotNull(ttsInitListener);
ttsInitListener.onInit(TextToSpeech.SUCCESS);
//TODO
//verify(mockTask, tts);
}
@Test
public void testStart_notReady() {
doStart();
OnInitListener ttsInitListener = initListenerCapture.getValue();
Assert.assertNotNull(ttsInitListener);
ttsInitListener.onInit(TextToSpeech.ERROR);
//TODO
//AndroidMock.verify(mockTask, tts);
}
@Test
public void testShutdown() {
// First, start
doStart();
verify(mockTask);
reset(mockTask);
// Then, shut down
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
mockTask.listenToPhoneState(
same(phoneListener), eq(PhoneStateListener.LISTEN_NONE));
tts.shutdown();
task.shutdown();
//TODO
//AndroidMock.verify(mockTask, tts);
}
@Test
public void testRun() {
// when service data calls
TripStatistics stats = new TripStatistics();
// when announcement building call
when(mockTask.getAnnouncement(same(stats))).thenReturn(ANNOUNCEMENT);
// Put task in "ready" state
startTask(TextToSpeech.SUCCESS);
when(tts.isLanguageAvailable(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
when(tts.setLanguage(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
when(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).thenReturn(TextToSpeech.SUCCESS);
when(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) any())).thenReturn(0);
// Run the announcement
task.announce(stats);
//TODO
//AndroidMock.verify(mockTask, tts);
}
@Test
public void testRun_notReady() {
// Put task in "not ready" state
startTask(TextToSpeech.ERROR);
// Run the announcement
task.run(null);
//TODO
//verify(mockTask, tts);
}
@Test
public void testRun_duringCall() {
startTask(TextToSpeech.SUCCESS);
when(tts.isSpeaking()).thenReturn(false);
// Run the announcement
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
task.run(null);
//TODO
//verify(mockTask, tts);
}
@Test
public void testRun_ringWhileSpeaking() {
startTask(TextToSpeech.SUCCESS);
when(tts.isSpeaking()).thenReturn(true);
when(tts.stop()).thenReturn(TextToSpeech.SUCCESS);
// Update the state to ringing - this should stop the current announcement.
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
// Run the announcement - this should do nothing.
task.run(null);
//TODO
//AndroidMock.verify(mockTask, tts);
}
@Test
public void testRun_whileRinging() {
startTask(TextToSpeech.SUCCESS);
when(tts.isSpeaking()).thenReturn(false);
// Run the announcement
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
task.run(null);
//TODO
//AndroidMock.verify(mockTask, tts);
}
@Test
public void testRun_noService() {
startTask(TextToSpeech.SUCCESS);
// Run the announcement
task.run(null);
//TODO
//AndroidMock.verify(mockTask, tts);
}
@Test
public void testRun_noStats() {
// when service data calls
startTask(TextToSpeech.SUCCESS);
// Run the announcement
task.run(null);
//TODO
//AndroidMock.verify(mockTask, tts);
}
/**
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero.
*/
@Test
public void testGetAnnounceTime_time_zero() {
long time = 0; // 0 seconds
Assert.assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time));
}
/**
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time one.
*/
@Test
public void testGetAnnounceTime_time_one() {
long time = 1 * 1000; // 1 second
Assert.assertEquals("0 minutes 1 second", task.getAnnounceTime(time));
}
/**
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
* numbers with the hour unit.
*/
@Test
public void testGetAnnounceTime_singular_has_hour() {
long time = (1 * 60 * 60 * 1000) + (1 * 60 * 1000) + (1 * 1000); // 1 hour 1 minute 1 second
Assert.assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time));
}
/**
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
* with the hour unit.
*/
@Test
public void testGetAnnounceTime_plural_has_hour() {
long time = (2 * 60 * 60 * 1000) + (2 * 60 * 1000) + (2 * 1000); // 2 hours 2 minutes 2 seconds
Assert.assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time));
}
/**
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
* numbers without the hour unit.
*/
@Test
public void testGetAnnounceTime_singular_no_hour() {
long time = (1 * 60 * 1000) + (1 * 1000); // 1 minute 1 second
Assert.assertEquals("1 minute 1 second", task.getAnnounceTime(time));
}
/**
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
* without the hour unit.
*/
@Test
public void testGetAnnounceTime_plural_no_hour() {
long time = (2 * 60 * 1000) + (2 * 1000); // 2 minutes 2 seconds
Assert.assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time));
}
private void startTask(int state) {
reset(tts);
doStart();
OnInitListener ttsInitListener = initListenerCapture.getValue();
ttsInitListener.onInit(state);
//TODO
reset(tts);
//AndroidMock.resetToDefault(tts);
}
private void doStart() {
mockTask.listenToPhoneState(phoneListenerCapture.capture(), eq(PhoneStateListener.LISTEN_CALL_STATE));
when(mockTask.newTextToSpeech(same(context), initListenerCapture.capture())).thenReturn(ttsDelegate);
task.start();
}
/**
* Mockable interface that we delegate TTS calls to.
*/
interface TextToSpeechInterface {
int addEarcon(String earcon, String packagename, int resourceId);
int addEarcon(String earcon, String filename);
int addSpeech(String text, String packagename, int resourceId);
int addSpeech(String text, String filename);
boolean areDefaultsEnforced();
String getDefaultEngine();
Locale getLanguage();
int isLanguageAvailable(Locale loc);
boolean isSpeaking();
int playEarcon(String earcon, int queueMode,
HashMap<String, String> params);
int playSilence(long durationInMs, int queueMode,
HashMap<String, String> params);
int setEngineByPackageName(String enginePackageName);
int setLanguage(Locale loc);
int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener);
int setPitch(float pitch);
int setSpeechRate(float speechRate);
void shutdown();
int speak(String text, int queueMode, HashMap<String, String> params);
int stop();
int synthesizeToFile(String text, HashMap<String, String> params,
String filename);
}
/**
* Subclass of {@link TextToSpeech} which delegates calls to the interface
* above.
* The logic here is stupid and the author is ashamed of having to write it
* like this, but basically the issue is that TextToSpeech cannot be mocked
* without running its constructor, its constructor runs async operations
* which call other methods (and then if the methods are part of a mock we'd
* have to set a behavior, but we can't 'cause the object hasn't been fully
* built yet).
* The logic is that calls made during the constructor (when tts is not yet
* set) will go up to the original class, but after tts is set we'll forward
* them all to the mock.
*/
private class TextToSpeechDelegate extends TextToSpeech implements TextToSpeechInterface {
public TextToSpeechDelegate(Context context, OnInitListener listener) {
super(context, listener);
}
@Override
public int addEarcon(String earcon, String packagename, int resourceId) {
if (tts == null) {
return super.addEarcon(earcon, packagename, resourceId);
}
return tts.addEarcon(earcon, packagename, resourceId);
}
@Override
public int addEarcon(String earcon, String filename) {
if (tts == null) {
return super.addEarcon(earcon, filename);
}
return tts.addEarcon(earcon, filename);
}
@Override
public int addSpeech(String text, String packagename, int resourceId) {
if (tts == null) {
return super.addSpeech(text, packagename, resourceId);
}
return tts.addSpeech(text, packagename, resourceId);
}
@Override
public int addSpeech(String text, String filename) {
if (tts == null) {
return super.addSpeech(text, filename);
}
return tts.addSpeech(text, filename);
}
@Override
public Locale getLanguage() {
if (tts == null) {
return super.getLanguage();
}
return tts.getLanguage();
}
@Override
public int isLanguageAvailable(Locale loc) {
if (tts == null) {
return super.isLanguageAvailable(loc);
}
return tts.isLanguageAvailable(loc);
}
@Override
public boolean isSpeaking() {
if (tts == null) {
return super.isSpeaking();
}
return tts.isSpeaking();
}
@Override
public int playEarcon(String earcon, int queueMode, HashMap<String, String> params) {
if (tts == null) {
return super.playEarcon(earcon, queueMode, params);
}
return tts.playEarcon(earcon, queueMode, params);
}
@Override
public int playSilence(long durationInMs, int queueMode, HashMap<String, String> params) {
if (tts == null) {
return super.playSilence(durationInMs, queueMode, params);
}
return tts.playSilence(durationInMs, queueMode, params);
}
@Override
public int setLanguage(Locale loc) {
if (tts == null) {
return super.setLanguage(loc);
}
return tts.setLanguage(loc);
}
@Override
public int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener) {
if (tts == null) {
return super.setOnUtteranceCompletedListener(listener);
}
return tts.setOnUtteranceCompletedListener(listener);
}
@Override
public int setPitch(float pitch) {
if (tts == null) {
return super.setPitch(pitch);
}
return tts.setPitch(pitch);
}
@Override
public int setSpeechRate(float speechRate) {
if (tts == null) {
return super.setSpeechRate(speechRate);
}
return tts.setSpeechRate(speechRate);
}
@Override
public void shutdown() {
if (tts == null) {
super.shutdown();
return;
}
tts.shutdown();
}
@Override
public int speak(
String text, int queueMode, HashMap<String, String> params) {
if (tts == null) {
return super.speak(text, queueMode, params);
}
return tts.speak(text, queueMode, params);
}
@Override
public int stop() {
if (tts == null) {
return super.stop();
}
return tts.stop();
}
@Override
public int synthesizeToFile(String text, HashMap<String, String> params, String filename) {
if (tts == null) {
return super.synthesizeToFile(text, params, filename);
}
return tts.synthesizeToFile(text, params, filename);
}
}
}
@@ -15,7 +15,11 @@
*/
package de.dennisguse.opentracks.stats;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link TripStatistics}.
@@ -23,26 +27,17 @@ import junit.framework.TestCase;
*
* @author Rodrigo Damazio
*/
public class TripStatisticsTest extends TestCase {
@RunWith(JUnit4.class)
public class TripStatisticsTest {
private TripStatistics statistics;
@Override
protected void setUp() throws Exception {
super.setUp();
@Before
public void setUp() {
statistics = new TripStatistics();
}
public void testSetBounds() {
// This is not a trivial setter, conversion happens in it
statistics.setBounds(12345, -34567, 56789, -98765);
assertEquals(12345, statistics.getLeft());
assertEquals(-34567, statistics.getTop());
assertEquals(56789, statistics.getRight());
assertEquals(-98765, statistics.getBottom());
}
@Test
public void testMerge() {
TripStatistics statistics2 = new TripStatistics();
statistics.setStartTime(1000L); // Resulting start time
@@ -68,38 +63,32 @@ public class TripStatisticsTest extends TestCase {
statistics2.setMaxGrade(35.0); // Resulting max grade
statistics2.setMinGrade(0.0);
// Resulting bounds: -10000, 35000, 30000, -40000
statistics.setBounds(-10000, 20000, 30000, -40000);
statistics2.setBounds(-5000, 35000, 0, 20000);
statistics.merge(statistics2);
assertEquals(1000L, statistics.getStartTime());
assertEquals(4000L, statistics.getStopTime());
assertEquals(2500L, statistics.getTotalTime());
assertEquals(1300L, statistics.getMovingTime());
assertEquals(1100.0, statistics.getTotalDistance());
assertEquals(900.0, statistics.getTotalElevationGain());
assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed());
assertEquals(-10000, statistics.getLeft());
assertEquals(30000, statistics.getRight());
assertEquals(35000, statistics.getTop());
assertEquals(-40000, statistics.getBottom());
assertEquals(1200.0, statistics.getMinElevation());
assertEquals(3575.0, statistics.getMaxElevation());
assertEquals(-25.0, statistics.getMinGrade());
assertEquals(35.0, statistics.getMaxGrade());
Assert.assertEquals(1000L, statistics.getStartTime());
Assert.assertEquals(4000L, statistics.getStopTime());
Assert.assertEquals(2500L, statistics.getTotalTime());
Assert.assertEquals(1300L, statistics.getMovingTime());
Assert.assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
Assert.assertEquals(900.0, statistics.getTotalElevationGain(), 0.001);
Assert.assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed(), 0.001);
Assert.assertEquals(1200.0, statistics.getMinElevation(), 0.001);
Assert.assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
Assert.assertEquals(-25.0, statistics.getMinGrade(), 0.001);
Assert.assertEquals(35.0, statistics.getMaxGrade(), 0.001);
}
@Test
public void testGetAverageSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setTotalTime(50000); // in milliseconds
assertEquals(20.0, statistics.getAverageSpeed());
Assert.assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
}
@Test
public void testGetAverageMovingSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setMovingTime(20000); // in milliseconds
assertEquals(50.0, statistics.getAverageMovingSpeed());
Assert.assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
}
}
@@ -15,9 +15,10 @@
*/
package de.dennisguse.opentracks.util;
import android.os.Environment;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.File;
@@ -26,82 +27,83 @@ import java.io.File;
*
* @author Rodrigo Damazio
*/
public class FileUtilsTest extends TestCase {
@RunWith(JUnit4.class)
public class FileUtilsTest {
/**
* Tests {@link FileUtils#getPath(String...)}.
*/
public void testBuildExternalDirectoryPath() {
String expectedName = Environment.getExternalStorageDirectory() + File.separator
+ FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator
+ "c";
String dirName = FileUtils.getPath("a", "b", "c");
assertEquals(expectedName, dirName);
}
/**
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the
* file is new.
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file is new.
*/
@Test
public void testBuildUniqueFileName_new() {
String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext");
assertEquals("Filename.ext", filename);
Assert.assertEquals("Filename.ext", filename);
}
/**
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the
* file exists already.
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file exists already.
*/
@Test
public void testBuildUniqueFileName_exist() {
// Expect "/default.prop" to exist on the phone/emulator
String filename = FileUtils.buildUniqueFileName(new File("/"), "default", "prop");
assertEquals("default(1).prop", filename);
Assert.assertEquals("default(1).prop", filename);
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with special characters.
* Verifies that they are sanitized.
*/
@Test
public void testSanitizeFileName() {
String name = "Swim\10ming-^across:/the/ pacific (ocean).";
String expected = "Swim_ming-^across_the_ pacific (ocean)_";
assertEquals(expected, FileUtils.sanitizeFileName(name));
String expected = "Swim_ming-^across_the_ pacific (ocean).";
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
@Test
public void testSanitizeFileNameWithSuffix() {
String name = "FileName.jpeg";
Assert.assertEquals(name, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in
* Chinese and Russian). Verifies that they are allowed.
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
* Verifies that they are allowed.
*/
@Test
public void testSanitizeFileName_i18n() {
String name = "您好-привет";
String expected = "您好-привет";
assertEquals(expected, FileUtils.sanitizeFileName(name));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32
* characters. Verifies that they are allowed.
* Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32 characters.
* Verifies that they are allowed.
*/
@Test
public void testSanitizeFileName_special_characters() {
String name = "$%'-_@~`!(){}^#&+,;=[] ";
String expected = "$%'-_@~`!(){}^#&+,;=[] ";
assertEquals(expected, FileUtils.sanitizeFileName(name));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped
* characters in a row. Verifies that they are collapsed into one underscore.
* Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped characters in a row.
* Verifies that they are collapsed into one underscore.
*/
@Test
public void testSanitizeFileName_collapse() {
String name = "hello//there";
String expected = "hello_there";
assertEquals(expected, FileUtils.sanitizeFileName(name));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#truncateFileName(File, String, String)}. Verifies
* the a long file name is truncated.
* Tests {@link FileUtils#truncateFileName(File, String, String)}.
* Verifies the a long file name is truncated.
*/
@Test
public void testTruncateFileName() {
File directory = new File("/dir1/dir2/");
String suffix = ".gpx";
@@ -113,9 +115,8 @@ public class FileUtilsTest extends TestCase {
String truncated = FileUtils.truncateFileName(directory, nameString, suffix);
for (int i = 0; i < truncated.length(); i++) {
assertEquals('a', truncated.charAt(i));
Assert.assertEquals('a', truncated.charAt(i));
}
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH,
new File(directory, truncated + suffix).getPath().length());
Assert.assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
}
}
@@ -131,7 +131,7 @@ public class StringUtilsTest {
assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300);
assertGetTime("2010-05-04T03:02:01.35", 2010, 5, 4, 3, 2, 1, 350);
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
assertGetTime("2010-05-04T03:02:01.3525Z", 2010, 5, 4, 3, 2, 1, 352);
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 353);
}
/**
@@ -149,8 +149,7 @@ public class StringUtilsTest {
}
/**
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time
* zone.
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time zone.
*/
@Test
public void testGetTime_fractionalAndTimezone() {
@@ -161,8 +160,7 @@ public class StringUtilsTest {
}
/**
* Asserts the {@link StringUtils#getTime(String)} returns the expected
* values.
* Asserts the {@link StringUtils#getTime(String)} returns the expected values.
*
* @param xmlDateTime the xml date time string
* @param year the expected year
@@ -173,8 +171,7 @@ public class StringUtilsTest {
* @param second the expected second
* @param millisecond the expected milliseconds
*/
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute,
int second, int millisecond) {
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute, int second, int millisecond) {
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.set(year, month - 1, day, hour, minute, second);
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
@@ -185,7 +182,7 @@ public class StringUtilsTest {
* Tests {@link StringUtils#getTimeParts(long)} with a positive number.
*/
@Test
public void testGetTimeParts_postive() {
public void testGetTimeParts_positive() {
int[] parts = StringUtils.getTimeParts(61000);
Assert.assertEquals(1, parts[0]);
Assert.assertEquals(1, parts[1]);
@@ -202,4 +199,24 @@ public class StringUtilsTest {
Assert.assertEquals(-1, parts[1]);
Assert.assertEquals(0, parts[2]);
}
@Test
public void testFormatDecimal() {
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 0));
Assert.assertEquals("0", StringUtils.formatDecimal(0.1, 0));
Assert.assertEquals("1", StringUtils.formatDecimal(1.1, 0));
Assert.assertEquals("10", StringUtils.formatDecimal(10, 0));
Assert.assertEquals("10", StringUtils.formatDecimal(10.1, 0));
Assert.assertEquals("0", StringUtils.formatDecimal(-0.1, 0));
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 2));
Assert.assertEquals("0.1", StringUtils.formatDecimal(0.1, 2));
Assert.assertEquals("1.1", StringUtils.formatDecimal(1.1, 2));
Assert.assertEquals("10", StringUtils.formatDecimal(10, 2));
Assert.assertEquals("10.1", StringUtils.formatDecimal(10.1, 2));
Assert.assertEquals("10.11", StringUtils.formatDecimal(10.111, 2));
Assert.assertEquals("-0.1", StringUtils.formatDecimal(-0.1, 2));
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
}
}
@@ -45,8 +45,7 @@ public class TrackNameUtilsTest {
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
/**
* Tests when the track_name_key is
* settings_recording_track_name_date_local_value.
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
*/
@Test
public void testTrackName_date_local() {
@@ -55,8 +54,7 @@ public class TrackNameUtilsTest {
}
/**
* Tests when the track_name_key is
* settings_recording_track_name_date_iso_8601_value.
* Tests when the track_name_key is settings_recording_track_name_date_iso_8601_value.
*/
@Test
public void testTrackName_date_iso_8601() {
@@ -66,8 +64,7 @@ public class TrackNameUtilsTest {
}
/**
* Tests when the track_name_key is
* settings_recording_track_name_number_value.
* Tests when the track_name_key is settings_recording_track_name_number_value.
*/
@Test
public void testTrackName_number() {
+66 -109
View File
@@ -14,75 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="de.dennisguse.opentracks"
android:installLocation="auto"
android:versionCode="2947"
android:versionName="v3.0.1"> <!-- For FDroid. -->
android:versionCode="3243"
android:versionName="v3.3.0"> <!-- For FDroid. -->
<!-- Permission to initialize services -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Permissions to record locations -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Permissions to access sensors -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- Permission to read/write external storage -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Required features -->
<uses-feature
android:name="android.hardware.location.gps"
android:required="true" />
<!-- Optional features -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.faketouch"
android:required="false" />
<uses-feature
android:name="android.hardware.sensor.compass"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony.gsm"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen.multitouch"
android:required="false" />
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="true" />
<application
android:name=".InitPreferences"
android:name=".Startup"
android:allowBackup="false"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_logo"
android:label="@string/app_name"
android:theme="@style/ThemeCustom"
tools:replace="android:label">
android:icon="${applicationLogo}"
android:label="${applicationName}"
android:theme="@style/ThemeCustom">
<!-- Activities -->
<activity android:name=".AboutActivity" />
<activity
android:name=".AggregatedStatsActivity"
android:label="@string/menu_aggregated_statistics" />
<activity android:name=".DeleteActivity" />
<activity
android:name=".HelpActivity"
android:label="@string/menu_help" />
<activity
android:name=".io.file.importer.ImportActivity"
android:theme="@style/ThemeCustomTransparent">
@@ -100,8 +53,11 @@ limitations under the License.
<data android:scheme="file" />
</intent-filter>
</activity>
<activity android:name=".MarkerEditActivity" />
<activity android:name=".MarkerDetailActivity" />
<activity android:name=".MarkerEditActivity" />
<activity
android:name=".MarkerListActivity"
android:label="@string/menu_markers">
@@ -109,6 +65,7 @@ limitations under the License.
android:name="android.app.default_searchable"
android:value=".SearchListActivity" />
</activity>
<activity
android:name=".SearchListActivity"
android:launchMode="singleTop">
@@ -117,10 +74,15 @@ limitations under the License.
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/search" />
android:resource="@xml/searchable" />
</activity>
<activity android:name=".TrackDetailActivity" />
<activity
android:name=".TrackDetailActivity"
android:screenOrientation="userPortrait" />
<activity android:name=".TrackEditActivity" />
<activity android:name=".TrackListActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -130,59 +92,18 @@ limitations under the License.
android:name="android.app.default_searchable"
android:value=".SearchListActivity" />
</activity>
<activity
android:name=".io.file.exporter.ExportActivity"
android:theme="@style/ThemeCustomTransparent" />
<activity android:name=".settings.RecordingSettingsActivity" />
<activity android:name=".settings.SettingsActivity" />
<activity android:name=".settings.StatsSettingsActivity" />
<activity
android:name=".widgets.TrackWidgetConfigActivity"
android:label="@string/track_widget_config_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<service
android:name=".services.ControlRecordingService"
android:exported="false" />
<service
android:name=".services.TrackRecordingService"
android:enabled="true"
android:exported="false"
android:icon="@drawable/ic_arrow_320"
android:label="@string/recording_service" />
<!-- Receivers -->
<receiver android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".widgets.TrackWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="TRACK_PAUSED" />
<action android:name="TRACK_RESUMED" />
<action android:name="TRACK_STARTED" />
<action android:name="TRACK_STOPPED" />
<action android:name="TRACK_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/track_widget_info" />
</receiver>
<!-- Providers -->
<activity android:name=".settings.SettingsActivity" />
<provider
android:name=".content.CustomContentProvider"
android:authorities="${applicationId}"
android:name=".content.ShareContentProvider"
android:authorities="${applicationId}.content"
android:exported="false"
android:grantUriPermissions="true" />
<!-- Search suggestion provider -->
<provider
android:name=".content.SearchEngineProvider"
android:authorities="${applicationId}.search"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
@@ -193,5 +114,41 @@ limitations under the License.
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service
android:name=".services.TrackRecordingService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location|connectedDevice"
android:icon="@drawable/ic_logo_color_24dp"
android:label="@string/recording_service" />
</application>
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="true" />
<!-- Permissions to record locations -->
<uses-feature
android:name="android.hardware.location.gps"
android:required="true" />
<uses-feature
android:name="android.hardware.android.hardware.bluetooth_le"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.sensor.compass"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
</manifest>
@@ -17,36 +17,32 @@
package de.dennisguse.opentracks;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.view.Menu;
import androidx.fragment.app.FragmentActivity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TTSUtils;
/**
* @author Jimmy Shih
*/
public abstract class AbstractActivity extends FragmentActivity {
public abstract class AbstractActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set volume control stream for text to speech
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
setVolumeControlStream(TTSUtils.getTTSStream());
setContentView(getLayoutResId());
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
/**
* Gets the layout resource id.
*/
protected abstract int getLayoutResId();
@Override
public boolean onCreateOptionsMenu(Menu menu) {
TrackIconUtils.setMenuIconColor(menu);
return super.onCreateOptionsMenu(menu);
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.content.Intent;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* An abstract class for the following common tasks across
* {@link TrackListActivity}, {@link TrackDetailActivity}, and
* {@link SearchListActivity}:
* <p>
* - share track <br>
* - delete tracks <br>
*
* @author Jimmy Shih
*/
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
private static final String TAG = AbstractListActivity.class.getSimpleName();
protected static final int GPS_REQUEST_CODE = 6;
private static final int DELETE_REQUEST_CODE = 3;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == DELETE_REQUEST_CODE) {
onDeleted();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
/**
* Delete tracks.
*
* @param trackIds the track ids
*/
protected void deleteTracks(long[] trackIds) {
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
}
@Override
public void onConfirmDeleteDone(long[] trackIds) {
boolean stopRecording = false;
if (trackIds.length == 1 && trackIds[0] == -1L) {
stopRecording = true;
} else {
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (long trackId : trackIds) {
if (trackId == recordingTrackId) {
stopRecording = true;
break;
}
}
}
if (stopRecording) {
getTrackRecordingServiceConnection().stopRecording(this, false);
}
Intent intent = IntentUtils.newIntent(this, DeleteActivity.class);
intent.putExtra(DeleteActivity.EXTRA_TRACK_IDS, trackIds);
startActivityForResult(intent, DELETE_REQUEST_CODE);
}
/**
* Gets the track recording service connection.
* For stopping the current recording if need to delete the current recording track.
*/
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection();
/**
* Called after {@link DeleteActivity} returns its result.
*/
abstract protected void onDeleted();
}
@@ -1,63 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.os.Bundle;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.R;
import java.util.List;
/**
* An activity to view aggregated stats from all recorded tracks.
*
* @author Fergus Nelson
*/
public class AggregatedStatsActivity extends AbstractActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatsUtils.setTripStatisticsValues(this, this, null, getTripStatistics(), null);
StatsUtils.setLocationValues(this, this, null, null, false);
}
@Override
protected int getLayoutResId() {
return R.layout.stats;
}
/**
* Gets the aggregated trip statistics for all the recorded tracks or null if
* there is no track.
*/
private TripStatistics getTripStatistics() {
List<Track> tracks = ContentProviderUtils.Factory.get(this).getAllTracks();
TripStatistics tripStatistics = null;
if (!tracks.isEmpty()) {
tripStatistics = new TripStatistics(tracks.iterator().next().getTripStatistics());
for (int i = 1; i < tracks.size(); i++) {
tripStatistics.merge(tracks.get(i).getTripStatistics());
}
}
return tripStatistics;
}
}
@@ -1,57 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import de.dennisguse.opentracks.services.TrackRecordingService;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
/**
* This class react to the BOOT_COMPLETED broadcast.
* <p>
* One example of a broadcast message that this class is interested in,
* is notification about the phone boot. We may want to resume a previously
* started tracking session if the phone crashed (hopefully not), or the user
* decided to swap the battery or some external event occurred which forced
* a phone reboot.
* <p>
* This class simply delegates to {@link TrackRecordingService} to make a
* decision whether to continue with the previous track (if any), or just
* abandon it.
*
* @author Bartlomiej Niechwiej
*/
public class BootReceiver extends BroadcastReceiver {
private static final String TAG = BootReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "BootReceiver.onReceive: " + intent.getAction());
if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Intent startIntent = new Intent(context, TrackRecordingService.class)
.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
context.startService(startIntent);
} else {
Log.w(TAG, "BootReceiver: unsupported action");
}
}
}
@@ -1,33 +0,0 @@
/*
* Copyright 2008 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;
/**
* Common constants.
*
* @author Leif Hendrik Wilden
*/
public class Constants {
/**
* Maximum number of waypoints that will be loaded at one time.
*/
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
private Constants() {
}
}

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