Compare commits

...

155 Commits

Author SHA1 Message Date
Dennis Guse d240b457e2 Release v3.6.0 2020-04-10 14:55:04 +02:00
Dennis Guse 6d800caab1 While importing photos, check url carefully.
(cherry picked from commit 309c96e4789bd7623b84720a8c8f2bf297c95d59)
2020-04-10 14:42:11 +02:00
Román 7bf153db39 Avoid NullPointer in creating photoUrl when importing 2020-04-10 14:42:11 +02:00
Román 1b69f29751 Importing creates content:// for attached pictures. Also, exporting without points bug fixed and images href contains relative link. Fixes #166 and #176. 2020-04-10 14:42:11 +02:00
Dennis Guse dfc3837739 Settings: PrefsFragment could not be re-created. Fixes #179. 2020-04-08 08:10:46 +02:00
Dennis Guse 30e847866e Removed TravisCI it does not work as desired. 2020-04-07 21:01:54 +02:00
Dennis Guse fbc902e830 Cleanup using AndroidStudio code analyzer.
Moved to iterator for loops for ChartView and ChartFragment.
2020-04-05 21:56:26 +02:00
Dennis Guse 208f1d71ba Refactor ChartView/ChartFragment to use ChartPoint. 2020-04-05 21:56:26 +02:00
Dennis Guse 38a15a96b3 Cleanup. 2020-04-05 21:56:26 +02:00
Dennis Guse 46f8753746 Removed TrackDataType and simplified TrackDataManager. 2020-04-05 21:56:26 +02:00
Dennis Guse 70bc8f33b1 Do not expose TrackDataType to fragments. 2020-04-05 21:56:26 +02:00
Dennis Guse 6cd27ab096 Merged DataSourceManager into TrackDataHub. 2020-04-05 21:56:26 +02:00
Dennis Guse 5581b404f7 DataSourceManager now always registers for all changes. 2020-04-05 21:56:26 +02:00
Dennis Guse 2ef74eb908 Automated code cleanup using AndroidStudio. 2020-04-05 21:56:26 +02:00
Dennis Guse 16c654141f Renamed TripStatistics to TrackStatistics. 2020-04-05 21:56:26 +02:00
Dennis Guse dfc785f2fd Simplified internal data processing (class structure). 2020-04-05 21:56:26 +02:00
Dennis Guse f621cdf032 Removed grade internals completely. 2020-04-05 21:56:26 +02:00
Román 8b04d9c4a6 creates a segment like pause/continue. 2020-04-05 14:25:29 +02:00
Dennis Guse 364b4702e3 Waypoint requires a non-null location.
Remove setLocation() from Waypoint and TrackPoint.
2020-03-30 19:52:06 +02:00
Dennis Guse a98c16de28 Removed TrackPointFactory.
New TrackPoints are now always created and old ones not reused.
2020-03-30 19:52:06 +02:00
Dennis Guse 881bf1982a Removed TrackWriterTrackPointFactory.
Replaced with local variable for TrackPoint swapping.
2020-03-30 19:52:06 +02:00
Dennis Guse f340622977 Automated code analysis improvements. 2020-03-30 19:52:06 +02:00
Dennis Guse 194379295d TrackPoint has Location as delegate instead of inheritance. 2020-03-30 19:52:06 +02:00
Dennis Guse 901cffee97 Use TrackPoint instead of Location (and omit casting). 2020-03-30 19:52:06 +02:00
Román d93c2ec5f3 TrackDetailActivity starts an actual recording and a stopped recording can be resumed. Fixes #103 and #11. 2020-03-30 19:25:22 +02:00
Dennis Guse 306522863a Release v3.5.0 2020-03-21 23:18:57 +01:00
Dennis Guse e54d85e04d ChartFragment: unregister from preference changes on pause. 2020-03-21 20:54:49 +01:00
Dennis Guse cb30ad27c3 Removed that tracks store count of the locations. 2020-03-20 19:44:31 +01:00
Dennis Guse 8685c9cc88 Merge pull request #162 from rgmf/issue#89
Share pictures from marker.
2020-03-18 22:08:42 +01:00
Román a6f03d0f23 Share pictures from marker.
Fixes #89.
2020-03-18 19:33:57 +01:00
Dennis Guse e3c812b155 TrackDeleteActivity now has a transparant background. 2020-03-18 07:43:21 +01:00
Dennis Guse f149de9ce3 Merge pull request #167 from rgmf/issue#163
Add android:key in PreferenceCategory with expandable.
2020-03-17 21:54:08 +01:00
Dennis Guse 3447dd2569 Merge pull request #165 from OpenTracksApp/dbCleanup
Database cleanup and optimization
2020-03-17 21:44:28 +01:00
Dennis Guse b2b8c7fd54 Database upgrade (incl. tests): removed unused columns and added indices.
Fixes #108.
2020-03-17 21:39:10 +01:00
Román 486bcd0aec Add android:key in PreferenceCategory with expandable.
Fixes #163.
2020-03-17 10:57:04 +01:00
Dennis Guse c8a33201f5 Moved annoucement text generation into util class. 2020-03-16 21:43:23 +01:00
Dennis Guse 4a37c2cc7e Cleanup. 2020-03-16 21:43:23 +01:00
Dennis Guse d23db5bafd Charts use preferred units and rate.
Fixes #150.
2020-03-16 21:43:23 +01:00
Dennis Guse ac528f9a10 Use trackname for sharing files.
Fixes #130.
2020-03-16 21:43:23 +01:00
Dennis Guse 256eadd49d Added some french translation provided by @edern-appere.
Fixes #147.
2020-03-16 21:43:23 +01:00
Dennis Guse 64e58934a5 Settings: fix label for elevation (removed "Grade" as it is not shown anymore). 2020-03-16 21:43:23 +01:00
Dennis Guse 92dfadfdba Remove elevation min/max from UI as GPS-derived data often lead to invalid data (especially initial fix phase).
Fixes #148.
2020-03-16 21:43:23 +01:00
Román cbce70143b All marker management is now in MarkerEditActivity (including take photo and added gallery image to the marker).
Issue 122.
2020-03-11 19:59:15 +01:00
Román f4b9e3159e New screenshots for F-Droid/PlayStore. Fixes #132. 2020-03-11 19:40:57 +01:00
Dennis Guse 928781391d Release v3.4.0 2020-02-05 23:41:55 +01:00
Dennis Guse 05cd672464 Updated help text. 2020-02-05 23:41:50 +01:00
Dennis Guse d3109bbc37 Cleanup. 2020-02-05 23:08:17 +01:00
Dennis Guse eabdb7c721 Unify order of show on map and share menu entries. 2020-02-04 19:52:05 +01:00
Peter Storch ad990e8cd7 Forward multiple tracks to dashboard 2020-02-04 00:07:17 +01:00
Dennis Guse db3eef0104 Test: date comparison tends to be flaky. 2020-02-03 23:27:19 +01:00
Dennis Guse c4f1ba7962 Upgrade material design dependency.
Requires including androidx.documentfile directly.
2020-02-03 23:27:19 +01:00
Dennis Guse 7451fbd413 CI: enable testing via Github Actions. 2020-02-03 23:27:19 +01:00
Dennis Guse b6c55de55a CI: enable testing TravisCI on again. 2020-02-03 23:27:19 +01:00
Dennis Guse dd3ee6d99e Bugfix: match internal ContentURI correctly.
TrackPoint by TrackId was not working (and not used).
2020-02-03 21:32:06 +01:00
Dennis Guse bbb6a7c22c Bugfix: keep TrackRecordingService in foreground if paused. 2020-02-03 20:45:14 +01:00
Dennis Guse dc1b50a7bf Cleanup: move PAUSE_LATITUDE and RESUME_LATITUDE to TrackPointsColumn. 2020-02-03 20:45:14 +01:00
Dennis Guse 21bfb88e0b Bugfix: initialize fragments from bundle rather via constructor.
Introduced in 8b11e1d611
This fixes #127.
2020-02-03 19:51:04 +01:00
Dennis Guse aaab8adce9 ExportAsyncTask: always get a wakelock. 2020-02-03 19:51:04 +01:00
Dennis Guse df502f835c Release v3.3.2 2020-01-11 20:33:47 +01:00
Dennis Guse dd6a4cd153 Export: files are now overwritten. 2020-01-11 20:29:20 +01:00
Dennis Guse 76656997ef Sharing: check if a compatible app is installed rather than trying it. 2020-01-11 20:27:43 +01:00
Dennis Guse 32070a0bd6 Use java.time instead of custom code for parsing (requires API 26+).
This is a functional change on API26+ as a timezone identifier is required.
2020-01-11 19:31:58 +01:00
Dennis Guse b9420d4dde Removed LocationManagerConnector. 2020-01-11 19:31:58 +01:00
Dennis Guse 1b810e1294 TrackRecordingService: do not try to register location listener every minute. 2020-01-11 19:31:58 +01:00
Dennis Guse f16d59bff6 Cleanup. 2020-01-11 19:31:58 +01:00
Dennis Guse b5bf3aadc5 Refactored wake lock release. 2020-01-11 19:31:58 +01:00
Dennis Guse f6a27a1522 Documentation: add activities overview. 2020-01-11 19:31:58 +01:00
Dennis Guse 83758fcd72 Always cancel TrackRecordingService's notification. 2020-01-11 19:31:58 +01:00
Dennis Guse 070917bc0b Extracted TrackRecordingService's Binder. 2020-01-11 19:31:58 +01:00
Dennis Guse 543a04dfdd Cleanup. 2020-01-11 19:31:58 +01:00
Dennis Guse b4d5eb0eec Refactoring: SharedPreferences are not provided via DataSource anymore. 2020-01-11 19:31:58 +01:00
Dennis Guse 9f84cc4952 Use ContentUris.parseId(). 2020-01-11 19:31:58 +01:00
Dennis Guse 09fe35f1ca Cleanup. 2020-01-11 19:31:58 +01:00
Dennis Guse 27e50b2dd9 Moved ContentProvider into separate package. 2020-01-11 19:31:58 +01:00
Dennis Guse 971460848b Extracted LocationIterator from ContentProviderUtils.
Also refactored LocationFactory to TrackPointFactory.
2020-01-11 19:31:58 +01:00
Dennis Guse 8bc2937bb4 Extracted LocationIterator from ContentProviderUtils. 2020-01-11 19:31:58 +01:00
Dennis Guse 0e9b68c2a1 Cleanup. 2020-01-11 19:31:58 +01:00
Nathan Hearnsberger ba298c2afb Additional language support for export_with/without_photos (DE, ES). 2020-01-11 19:27:53 +01:00
Nathan Hearnsberger 79f5ed4dc3 Added KMZ without photos, photo details to export picker. 2020-01-11 19:27:53 +01:00
Dennis Guse bbb2f874ee Release v3.3.1 2020-01-03 18:11:57 +01:00
Dennis Guse 9af3759b07 Bugfix: app name (release) not shown.
Introduced in 6cc7271a41.
2020-01-03 17:54:15 +01:00
Dennis Guse 1f6fe6bf0c Android 10: on uninstall ask the user if the app data should be kept. 2020-01-03 17:50:07 +01:00
Dennis Guse 7b291e85d7 Renamed DeleteActivity to TrackDeleteActivity. 2020-01-03 17:04:51 +01:00
Dennis Guse 0f72066bc0 Add IntentChooser for dashboards. 2020-01-02 19:56:24 +01:00
Dennis Guse 70e0e7b216 Add README for data API. 2020-01-02 19:54:19 +01:00
Peter Storch 7e50bb1b6d Merge pull request #114 from pstorch/79_osm_plugin
integrate dashboard plugin
2020-01-02 17:56:07 +01:00
Peter Storch a3eb107068 integrate dashboard plugin mechanism
To be able to integrate the OSMDashboard https://github.com/OpenTracksApp/OSMDashboard,
which addresses #79. Also other Dashboards will be possible.
2020-01-02 17:45:01 +01:00
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
287 changed files with 7660 additions and 23290 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
+21
View File
@@ -0,0 +1,21 @@
#https://github.com/marketplace/actions/android-emulator-runner
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
strategy:
matrix:
api-level: [21, 29]
target: [default]
steps:
- name: checkout
uses: actions/checkout@v2
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
script: ./gradlew connectedCheck
+6 -7
View File
@@ -45,19 +45,16 @@ captures/
# IntelliJ IDEA
*.iml
*.iws
/out/
# User-specific configurations
.idea/.name
.idea/assetWizardSettings.xml
.idea/caches/
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/dataSources.ids
.idea/datasources.xml
.idea/dictionaries
.idea/dynamic.xml
.idea/encodings.xml
.idea/jsLibraryMappings.xml
@@ -67,14 +64,16 @@ captures/
.idea/scopes/scope_settings.xml
.idea/shelf/
.idea/sqlDataSources.xml
.idea/tasks.xml
.idea/vcs.xml
.idea/uiDesigner.xml
.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
-45
View File
@@ -1,45 +0,0 @@
#See https://docs.travis-ci.com/user/languages/android/
#See https://github.com/ankidroid/Anki-Android/blob/master/.travis.yml
language: android
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
# 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 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
-16
View File
@@ -1,16 +0,0 @@
# OpenTrack: Developer information
## Create releases for FDroid
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)
## Translations (localization)
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
+3
View File
@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"
+29 -8
View File
@@ -2,17 +2,38 @@
_OpenTracks_ is a sport tracking application that completely respects your privacy.
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="60">
</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 (also with photos)
* __Heart rate:__ supports bluetooth LE sensors
* __Heart rate:__ supports Bluetooth LE sensors
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
* __No Internet access:__ Internet is not used
* __Voice annoucements__
* __Voice announcements__
* __No advertising__
* __Privacy:__
* _does not contain any in-app analytics_
@@ -26,9 +47,9 @@ _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.
<a href="https://liberapay.com/dennis.guse/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
</a>
## Custom Dashboards
As of v3.3.1 OpenTracks enables to use custom dashboards.
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
## Screenshots
<div>
+30
View File
@@ -0,0 +1,30 @@
# OpenTracks Data API
## Reason
OpenTracks by itself only provides basic features for data visualization and also lacks functionality for analytics.
By itself OpenTracks provides a basic dashboard that can show summary of a track (while recording as well as afterwards).
Although this might be (mostly) sufficient, it has some limitations.
This include limited configurability (e.g., ordering of shown elements) and the lack of Internet access.
In fact, the later is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
_NOTE:_ for showing a track on a map a workaround was implemented in OpenTracks.
Data could be exported as KMZ and presented in 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)).
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
## Implementation
OpenTracks by default does not expose any data to 3rd-party applications.
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary) as well as the `TrackPoint`s of one or more selected tracks.
For each, one URI is provided.
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
Limitations:
* only tracks selected by the user are exposed,
* `Waypoints` are not exposed,
* access is only granted temporarily and automatically revoked (see `FLAG_GRANT_READ_URI_PERMISSION`),
* no write access is possible, and
* neither track recordings can be started, paused, or stopped.
All required information can be found in `IntentDashboardUtils.java`.
A _reference implementation_ is the [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard).
+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/).
+17 -1
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
@@ -54,6 +54,11 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
manifestPlaceholders = [
applicationLogo: "@drawable/ic_logo_color_24dp",
applicationName: "@string/app_name"
]
//Useful for debugging.
//versionCode getVersionCode()
@@ -69,11 +74,20 @@ android {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
manifestPlaceholders = [
applicationLogo: "@drawable/ic_logo_black_24dp",
applicationName: "OpenTracks (Debug)"
]
}
release {
minifyEnabled false
}
releasePlayStore {
applicationIdSuffix ".playstore"
versionNameSuffix "-PlayStore"
}
}
applicationVariants.all { variant ->
@@ -84,6 +98,7 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
@@ -95,4 +110,5 @@ dependencies {
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.mockito:mockito-core:3.0.0'
implementation 'com.google.android.material:material:1.1.0'
}
+40
View File
@@ -0,0 +1,40 @@
@startuml
title
State of OpenTracks
end title
legend right
SettingsActivity can be reached
from every activity showing a menu.
endlegend
[*] --> TrackListActivity
TrackListActivity --> TrackDetailActivity : Start recording
TrackListActivity --> SearchListActivity
TrackListActivity --> MarkerListActivity : Show all markers
TrackListActivity --> TrackDetailActivity : Show track
TrackListActivity --> TrackDeleteActivity
SearchListActivity --> MarkerDetailActivity : Search result
SearchListActivity --> TrackDetailActivity : Search result
TrackDetailActivity --> TrackDeleteActivity
TrackDetailActivity --> TrackEditActivity
TrackDetailActivity --> MarkerListActivity
TrackDetailActivity --> MarkerEditActivity : Add marker
TrackDetailActivity --> CameraIntent : Add marker (with photo)
TrackDetailActivity --> ShareFileIntent
TrackDetailActivity --> DashboardIntent : Database cursor
TrackDetailActivity --> TrackOnMapIntent : KMZ
MarkerListActivity --> MarkerDetailActivity
MarkerDetailActivity --> MarkerEditActivity
MarkerDetailActivity --> CoordinateOnMapIntent : URI
SettingsActivity --> AboutActivity
SettingsActivity --> HelpActivity
SettingsActivity --> ExportActivity
SettingsActivity --> ImportActivity
@enduml
+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
+1 -1
View File
@@ -17,7 +17,7 @@
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title></dc:title>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

+1 -1
View File
@@ -43,7 +43,7 @@
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title></dc:title>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

+1 -1
View File
@@ -41,7 +41,7 @@
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title></dc:title>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+1 -1
View File
@@ -33,7 +33,7 @@
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title></dc:title>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+1 -1
View File
@@ -39,7 +39,7 @@
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title></dc:title>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

+1 -1
View File
@@ -18,7 +18,7 @@
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title></dc:title>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+1 -1
View File
@@ -21,7 +21,7 @@
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:title></dc:title>
<dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.8 KiB

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,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,12 @@
v3.3.1: OpenTracks
Changes:
- Android 10: on uninstall allow to keep app data
- Introduced Dashboard API: map icon can be used to open current track in external app
(requires installation of another app like OSMDashboard)
Bugfix:
- App label was broken (introduced in v3.3.0)
Developer:
- Documentation for Dashboard API can be found in README_DEVELOPER.md
@@ -0,0 +1,11 @@
v3.3.2: OpenTracks
Changes:
- Export: overwrites files now
Bugfix:
- Always remove recording notification
Developer:
- On API 26+ java.time is used for parsing and writing dates
- Refactoring of TrackRecordingService
@@ -0,0 +1,12 @@
v3.4.0: OpenTracks
Changes:
- Dashboard API: multiple tracks
- Updated help
Bugfix:
- Fixed crash on fragment re-creation
- When paused: prevent stopping app
Developer:
- Enable testing via CI (Github Actions and TravisCI)
@@ -0,0 +1,14 @@
v3.5.0: OpenTracks
Changes:
- Marker: select pictures from gallery
- Marker: share picture
- Use track name as filename when sharing
- Removed elevation min/max (was imprecise)
Bugfix:
- Charts use preferred units and rate
Developer:
- Database: remove unused columns
- Database: add indices
@@ -0,0 +1,10 @@
v3.6.0: OpenTracks
Changes:
- Stopped tracks can now be resumed
Bugfix:
- Import of KMZ did not process the photo URL correctly
Developer:
- Simplified data processing
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

@@ -1 +1 @@
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
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,103 +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);
Assert.assertNotNull(activityManager);
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,8 +18,8 @@ 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;
/**
@@ -49,7 +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.addTrackPoint(createDefaultTrackPoint(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
}
return track;
@@ -60,27 +60,25 @@ public class TrackStubUtils {
*
* @return a track stub.
*/
public static SensorDataSetLocation createSensorDataSetLocation() {
return createSensorDataSetLocation(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE);
public static TrackPoint createDefaultTrackPoint() {
return createDefaultTrackPoint(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.
*/
private static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, double altitude) {
// Initial Location
Location loc = new Location(LOCATION_PROVIDER);
loc.setLatitude(latitude);
loc.setLongitude(longitude);
loc.setAltitude(altitude);
loc.setAccuracy(INITIAL_ACCURACY);
loc.setSpeed(INITIAL_SPEED);
loc.setTime(INITIAL_TIME);
loc.setBearing(INITIAL_BEARING);
SensorDataSet sd = new SensorDataSet(Float.NaN, Float.NaN);
private static TrackPoint createDefaultTrackPoint(double latitude, double longitude, double altitude) {
Location location = new Location(LOCATION_PROVIDER);
location.setLatitude(latitude);
location.setLongitude(longitude);
location.setAltitude(altitude);
location.setAccuracy(INITIAL_ACCURACY);
location.setSpeed(INITIAL_SPEED);
location.setTime(INITIAL_TIME);
location.setBearing(INITIAL_BEARING);
return new SensorDataSetLocation(loc, sd);
return new TrackPoint(location, new SensorDataSet(Float.NaN, Float.NaN));
}
}
@@ -15,6 +15,7 @@
*/
package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -44,7 +45,17 @@ public class ChartValueSeriesTest {
R.string.description_elevation_metric,
R.string.description_elevation_imperial,
R.color.chart_elevation_fill,
R.color.chart_elevation_border);
R.color.chart_elevation_border) {
@Override
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getElevation();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
};
}
@Test
@@ -63,8 +74,8 @@ public class ChartValueSeriesTest {
@Test
public void testSmallUpdates() {
series.update(0);
series.update(10);
series.update(new ChartPoint(0));
series.update(new ChartPoint(10));
series.updateDimension();
Assert.assertEquals(100, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue());
@@ -73,8 +84,8 @@ public class ChartValueSeriesTest {
@Test
public void testBigUpdates() {
series.update(0);
series.update(901);
series.update(new ChartPoint(0));
series.update(new ChartPoint(901));
series.updateDimension();
Assert.assertEquals(1000, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue());
@@ -83,8 +94,8 @@ public class ChartValueSeriesTest {
@Test
public void testNotZeroBasedUpdates() {
series.update(220);
series.update(250);
series.update(new ChartPoint(220));
series.update(new ChartPoint(250));
series.updateDimension();
Assert.assertEquals(100, series.getInterval());
Assert.assertEquals(200, series.getMinMarkerValue());
@@ -1,145 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import de.dennisguse.opentracks.content.CustomContentProvider.DatabaseHelper;
/**
* Tests {@link CustomContentProvider}.
*
* @author Youtao Liu
*/
public class CustomContentProviderTest {
private static final String DATABASE_NAME = "test.db";
private SQLiteDatabase db;
private CustomContentProvider customContentProvider;
private Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
context.deleteDatabase(DATABASE_NAME);
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
customContentProvider = new CustomContentProvider() {
};
}
/**
* Tests {@link CustomContentProvider.DatabaseHelper#onCreate(SQLiteDatabase)}.
*/
@Test
public void testDatabaseHelper_OnCreate() {
Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME));
Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME));
Assert.assertTrue(hasTable(WaypointsColumns.TABLE_NAME));
}
/**
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
*/
@Test
public void testOnCreate() {
Assert.assertTrue(customContentProvider.onCreate(context));
}
/**
* Tests {@link CustomContentProvider#getType(Uri)}.
*/
@Test
public void testGetType() {
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI));
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
}
/**
* Creates a table, containing one column.
*
* @param table the table name
*/
private void createTable(String table) {
db.execSQL("CREATE TABLE " + table + " (test INTEGER)");
}
/**
* Drops a table in database.
*
* @param table the table name
*/
private void dropTable(String table) {
db.execSQL("Drop TABLE " + table);
}
/**
* Returns true if the table exists.
*
* @param table the table name
*/
private boolean hasTable(String table) {
try {
db.rawQuery("select count(*) from " + table, null);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Returns true if the column in the table exists.
*
* @param table the table name
* @param column the column name
*/
private boolean hasColumn(String table, String column) {
try {
db.execSQL("SElECT count(*) from " + table + " order by " + column);
} catch (Exception e) {
if (e.getMessage().contains("no such column")) {
return false;
}
}
return true;
}
/**
* Sets up upgrade.
*
* @param oldVersion thd old database version
*/
private void setupUpgrade(int oldVersion) {
dropTable(TracksColumns.TABLE_NAME);
dropTable(TrackPointsColumns.TABLE_NAME);
dropTable(WaypointsColumns.TABLE_NAME);
createTable(TracksColumns.TABLE_NAME);
createTable(TrackPointsColumns.TABLE_NAME);
createTable(WaypointsColumns.TABLE_NAME);
DatabaseHelper databaseHelper = new DatabaseHelper(context);
databaseHelper.onUpgrade(db, oldVersion, CustomContentProvider.DATABASE_VERSION);
}
}
@@ -27,34 +27,35 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
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();
private final Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
descriptionGenerator = new DescriptionGeneratorImpl(ApplicationProvider.getApplicationContext());
descriptionGenerator = new DescriptionGenerator(ApplicationProvider.getApplicationContext());
}
/**
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, boolean)}.
* Tests {@link DescriptionGenerator#generateTrackDescription(Track, boolean)}.
*/
@Test
public void testGenerateTrackDescription() {
Track track = new Track();
TripStatistics stats = new TripStatistics();
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
@@ -62,13 +63,11 @@ public class DescriptionGeneratorImplTest {
stats.setMaxElevation(550);
stats.setMinElevation(-500);
stats.setTotalElevationGain(6000);
stats.setMaxGrade(0.42);
stats.setMinGrade(0.11);
stats.setStartTime(START_TIME);
track.setTripStatistics(stats);
stats.setStartTime_ms(START_TIME);
track.setTrackStatistics(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>"
@@ -84,51 +83,14 @@ public class DescriptionGeneratorImplTest {
+ "Max elevation: 550 m (1804 ft)<br>"
+ "Min elevation: -500 m (-1640 ft)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Max grade: 42 %<br>"
+ "Min grade: 11 %<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
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() {
@@ -138,7 +100,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeTime(long, StringBuilder, int, String)}.
*/
@Test
public void testWriteTime() {
@@ -148,7 +110,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteSpeed() {
@@ -158,7 +120,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeElevation(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteElevation() {
@@ -168,7 +130,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}.
*/
@Test
public void testWritePace() {
@@ -176,34 +138,4 @@ public class DescriptionGeneratorImplTest {
descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n");
Assert.assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString());
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteGrade() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeGrade(.042, builder, R.string.description_max_grade, "<br>");
Assert.assertEquals("Max grade: 4 %<br>", builder.toString());
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with a NaN.
*/
@Test
public void testWriteGrade_nan() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeGrade(Double.NaN, builder, R.string.description_max_grade, "<br>");
Assert.assertEquals("Max grade: 0 %<br>", builder.toString());
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with an infinite number.
*/
@Test
public void testWriteGrade_infinite() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeGrade(Double.POSITIVE_INFINITY, builder, R.string.description_max_grade, "<br>");
Assert.assertEquals("Max grade: 0 %<br>", builder.toString());
}
}
@@ -36,7 +36,10 @@ import java.util.List;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Tests for {@link SearchEngine}.
@@ -53,7 +56,7 @@ public class SearchEngineTest {
private ContentProviderUtils providerUtils;
private SearchEngine engine;
private Context context = ApplicationProvider.getApplicationContext();
private final Context context = ApplicationProvider.getApplicationContext();
@Before
@@ -63,7 +66,7 @@ public class SearchEngineTest {
}
@After
public void tearDown() throws Exception {
public void tearDown() {
providerUtils.deleteAllTracks(context);
}
@@ -73,17 +76,13 @@ public class SearchEngineTest {
track.setDescription(description);
track.setCategory(category);
TripStatistics stats = track.getTripStatistics();
TrackStatistics stats = track.getTrackStatistics();
if (hoursAgo > 0) {
// Started twice hoursAgo, so the average time is hoursAgo.
stats.setStartTime(NOW - hoursAgo * 1000L * 60L * 60L * 2);
stats.setStopTime(NOW);
stats.setStartTime_ms(NOW - hoursAgo * 1000L * 60L * 60L * 2);
stats.setStopTime_ms(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);
}
@@ -101,19 +100,17 @@ public class SearchEngineTest {
}
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) {
Waypoint waypoint = new Waypoint();
waypoint.setName(title);
waypoint.setDescription(description);
waypoint.setCategory(category);
waypoint.setTrackId(trackId);
Location location = new Location(HERE);
location.setLatitude(location.getLatitude() + distance);
location.setLongitude(location.getLongitude() + distance);
if (hoursAgo >= 0) {
location.setTime(NOW - hoursAgo * 1000L * 60L * 60L);
}
waypoint.setLocation(location);
Waypoint waypoint = new Waypoint(location);
waypoint.setName(title);
waypoint.setDescription(description);
waypoint.setCategory(category);
waypoint.setTrackId(trackId);
Uri uri = providerUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri);
@@ -154,7 +151,7 @@ public class SearchEngineTest {
long allMatchId = insertTrack("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
assertTrackResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
@@ -179,7 +176,7 @@ public class SearchEngineTest {
long allMatchId = insertWaypoint("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
assertWaypointResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
@@ -200,7 +197,7 @@ public class SearchEngineTest {
long trackId = insertTrack("aa", "bb", "cc");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
Assert.assertEquals(results.toString(), 3, results.size());
@@ -209,20 +206,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.
@@ -231,7 +214,7 @@ public class SearchEngineTest {
long farId = insertWaypoint("ac", 0.2);
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Distance order.
assertWaypointResults(results, nearId, farId, farFarAwayId);
@@ -244,7 +227,7 @@ public class SearchEngineTest {
long oldId = insertTrack("ac", 2);
SearchQuery query = new SearchQuery("a", null, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
assertTrackResults(results, recentId, oldId, oldestId);
@@ -258,7 +241,7 @@ public class SearchEngineTest {
long oldId = insertWaypoint("ac", 1);
SearchQuery query = new SearchQuery("a", null, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
assertWaypointResults(results, recentId, oldId, oldestId);
@@ -271,7 +254,7 @@ public class SearchEngineTest {
long otherId = insertTrack("aa", 1);
SearchQuery query = new SearchQuery("a", null, currentId, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Current track should be demoted.
assertTrackResults(results, otherId, currentId);
@@ -284,7 +267,7 @@ public class SearchEngineTest {
long currentId = insertWaypoint("ab", 1, 123);
SearchQuery query = new SearchQuery("a", null, 123, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Waypoint in current track should be promoted.
assertWaypointResults(results, currentId, otherId);
@@ -0,0 +1,43 @@
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);
for (int i = 0; i < numPoints; i++) {
track.addTrackPoint(createTrackPoint(i));
}
return track;
}
/**
* Creates a location.
*
* @param i the index to set the value of location.
* @return created location
*/
public static TrackPoint createTrackPoint(int i) {
Location location = new Location("test");
location.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
location.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
location.setAccuracy((float) i / 100.0f);
location.setAltitude(i * ALTITUDE_INTERVAL);
location.setTime(i + 1);
return new TrackPoint(location);
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content.provider;
import android.content.ContentUris;
import android.content.Context;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
/**
* Tests {@link CustomContentProvider}.
*
* @author Youtao Liu
*/
public class CustomContentProviderTest {
private CustomContentProvider customContentProvider;
private final Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
customContentProvider = new CustomContentProvider() {
};
}
/**
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
*/
@Test
public void testOnCreate() {
Assert.assertTrue(customContentProvider.onCreate(context));
}
/**
* Tests {@link CustomContentProvider#getType(Uri)}.
*/
@Test
public void testGetType() {
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
Assert.assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build()));
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
Assert.assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
Assert.assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
}
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.provider;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -33,10 +33,15 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.mockito.Mockito.when;
@@ -52,12 +57,9 @@ public class CustomContentProviderUtilsTest {
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 static final double INITIAL_LATITUDE = 37.0;
private static final double INITIAL_LONGITUDE = -57.0;
private static final double ALTITUDE_INTERVAL = 2.5;
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils;
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
@Mock
private Cursor cursorMock;
@@ -67,120 +69,92 @@ public class CustomContentProviderUtilsTest {
@Before
public void setUp() {
providerUtils = new ContentProviderUtils(context);
providerUtils.deleteAllTracks(context);
contentProviderUtils = new ContentProviderUtils(context);
contentProviderUtils.deleteAllTracks(context);
}
@Test
public void testLocationIterator_noPoints() {
testIterator(1, 0, 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_customFactory() {
final Location location = new Location("test_location");
final AtomicInteger counter = new AtomicInteger();
testIterator(1, 15, 4, false, new LocationFactory() {
@Override
public Location createLocation() {
counter.incrementAndGet();
return location;
}
});
// Make sure we were called exactly as many times as we had track points.
Assert.assertEquals(15, counter.get());
}
@Test
public void testLocationIterator_nullFactory() {
try {
testIterator(1, 15, 4, false, null);
Assert.fail("Expecting IllegalArgumentException");
} catch (IllegalArgumentException e) {
// Expected.
}
testIterator(1, 0, 1, false);
}
@Test
public void testLocationIterator_noBatchAscending() {
testIterator(1, 50, 100, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 100, false);
testIterator(2, 50, 50, false);
}
@Test
public void testLocationIterator_noBatchDescending() {
testIterator(1, 50, 100, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 100, true);
testIterator(2, 50, 50, true);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(1, 50, 11, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 11, false);
testIterator(2, 50, 25, false);
}
@Test
public void testLocationIterator_batchDescending() {
testIterator(1, 50, 11, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(1, 50, 11, true);
testIterator(2, 50, 25, true);
}
@Test
public void testLocationIterator_largeTrack() {
testIterator(1, 20000, 2000, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
testIterator(1, 20000, 2000, false);
}
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
private void testIterator(long trackId, int numPoints, int batchSize, boolean descending) {
long lastPointId = initializeTrack(trackId, numPoints);
((ContentProviderUtils) providerUtils).setDefaultCursorBatchSize(batchSize);
List<Location> locations = new ArrayList<Location>(numPoints);
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
List<TrackPoint> locations = new ArrayList<>(numPoints);
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending)) {
while (it.hasNext()) {
Location loc = it.next();
TrackPoint loc = it.next();
Assert.assertNotNull(loc);
locations.add(loc);
// Make sure the IDs are returned in the right order.
Assert.assertEquals(descending ? lastPointId - locations.size() + 1
: lastPointId - numPoints + locations.size(), it.getLocationId());
: lastPointId - numPoints + locations.size(), it.getTrackPointId());
}
Assert.assertEquals(numPoints, locations.size());
}
return locations;
}
private long initializeTrack(long id, int numPoints) {
Track track = new Track();
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];
TrackPoint[] trackPoints = new TrackPoint[numPoints];
for (int i = 0; i < numPoints; ++i) {
Location loc = new Location("test");
loc.setLatitude(37.0 + (double) i / 10000.0);
loc.setLongitude(57.0 - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * 2.5);
locations[i] = loc;
trackPoints[i] = new TrackPoint(loc);
}
providerUtils.bulkInsertTrackPoint(locations, numPoints, id);
contentProviderUtils.bulkInsertTrackPoint(trackPoints, numPoints, id);
// Load all inserted locations.
// Load all inserted trackPoints.
long lastPointId = -1;
int counter = 0;
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false)) {
while (it.hasNext()) {
it.next();
lastPointId = it.getLocationId();
lastPointId = it.getTrackPointId();
counter++;
}
}
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
Assert.assertEquals(numPoints, track.getNumberOfPoints());
Assert.assertEquals(numPoints, counter);
return lastPointId;
@@ -208,7 +182,7 @@ public class CustomContentProviderUtilsTest {
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());
}
@@ -220,23 +194,23 @@ public class CustomContentProviderUtilsTest {
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());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(10, tracksPointsCursor.getCount());
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());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(0, tracksPointsCursor.getCount());
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(0, waypointCursor.getCount());
@@ -249,29 +223,29 @@ public class CustomContentProviderUtilsTest {
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);
Assert.assertEquals(3, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(20, tracksPointsCursor.getCount());
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);
Assert.assertEquals(2, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(20, tracksPointsCursor.getCount());
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(0, waypointCursor.getCount());
@@ -282,10 +256,10 @@ public class CustomContentProviderUtilsTest {
*/
@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());
}
@@ -296,8 +270,8 @@ public class CustomContentProviderUtilsTest {
@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());
}
/**
@@ -306,8 +280,8 @@ public class CustomContentProviderUtilsTest {
@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));
}
/**
@@ -316,15 +290,15 @@ public class CustomContentProviderUtilsTest {
@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());
}
/**
@@ -333,15 +307,13 @@ public class CustomContentProviderUtilsTest {
@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
double minGrade = -20.11;
TripStatistics statistics = new TripStatistics();
statistics.setStartTime(startTime);
statistics.setStopTime(2500L);
TrackStatistics statistics = new TrackStatistics();
statistics.setStartTime_ms(startTime);
statistics.setStopTime_ms(2500L);
statistics.setTotalTime(1500L);
statistics.setMovingTime(700L);
statistics.setTotalDistance(750.0);
@@ -349,22 +321,20 @@ public class CustomContentProviderUtilsTest {
statistics.setMaxSpeed(60.0);
statistics.setMaxElevation(1250.0);
statistics.setMinElevation(1200.0);
statistics.setMaxGrade(15.0);
statistics.setMinGrade(minGrade);
statistics.setBounds(-10000, 20000, 30000, -40000);
// Insert at first.
Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC);
waypoint.setType(WaypointType.STATISTICS);
waypoint.setTripStatistics(statistics);
track.setTrackStatistics(statistics);
contentProviderUtils.insertTrack(track);
// Insert at first.
Location location = new Location("test");
location.setLatitude(22);
location.setLongitude(22);
location.setAccuracy((float) 1 / 100.0f);
location.setAltitude(2.5);
waypoint.setLocation(location);
providerUtils.insertWaypoint(waypoint);
Waypoint waypoint = new Waypoint(location);
waypoint.setDescription(TEST_DESC);
contentProviderUtils.insertWaypoint(waypoint);
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
@@ -374,8 +344,6 @@ public class CustomContentProviderUtilsTest {
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));
}
/**
@@ -406,7 +374,7 @@ public class CustomContentProviderUtilsTest {
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());
@@ -414,54 +382,40 @@ public class CustomContentProviderUtilsTest {
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
* {@link ContentProviderUtils#deleteWaypoint(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(1);
@Override
public String generateWaypointDescription(TripStatistics tripStatistics) {
return CustomContentProviderUtilsTest.MOCK_DESC;
}
@Override
public String generateTrackDescription(Track aTrack, boolean html) {
return null;
}
};
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
Assert.assertNull(providerUtils.getWaypoint(1));
Assert.assertNull(contentProviderUtils.getWaypoint(1));
}
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
* {@link ContentProviderUtils#deleteWaypoint(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);
statistics.setStopTime(2500L);
TrackStatistics statistics = new TrackStatistics();
statistics.setStartTime_ms(1000L);
statistics.setStopTime_ms(2500L);
statistics.setTotalTime(1500L);
statistics.setMovingTime(700L);
statistics.setTotalDistance(750.0);
@@ -469,126 +423,53 @@ public class CustomContentProviderUtilsTest {
statistics.setMaxSpeed(60.0);
statistics.setMaxElevation(1250.0);
statistics.setMinElevation(1200.0);
statistics.setMaxGrade(15.0);
statistics.setMinGrade(-25.0);
statistics.setBounds(-10000, 20000, 30000, -40000);
track.setTrackStatistics(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);
long waypoint1Id = ContentUris.parseId(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);
long waypoint2Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
// Delete
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
@Override
public String generateWaypointDescription(TripStatistics tripStatistics) {
return CustomContentProviderUtilsTest.MOCK_DESC;
}
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
contentProviderUtils.deleteWaypoint(waypoint1Id);
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
@Override
public String generateTrackDescription(Track aTrack, boolean html) {
return null;
}
};
Assert.assertNotNull(providerUtils.getWaypoint(waypoint1Id));
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
Assert.assertEquals(CustomContentProviderUtilsTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
.getDescription());
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
}
/**
* Tests the method {@link ContentProviderUtils#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);
long waypoint1id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
Waypoint waypoint2 = new Waypoint();
waypoint2.setTrackId(trackId);
long waypoint2id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1));
Assert.assertEquals(waypoint1id, providerUtils.getFirstWaypointId(trackId));
Assert.assertEquals(waypoint2id, providerUtils.getLastWaypoint(trackId, WaypointType.WAYPOINT).getId());
}
/**
* Tests the method {@link ContentProviderUtils#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));
}
/**
* Tests the method
* {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}.
*/
@Test
public void testGetLastStatisticsWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.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);
Assert.assertEquals("Desc2", providerUtils.getLastWaypoint(trackId, WaypointType.STATISTICS).getDescription());
Assert.assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
}
/**
@@ -598,15 +479,15 @@ public class CustomContentProviderUtilsTest {
@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);
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(waypointId).getDescription());
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
@@ -615,36 +496,36 @@ public class CustomContentProviderUtilsTest {
@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);
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
// Update
waypoint = providerUtils.getWaypoint(waypointId);
waypoint = contentProviderUtils.getWaypoint(waypointId);
waypoint.setDescription(TEST_DESC_NEW);
providerUtils.updateWaypoint(waypoint);
contentProviderUtils.updateWaypoint(waypoint);
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(waypointId).getDescription());
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(Location[], int, long)}.
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], 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.getTrackPoints().toArray(new TrackPoint[0]), -1, trackId);
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), 8, trackId);
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
@@ -699,52 +580,26 @@ public class CustomContentProviderUtilsTest {
byte[] sensor = "Sensor state".getBytes();
when(cursorMock.getBlob(index++)).thenReturn(sensor);
Location location = providerUtils.createTrackPoint(cursorMock);
Assert.assertEquals((double) longitude, location.getLongitude(), 0.01);
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
TrackPoint location = contentProviderUtils.createTrackPoint(cursorMock);
Assert.assertEquals(longitude, location.getLongitude(), 0.01);
Assert.assertEquals(latitude, location.getLatitude(), 0.01);
Assert.assertEquals(time, location.getTime(), 0.01);
Assert.assertEquals(speed, location.getSpeed(), 0.01);
}
/**
* Tests the method
* {@link ContentProviderUtils#insertTrackPoint(Location, long)}.
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, 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());
}
/**
* Tests the method {@link ContentProviderUtils#getFirstTrackPointId(long)}.
*/
@Test
public void testGetFirstTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Assert.assertNotEquals(-1L, providerUtils.getFirstTrackPointId(trackId));
}
/**
* Tests the method {@link ContentProviderUtils#getLastTrackPointId(long)}.
*/
@Test
public void testGetLastTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Assert.assertTrue(providerUtils.getFirstTrackPointId(trackId) < providerUtils.getLastTrackPointId(trackId));
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
Assert.assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
@@ -754,11 +609,11 @@ public class CustomContentProviderUtilsTest {
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);
checkLocation(9, lastLocation);
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
checkLocation(9, lastTrackPoint.getLocation());
}
/**
@@ -768,15 +623,15 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointCursor_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getTrackPoints().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
}
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
Assert.assertEquals(2, cursor.getCount());
}
@@ -787,117 +642,84 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointCursor_asc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getTrackPoints().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
}
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
Assert.assertEquals(2, cursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in descending.
*/
@Test
public void testGetTrackPointLocationIterator_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getTrackPoints().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
}
long startTrackPointId = trackpointIds[9];
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true);
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next();
Assert.assertEquals(startTrackPointId - i, locationIterator.getLocationId());
checkLocation((trackpointIds.length - 1) - i, location);
Assert.assertTrue(trackPointIterator.hasNext());
TrackPoint trackPoint = trackPointIterator.next();
Assert.assertEquals(startTrackPointId - i, trackPointIterator.getTrackPointId());
checkLocation((trackpointIds.length - 1) - i, trackPoint.getLocation());
}
Assert.assertFalse(locationIterator.hasNext());
Assert.assertFalse(trackPointIterator.hasNext());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in ascending.
*/
@Test
public void testGetTrackPointLocationIterator_asc() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getTrackPoints().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
}
long startTrackPointId = trackpointIds[0];
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false);
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next();
Assert.assertEquals(startTrackPointId + i, locationIterator.getLocationId());
TrackPoint trackPoint = locationIterator.next();
Assert.assertEquals(startTrackPointId + i, locationIterator.getTrackPointId());
checkLocation(i, location);
checkLocation(i, trackPoint.getLocation());
}
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 CustomContentProviderUtilsTest#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);
}
/**
@@ -906,7 +728,15 @@ public class CustomContentProviderUtilsTest {
* @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.getTrackPoints().toArray(new TrackPoint[0]), track.getTrackPoints().size(), track.getId());
}
@Test
public void testFormatIdListForUri() {
Assert.assertEquals("", ContentProviderUtils.formatIdListForUri(new long[]{}));
Assert.assertEquals("12", ContentProviderUtils.formatIdListForUri(new long[]{12}));
Assert.assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new long[]{42, 43, 44}));
}
}
@@ -0,0 +1,179 @@
package de.dennisguse.opentracks.content.provider;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
import java.util.Map;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
@RunWith(AndroidJUnit4.class)
public class CustomSQLiteOpenHelperTest {
private static final String TRACKS_CREATE_TABLE_V23 = "CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)";
private static final String TRACKPOINTS_CREATE_TABLE_V23 = "CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT)";
private static final String WAYPOINTS_CREATE_TABLE_V23 = "CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, type INTEGER, length FLOAT, duration INTEGER, starttime INTEGER, startid INTEGER, stopid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, photoUrl TEXT)";
private static final String DATABASE_NAME = "test.db";
private final Context context = ApplicationProvider.getApplicationContext();
/**
* Get the SQL create statements for all tables (ordered by name).
*
* @return Map(TableName, SQL)
*/
private static Map<String, String> getTableSQL(SQLiteDatabase db) {
HashMap<String, String> tableSQL = new HashMap<>();
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + WaypointsColumns.TABLE_NAME + "')", null, null, null, "name")) {
if (cursor != null) {
while (cursor.moveToNext()) {
tableSQL.put(cursor.getString(0), cursor.getString(1));
}
}
}
return tableSQL;
}
/**
* Get the SQL create statements for all indices (ordered by name).
*
* @return Map(tableName, SQL)
*/
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
HashMap<String, String> indexSQL = new HashMap<>();
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
indexSQL.put(cursor.getString(0), cursor.getString(1));
}
}
}
return indexSQL;
}
/**
* Returns true if a SQL create statement is in the schema.
*
* @param sqlCreate the table name
*/
private static boolean hasSqlCreate(SQLiteDatabase db, String sqlCreate) {
try (Cursor cursor = db.rawQuery("SELECT SQL FROM sqlite_master", null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
String sql = cursor.getString(0);
if (sqlCreate.equals(sql)) {
return true;
}
}
}
}
return false;
}
@Before
public void setUp() {
context.deleteDatabase(DATABASE_NAME);
}
@Test
public void onCreate() {
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
Assert.assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
}
}
@Test
public void onUpgrade_FromVersion23() {
// Manually create database schema with version 23 (base version)
{
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}.getWritableDatabase();
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
dbBase.close();
}
// Open database with SQL upgrade
Map<String, String> tableByUpgrade;
Map<String, String> indicesByUpgrade;
try (SQLiteDatabase dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tableByUpgrade = getTableSQL(dbUpgraded);
indicesByUpgrade = getIndexSQL(dbUpgraded);
}
// Open database via
context.deleteDatabase(DATABASE_NAME);
Map<String, String> tablesByCreate;
Map<String, String> indicesByCreate;
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tablesByCreate = getTableSQL(dbCreated);
indicesByCreate = getIndexSQL(dbCreated);
}
// then - verify table structure
Assert.assertEquals(3, tableByUpgrade.size());
Assert.assertEquals(tableByUpgrade.size(), tablesByCreate.size());
Assert.assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
// then - verify custom indices
Assert.assertEquals(2, indicesByCreate.size());
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
}
@Test
public void onDowngrade_ToVersion23() {
// Create most recent database schema
new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase().close();
// Downgrade schema to version 23 (base version)
Map<String, String> tablesByDowngrade;
Map<String, String> indicesByDowngrade;
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23).getReadableDatabase()) {
tablesByDowngrade = getTableSQL(db);
indicesByDowngrade = getIndexSQL(db);
}
// then - verify table structure
Assert.assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks"));
Assert.assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints"));
Assert.assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints"));
// then - verify custom indices
Assert.assertEquals(0, indicesByDowngrade.size());
}
}
@@ -0,0 +1,28 @@
package de.dennisguse.opentracks.content.provider;
import android.net.Uri;
import android.util.Pair;
import org.junit.Assert;
import org.junit.Test;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
public class ShareContentProviderTest {
@Test
public void testCreateandParseURI() {
long[] trackIds = {1, 3, 5};
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertArrayEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
}
@Test
public void testCreateURIescapeFilename() {
long[] trackIds = {1};
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
}
}
@@ -30,8 +30,9 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.TrackStubUtils;
import de.dennisguse.opentracks.chart.ChartPoint;
import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -61,290 +62,271 @@ public class ChartFragmentTest {
@Before
public void setUp() {
boolean chartByDistance = false;
chartFragment = new ChartFragment(chartByDistance) {
};
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
chartFragment.setTrackStatisticsUpdater(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#createPendingPoint(TrackPoint)}.
*/
@Test
public void testFillDataPoint_sensorIncorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
public void testCreatePendingPoint_sensorIncorrect() {
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
// Input incorrect state.
// Creates SensorData.
SensorDataSet sensorDataSet = new SensorDataSet(SensorDataSet.DATA_UNAVAILABLE, SensorDataSet.DATA_UNAVAILABLE);
sensorDataSetLocation.setSensorDataSet(sensorDataSet);
trackPoint.setSensorDataSet(sensorDataSet);
// Test.
point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
}
/**
* Tests the logic to get the correct values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}.
* Tests the logic to get the correct values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testFillDataPoint_sensorCorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
public void testCreatePendingPoint_sensorCorrect() {
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
// Creates SensorData.
SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102);
// Creates SensorDataSet.
sensorDataSetLocation.setSensorDataSet(sensorDataSet);
trackPoint.setSensorDataSet(sensorDataSet);
// Test.
point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(102.0, point[ChartView.POWER_SERIES + 1], 0.01);
point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(100.0, point.getHeartRate(), 0.01);
Assert.assertEquals(101.0, point.getCadence(), 0.01);
Assert.assertEquals(102.0, point.getPower(), 0.01);
}
/**
* Tests the logic to get the value of metric Distance in {@link ChartFragment#fillDataPoint(Location, double[])}.
* Tests the logic to get the value of metric Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testFillDataPoint_distanceMetric() {
// By distance.
public void testCreatePendingPoint_distanceMetric() {
chartFragment.setChartByDistance(true);
// Resets last location and writes first location.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(0.0, point[0], 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation3.setLatitude(23);
point = fillDataPointTestHelper(sensorDataSetLocation3);
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
trackPoint3.setLatitude(23);
point = chartFragment.createPendingPoint(trackPoint3);
// Computes the distance between Latitude 22 and 23.
float[] results = new float[4];
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(),
sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(),
trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals(distance1, point[0], 0.01);
Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The fourth location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation4.setLatitude(24);
point = fillDataPointTestHelper(sensorDataSetLocation4);
TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint();
trackPoint4.setLatitude(24);
point = chartFragment.createPendingPoint(trackPoint4);
// Computes the distance between Latitude 23 and 24.
Location.distanceBetween(sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(),
sensorDataSetLocation4.getLatitude(), sensorDataSetLocation4.getLongitude(), results);
Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(),
trackPoint4.getLatitude(), trackPoint4.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals((distance1 + distance2), point[0], 0.01);
Assert.assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
}
/**
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#fillDataPoint(Location, double[])}.
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testFillDataPoint_distanceImperial() {
public void testCreatePendingPoint_distanceImperial() {
// By distance.
chartFragment.setChartByDistance(true);
// Setups to use imperial.
chartFragment.setMetricUnits(false);
// The first is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setLatitude(23);
point = fillDataPointTestHelper(sensorDataSetLocation2);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setLatitude(23);
point = chartFragment.createPendingPoint(trackPoint2);
/*
* Computes the distance between Latitude 22 and 23.
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
*/
float[] results = new float[4];
Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(), sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1, point[0], 0.01);
Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation3.setLatitude(24);
point = fillDataPointTestHelper(sensorDataSetLocation3);
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
trackPoint3.setLatitude(24);
point = chartFragment.createPendingPoint(trackPoint3);
/*
* Computes the distance between Latitude 23 and 24.
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
*/
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1 + distance2, point[0], 0.01);
Assert.assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
}
/**
* Tests the logic to get the values of time in {@link ChartFragment#fillDataPoint(Location, double[])}.
* Tests the logic to get the values of time in {@link ChartFragment#createPendingPoint(TrackPoint)}.
*/
@Test
public void testFillDataPoint_time() {
// By time
public void testCreatePendingPoint_time() {
chartFragment.setChartByDistance(false);
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
long timeSpan = 222;
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((double) timeSpan, point[0], 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setTime(trackPoint1.getTime() + timeSpan);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals((double) timeSpan, point.getTimeOrDistance(), 0.01);
}
/**
* Tests the logic to get the value of elevation in {@link ChartFragment#fillDataPoint(Location, double[])} by one and two points.
* Tests the logic to get the value of elevation in {@link ChartFragment#createPendingPoint(TrackPoint)} by one and two points.
*/
@Test
public void testFillDataPoint_elevation() {
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
public void testCreatePendingPoint_elevation() {
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
/*
* At first, clear old points of elevation, so give true to the second parameter.
* Then only one value INITIAL_ALTITUDE in buffer.
*/
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getElevation(), 0.01);
/*
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
*/
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
point[ChartView.ELEVATION_SERIES + 1], 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getElevation(), 0.01);
}
/**
* Tests the logic to get the value of speed in {@link ChartFragment#fillDataPoint(Location, double[])}.
* Tests the logic to get the value of speed in {@link ChartFragment#createPendingPoint(TrackPoint)}.
* In this test, firstly remove all points in memory, and then fill in two points one by one.
* The speed values of these points are 129, 130.
*/
@Test
public void testFillDataPoint_speed() {
public void testCreatePendingPoint_speed() {
/*
* At first, clear old points of speed, so give true to the second parameter.
* It will not be filled in to the speed buffer.
*/
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(128.5f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(128.5f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
/*
* Tests the logic when both metricUnits and reportSpeed are true.
* This location will be filled into speed buffer.
*/
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
/*
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
*/
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222);
sensorDataSetLocation2.setSpeed(130);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1], 0.01);
trackPoint2.setTime(trackPoint1.getTime() + 222);
trackPoint2.setSpeed(130);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point.getSpeed(), 0.01);
}
/**
* Tests the logic to compute speed when use Imperial.
*/
@Test
public void testFillDataPoint_speedImperial() {
// Setups to use imperial.
public void testCreatePendingPoint_speedImperial() {
chartFragment.setMetricUnits(false);
// First data point is not added to the speed buffer
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
/*
* Add a time span here to make sure the second point and the speed is valid.
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
*/
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
sensorDataSetLocation2.setSpeed(102);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[ChartView.SPEED_SERIES + 1], 0.01);
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
}
/**
* Tests the logic to get pace value when reportSpeed is false.
*/
@Test
public void testFillDataPoint_pace_nonZeroSpeed() {
// Setups reportSpeed to false.
public void testCreatePendingPoint_pace_nonZeroSpeed() {
chartFragment.setReportSpeed(false);
// First data point is not added to the speed buffer
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
/*
* Add a time span here to make sure the second point and the speed is valid.
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
*/
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
sensorDataSetLocation2.setSpeed(102);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1], 0.01);
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point.getPace(), 0.01);
}
/**
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
*/
@Test
public void testFillDataPoint_pace_zeroSpeed() {
// Setups reportSpeed to false.
public void testCreatePendingPoint_pace_zeroSpeed() {
chartFragment.setReportSpeed(false);
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation.setSpeed(0);
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1], 0.01);
}
/**
* Helper method to test fillDataPoint.
*
* @param location location to fill
* @return data of this location
*/
private double[] fillDataPointTestHelper(Location location) {
double[] point = new double[ChartView.NUM_SERIES + 1];
chartFragment.fillDataPoint(location, point);
return point;
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setSpeed(0);
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(0.0, point.getPace(), 0.01);
}
}
@@ -32,11 +32,12 @@ import java.text.SimpleDateFormat;
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.TrackPoint;
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.content.provider.ContentProviderUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
@@ -57,9 +58,9 @@ public abstract class AbstractTestFileTrackImporter {
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";
static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123Z";
static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123Z";
static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123Z";
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
@@ -83,26 +84,24 @@ public abstract class AbstractTestFileTrackImporter {
@Mock
public ContentProviderUtils contentProviderUtils;
Location createLocation(int index, long time) {
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(TRACK_LATITUDE + index);
location.setLongitude(TRACK_LONGITUDE + index);
location.setAltitude(TRACK_ELEVATION + index);
location.setTime(time);
return location;
TrackPoint createTrackPoint(int index, long time) {
Location trackPoint = new Location(LocationManager.GPS_PROVIDER);
trackPoint.setLatitude(TRACK_LATITUDE + index);
trackPoint.setLongitude(TRACK_LONGITUDE + index);
trackPoint.setAltitude(TRACK_ELEVATION + index);
trackPoint.setTime(time);
return new TrackPoint(trackPoint);
}
/**
* Expects the first track point to be added.
*
* @param location the location
* @param trackPoint the trackPoint
* @param trackId the track id
* @param trackPointId the track point id
*/
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);
protected void expectFirstTrackPoint(TrackPoint trackPoint, long trackId, long trackPointId) {
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(1), eq(trackId))).thenReturn(1);
}
/**
@@ -126,9 +125,7 @@ public abstract class AbstractTestFileTrackImporter {
Assert.assertEquals(name, track.getName());
Assert.assertEquals(description, track.getDescription());
if (time != -1L) {
Assert.assertEquals(time, track.getTripStatistics().getStartTime());
Assert.assertEquals(time, track.getTrackStatistics().getStartTime_ms());
}
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.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.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.stats.TrackStatistics;
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 TrackStatistics} for now.
*/
@RunWith(JUnit4.class)
public class ExportImportTest {
private static final String TAG = ExportImportTest.class.getSimpleName();
private final Context context = ApplicationProvider.getApplicationContext();
private final 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 final 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.getTrackPoints().toArray(new TrackPoint[0]), track.getTrackPoints().size(), track.getId());
for (int i = 0; i < 3; i++) {
Waypoint waypoint = new Waypoint(track.getTrackPoints().get(i).getLocation());
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);
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.getTrackPoints(), importedTrack.getTrackPoints());
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.getTrackPoints(), trackImported.getTrackPoints());
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);
}
}
}
@@ -16,8 +16,6 @@
package de.dennisguse.opentracks.io.file.importer;
import android.location.Location;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -27,7 +25,8 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
@@ -74,9 +73,9 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
String longitude = Double.toString(TRACK_LONGITUDE + index);
String elevation = Double.toString(TRACK_ELEVATION + index);
StringBuilder buffer = new StringBuilder();
buffer.append("<trkpt lat=\"" + latitude + "\" lon=\"" + longitude + "\"><ele>" + elevation + "</ele>");
buffer.append("<trkpt lat=\"").append(latitude).append("\" lon=\"").append(longitude).append("\"><ele>").append(elevation).append("</ele>");
if (time != null) {
buffer.append("<time>" + time + "</time>");
buffer.append("<time>").append(time).append("</time>");
}
buffer.append("</trkpt>");
return buffer.toString();
@@ -85,14 +84,14 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
@Test
public void testOneTrackOneSegment() throws Exception {
// 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());
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPoint1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
@@ -109,19 +108,19 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
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());
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
@Test
public void testOneTrackTwoSegments() throws Exception {
// given
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
expectFirstTrackPoint(trackPoint0, 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.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
@@ -141,7 +140,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
@@ -153,7 +152,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
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.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
@@ -169,7 +168,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
Assert.assertEquals(0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
}
@@ -207,9 +206,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
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.
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.bulkInsertTrackPoint((TrackPoint[]) any(), anyInt(), anyLong())).thenReturn(1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
@@ -16,8 +16,6 @@
package de.dennisguse.opentracks.io.file.importer;
import android.location.Location;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -27,7 +25,8 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
@@ -69,14 +68,14 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
@Test
public void testOneTrackOneSegment() throws Exception {
// 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());
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPointl1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
@@ -94,20 +93,20 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
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());
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
@Test
public void testOneTrackTwoSegments() throws Exception {
// given
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
expectFirstTrackPoint(trackPoint0, 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.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
@@ -127,7 +126,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
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,12 +36,11 @@ 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);
}
/**
@@ -48,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_m()}.
*/
@Test
public void testGetMinDistance() {
assertEquals(MIN_DISTANCE, adocationListenerPolicy.getMinDistance_m());
Assert.assertEquals(MIN_DISTANCE, adaptiveLocationListenerPolicy.getMinDistance_m());
}
}
@@ -2,7 +2,6 @@ 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;
@@ -12,6 +11,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
@@ -21,10 +22,10 @@ import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TrackRecordingServiceNotificationManagerTest {
private Context context = ApplicationProvider.getApplicationContext();
private final Context context = ApplicationProvider.getApplicationContext();
@Mock
private Location locationMock;
private TrackPoint locationMock;
@Mock
private NotificationCompat.Builder notificationCompatBuilder;
@@ -42,10 +43,10 @@ public class TrackRecordingServiceNotificationManagerTest {
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);
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, locationMock, 100);
subject.updateTrackPoint(context, locationMock, 1000);
subject.updateTrackPoint(context, locationMock, 100);
// then
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
@@ -42,13 +42,11 @@ 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.stats.TripStatistics;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
@@ -56,7 +54,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
*
* @author Bartlomiej Niechwiej
* <p>
* ATTENTION: This tests deletes all stored tracks in the database.
* //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)
@@ -68,20 +66,15 @@ public class TrackRecordingServiceTest {
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils;
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private long trackId = Math.abs(new Random().nextLong());
private final long trackId = Math.abs(new Random().nextLong());
static Intent createStartIntent(Context context) {
return new Intent(context, TrackRecordingService.class);
}
static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
PreferencesUtils.setString(context, R.string.auto_resume_track_timeout_key, "" + timeoutMins);
}
@Before
public void setUp() {
// Set up the mock content resolver
@@ -89,29 +82,26 @@ public class TrackRecordingServiceTest {
};
customContentProvider.attachInfo(context, null);
providerUtils = new ContentProviderUtils(context);
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply();
// Disable auto resume by default.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
// Ensure that the database is empty before every test
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
}
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
// Ensure that the database is empty after every test
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
}
@SmallTest
@@ -128,133 +118,15 @@ public class TrackRecordingServiceTest {
Assert.assertNotNull(service);
}
@MediumTest
@Test
public void testResumeAfterReboot_shouldResume() throws Exception {
// Insert a dummy track and mark it as recording track.
createDummyTrack(trackId, System.currentTimeMillis(), true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// then
Assert.assertNotNull(service);
// We expect to resume the previous track.
Assert.assertTrue(service.isRecording());
Assert.assertEquals(trackId, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_simulateReboot() throws Exception {
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
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.getRecordingTrackId(context));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
// then
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(trackId, System.currentTimeMillis(), false);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// then
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(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// then
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because it has expired.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
// Insert a dummy track.
createDummyTrack(trackId, System.currentTimeMillis(), true);
// Set the number of attempts to max.
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
//then
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(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_noTracks() throws Exception {
List<Track> tracks = providerUtils.getAllTracks();
List<Track> tracks = contentProviderUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
Intent startIntent = createStartIntent(context);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
// Test if we start in no-recording mode by default.
Assert.assertFalse(service.isRecording());
@@ -266,7 +138,7 @@ public class TrackRecordingServiceTest {
public void testRecording_oldTracks() throws Exception {
createDummyTrack(trackId, -1L, false);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@@ -275,7 +147,7 @@ public class TrackRecordingServiceTest {
@Test
public void testRecording_orphanedRecordingTrack() throws Exception {
Intent startIntent = createStartIntent(context);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
// Just set recording track to a bogus value.
// Make sure that the service will not start recording and will clear the bogus track.
@@ -289,22 +161,24 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testStartNewTrack_alreadyRecording() throws Exception {
createDummyTrack(trackId, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
long trackId = service.getRecordingTrackId();
long newTrackId = service.startNewTrack();
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId());
service.endCurrentTrack();
}
@MediumTest
@Test
public void testEndCurrentTrack_noRecording() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
// Ending the current track when there is no recording should not result in any error.
@@ -314,58 +188,21 @@ public class TrackRecordingServiceTest {
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
Assert.assertEquals(-1L, waypointId);
}
@MediumTest
@Test
public void testInsertStatisticsMarker_validLocation() throws Exception {
createDummyTrack(trackId, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertTrue(service.isRecording());
Assert.assertFalse(service.isPaused());
insertLocation(service);
long waypointId1 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
Assert.assertNotEquals(-1L, waypointId1);
long waypointId2 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
Assert.assertNotEquals(-1L, waypointId2);
Waypoint wpt = providerUtils.getWaypoint(waypointId1);
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 0), wpt.getName());
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
Assert.assertEquals(trackId, 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.
}
// 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");
TripStatistics tripStatistics = new TripStatistics();
tripStatistics.setStopTime(stopTime);
dummyTrack.setTripStatistics(tripStatistics);
TrackStatistics trackStatistics = new TrackStatistics();
trackStatistics.setStopTime_ms(stopTime);
dummyTrack.setTrackStatistics(trackStatistics);
addTrack(dummyTrack, isRecording);
}
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);
}
@@ -373,7 +210,7 @@ public class TrackRecordingServiceTest {
/**
* Inserts a location and waits for 200ms.
*/
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
private void insertLocation(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
Location location = new Location("gps");
location.setLongitude(35.0f);
location.setLatitude(45.0f);
@@ -381,7 +218,7 @@ public class TrackRecordingServiceTest {
location.setSpeed(10);
location.setTime(System.currentTimeMillis());
location.setBearing(3.0f);
trackRecordingService.insertTrackPoint(location);
trackRecordingService.insertLocation(location);
Thread.sleep(200);
}
@@ -389,31 +226,31 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertEquals(-1L, waypointId);
}
@MediumTest
@Test
public void testInsertWaypointMarker_validWaypoint() throws Exception {
createDummyTrack(trackId, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
insertLocation(service);
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
long trackId = service.getRecordingTrackId();
long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertNotEquals(-1L, waypointId);
Waypoint wpt = providerUtils.getWaypoint(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(WaypointType.WAYPOINT, wpt.getType());
Assert.assertEquals(trackId, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
Assert.assertNull(wpt.getTripStatistics());
service.endCurrentTrack();
}
}
@@ -25,11 +25,10 @@ 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.WaypointCreationRequest;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
@@ -46,9 +45,9 @@ public class TrackRecordingServiceTestLooper {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private Context context = ApplicationProvider.getApplicationContext();
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils;
private ContentProviderUtils contentProviderUtils;
@BeforeClass
public static void preSetUp() {
@@ -68,29 +67,26 @@ public class TrackRecordingServiceTestLooper {
};
customContentProvider.attachInfo(context, null);
providerUtils = new ContentProviderUtils(context);
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply();
// Disable auto resume by default.
TrackRecordingServiceTest.updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
// Ensure that the database is empty before every test
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
}
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
// Ensure that the database is empty after every test
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
}
@MediumTest
@@ -190,20 +186,20 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testIntegration_completeRecordingSession() throws TimeoutException {
List<Track> tracks = providerUtils.getAllTracks();
List<Track> tracks = contentProviderUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
fullRecordingSession();
}
private void fullRecordingSession() throws TimeoutException {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) 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);
Track track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
@@ -219,12 +215,10 @@ public class TrackRecordingServiceTestLooper {
location.setSpeed(10);
location.setTime(startTime + i * 10000);
location.setBearing(3.0f);
service.insertTrackPoint(location);
service.insertLocation(location);
if (i % 10 == 0) {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
} else if (i % 7 == 0) {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
if (i % 7 == 0) {
service.insertWaypoint(null, null, null, null);
}
}
@@ -232,12 +226,12 @@ public class TrackRecordingServiceTestLooper {
service.endCurrentTrack();
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
track = providerUtils.getTrack(id);
track = contentProviderUtils.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());
TrackStatistics trackStatistics = track.getTrackStatistics();
Assert.assertNotNull(trackStatistics);
Assert.assertTrue(trackStatistics.getStartTime_ms() > 0);
Assert.assertTrue(trackStatistics.getStopTime_ms() >= trackStatistics.getStartTime_ms());
}
}
@@ -36,7 +36,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class AnnouncementPeriodicTaskFactoryTest {
private Context context = ApplicationProvider.getApplicationContext();
private final Context context = ApplicationProvider.getApplicationContext();
@BeforeClass
public static void preSetUp() {
@@ -0,0 +1,88 @@
/*
* 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.stats;
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 TrackStatistics}.
* This only tests non-trivial pieces of that class.
*
* @author Rodrigo Damazio
*/
@RunWith(JUnit4.class)
public class TrackStatisticsTest {
private TrackStatistics statistics;
@Before
public void setUp() {
statistics = new TrackStatistics();
}
@Test
public void testMerge() {
TrackStatistics statistics2 = new TrackStatistics();
statistics.setStartTime_ms(1000L); // Resulting start time
statistics.setStopTime_ms(2500L);
statistics2.setStartTime_ms(3000L);
statistics2.setStopTime_ms(4000L); // Resulting stop time
statistics.setTotalTime(1500L);
statistics2.setTotalTime(1000L); // Result: 1500+1000
statistics.setMovingTime(700L);
statistics2.setMovingTime(600L); // Result: 700+600
statistics.setTotalDistance(750.0);
statistics2.setTotalDistance(350.0); // Result: 750+350
statistics.setTotalElevationGain(50.0);
statistics2.setTotalElevationGain(850.0); // Result: 850+50
statistics.setMaxSpeed(60.0); // Resulting max speed
statistics2.setMaxSpeed(30.0);
statistics.setMaxElevation(1250.0);
statistics.setMinElevation(1200.0); // Resulting min elevation
statistics2.setMaxElevation(3575.0); // Resulting max elevation
statistics2.setMinElevation(2800.0);
statistics.merge(statistics2);
Assert.assertEquals(1000L, statistics.getStartTime_ms());
Assert.assertEquals(4000L, statistics.getStopTime_ms());
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);
}
@Test
public void testGetAverageSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setTotalTime(50000); // in milliseconds
Assert.assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
}
@Test
public void testGetAverageMovingSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setMovingTime(20000); // in milliseconds
Assert.assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
}
}
@@ -1,105 +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.stats;
import junit.framework.TestCase;
/**
* Tests for {@link TripStatistics}.
* This only tests non-trivial pieces of that class.
*
* @author Rodrigo Damazio
*/
public class TripStatisticsTest extends TestCase {
private TripStatistics statistics;
@Override
protected void setUp() throws Exception {
super.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());
}
public void testMerge() {
TripStatistics statistics2 = new TripStatistics();
statistics.setStartTime(1000L); // Resulting start time
statistics.setStopTime(2500L);
statistics2.setStartTime(3000L);
statistics2.setStopTime(4000L); // Resulting stop time
statistics.setTotalTime(1500L);
statistics2.setTotalTime(1000L); // Result: 1500+1000
statistics.setMovingTime(700L);
statistics2.setMovingTime(600L); // Result: 700+600
statistics.setTotalDistance(750.0);
statistics2.setTotalDistance(350.0); // Result: 750+350
statistics.setTotalElevationGain(50.0);
statistics2.setTotalElevationGain(850.0); // Result: 850+50
statistics.setMaxSpeed(60.0); // Resulting max speed
statistics2.setMaxSpeed(30.0);
statistics.setMaxElevation(1250.0);
statistics.setMinElevation(1200.0); // Resulting min elevation
statistics2.setMaxElevation(3575.0); // Resulting max elevation
statistics2.setMinElevation(2800.0);
statistics.setMaxGrade(15.0);
statistics.setMinGrade(-25.0); // Resulting min grade
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());
}
public void testGetAverageSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setTotalTime(50000); // in milliseconds
assertEquals(20.0, statistics.getAverageSpeed());
}
public void testGetAverageMovingSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setMovingTime(20000); // in milliseconds
assertEquals(50.0, statistics.getAverageMovingSpeed());
}
}
@@ -0,0 +1,34 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.stats.TrackStatistics;
@RunWith(AndroidJUnit4.class)
public class AnnouncementUtilsTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Test
public void getAnnouncement() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats);
// then
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
}
}
@@ -15,107 +15,107 @@
*/
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;
import java.util.Arrays;
/**
* Tests for {@link FileUtils}.
*
* @author Rodrigo Damazio
*/
public class FileUtilsTest extends TestCase {
/**
* 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);
}
@RunWith(JUnit4.class)
public class FileUtilsTest {
/**
* 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.
*/
@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));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
@Test
public void testSanitizeFileNameWithSuffix() {
String name = "FileName.jpeg";
assertEquals(name, FileUtils.sanitizeFileName(name));
Assert.assertEquals(name, FileUtils.sanitizeFileName(name));
}
/**
* 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.
*/
@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.
*/
@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.
*/
@Test
public void testTruncateFileName() {
File directory = new File("/dir1/dir2/");
String suffix = ".gpx";
char[] name = new char[FileUtils.MAX_FAT32_PATH_LENGTH];
for (int i = 0; i < name.length; i++) {
name[i] = 'a';
}
Arrays.fill(name, 'a');
String nameString = new String(name);
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());
}
}
@@ -36,7 +36,7 @@ import java.util.TimeZone;
@RunWith(AndroidJUnit4.class)
public class StringUtilsTest {
private Context context = ApplicationProvider.getApplicationContext();
private final Context context = ApplicationProvider.getApplicationContext();
/**
@@ -115,27 +115,16 @@ public class StringUtilsTest {
}
/**
* Tests {@link StringUtils#getTime(String)}.
*/
@Test
public void testGetTime() {
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
}
/**
* Tests {@link StringUtils#getTime(String)} with fractional seconds.
* Tests {@link StringUtils#parseTime(String)} with fractional seconds.
*/
@Test
public void testGetTime_fractional() {
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.3529Z", 2010, 5, 4, 3, 2, 1, 353);
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 352);
}
/**
* Tests {@link StringUtils#getTime(String)} with time zone.
* Tests {@link StringUtils#parseTime(String)} with time zone.
*/
@Test
public void testGetTime_timezone() {
@@ -149,7 +138,7 @@ public class StringUtilsTest {
}
/**
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time zone.
* Tests {@link StringUtils#parseTime(String)} with fractional seconds and time zone.
*/
@Test
public void testGetTime_fractionalAndTimezone() {
@@ -160,7 +149,7 @@ public class StringUtilsTest {
}
/**
* Asserts the {@link StringUtils#getTime(String)} returns the expected values.
* Asserts the {@link StringUtils#parseTime(String)} returns the expected values.
*
* @param xmlDateTime the xml date time string
* @param year the expected year
@@ -175,7 +164,10 @@ public class StringUtilsTest {
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.set(year, month - 1, day, hour, minute, second);
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.getTime(xmlDateTime));
// This comparision tends to be flaky (difference of 1ms)
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
Assert.assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime)) <= 1);
}
/**
+10 -21
View File
@@ -16,21 +16,24 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks"
android:installLocation="auto"
android:versionCode="3175"
android:versionName="v3.2.9"> <!-- For FDroid. -->
android:versionCode="3330"
android:versionName="v3.6.0"> <!-- For FDroid. -->
<!-- Permission to initialize services -->
<application
android:name=".Startup"
android:allowBackup="false"
android:hasFragileUserData="true"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_logo_color_24dp"
android:label="@string/app_name"
android:icon="${applicationLogo}"
android:label="${applicationName}"
android:theme="@style/ThemeCustom">
<!-- Activities -->
<activity android:name=".AboutActivity" />
<activity android:name=".DeleteActivity" />
<activity
android:name=".TrackDeleteActivity"
android:theme="@style/ThemeCustomTransparent" />
<activity
android:name=".HelpActivity"
@@ -83,9 +86,7 @@ limitations under the License.
<activity android:name=".TrackEditActivity" />
<activity
android:name=".TrackListActivity"
android:screenOrientation="userPortrait">
<activity android:name=".TrackListActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -101,9 +102,8 @@ limitations under the License.
<activity android:name=".settings.SettingsActivity" />
<!-- Services -->
<provider
android:name=".content.ShareContentProvider"
android:name=".content.provider.ShareContentProvider"
android:authorities="${applicationId}.content"
android:exported="false"
android:grantUriPermissions="true" />
@@ -118,14 +118,6 @@ limitations under the License.
android:resource="@xml/file_paths" />
</provider>
<!-- Receivers -->
<receiver android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- Providers -->
<service
android:name=".services.TrackRecordingService"
android:enabled="true"
@@ -157,12 +149,9 @@ limitations under the License.
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<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" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
@@ -76,8 +76,8 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
if (stopRecording) {
getTrackRecordingServiceConnection().stopRecording(this, false);
}
Intent intent = IntentUtils.newIntent(this, DeleteActivity.class);
intent.putExtra(DeleteActivity.EXTRA_TRACK_IDS, trackIds);
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
startActivityForResult(intent, DELETE_REQUEST_CODE);
}
@@ -88,7 +88,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection();
/**
* Called after {@link DeleteActivity} returns its result.
* Called after {@link TrackDeleteActivity} returns its result.
*/
abstract protected void onDeleted();
}
@@ -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");
}
}
}
@@ -19,18 +19,18 @@ package de.dennisguse.opentracks;
import android.content.Context;
import android.os.AsyncTask;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
/**
* Async Task to delete tracks.
*
* @author Jimmy Shih
*/
public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private final long[] trackIds;
private final Context context;
private DeleteActivity deleteActivity;
private TrackDeleteActivity deleteActivity;
// true if the AsyncTask has completed
private boolean completed;
@@ -41,7 +41,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* @param trackIds the track ids to delete. To delete all, set to size 1 with
* trackIds[0] == -1L
*/
public DeleteAsyncTask(DeleteActivity deleteActivity, long[] trackIds) {
public DeleteAsyncTask(TrackDeleteActivity deleteActivity, long[] trackIds) {
this.deleteActivity = deleteActivity;
this.trackIds = trackIds;
context = deleteActivity.getApplicationContext();
@@ -53,7 +53,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
*
* @param deleteActivity the current activity, can be null
*/
public void setActivity(DeleteActivity deleteActivity) {
public void setActivity(TrackDeleteActivity deleteActivity) {
this.deleteActivity = deleteActivity;
if (completed && deleteActivity != null) {
deleteActivity.onAsyncTaskCompleted();
@@ -30,8 +30,8 @@ import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
@@ -71,12 +71,9 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor);
if (!currentMarker.isTripStatistics()) {
markerIds.add(currentMarker.getId());
if (currentMarker.getId() == markerId) {
markerIndex = markerIds.size() - 1;
}
markerIds.add(currentMarker.getId());
if (currentMarker.getId() == markerId) {
markerIndex = markerIds.size() - 1;
}
cursor.moveToNext();
@@ -16,20 +16,38 @@
package de.dennisguse.opentracks;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils;
/**
* An activity to add/edit a marker.
@@ -41,19 +59,27 @@ public class MarkerEditActivity extends AbstractActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
private static final int CAMERA_REQUEST_CODE = 5;
private static final int GALLERY_IMG_REQUEST_CODE = 7;
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private long trackId;
private long markerId;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private Waypoint waypoint;
private MenuItem insertPhotoMenuItem;
private MenuItem insertGalleryImgMenuItem;
private Uri photoUri;
private boolean hasCamera;
// UI elements
private View statisticsSection;
private EditText statisticsName;
private View waypointSection;
private EditText waypointName;
private AutoCompleteTextView waypointMarkerType;
private EditText waypointDescription;
private ImageView waypointPhoto;
private ImageView waypointDeletePhotoBtn;
private Button done;
@Override
@@ -62,18 +88,29 @@ public class MarkerEditActivity extends AbstractActivity {
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
// Setup UI elements
statisticsSection = findViewById(R.id.marker_edit_statistics_section);
statisticsName = findViewById(R.id.marker_edit_statistics_name);
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
waypointName = findViewById(R.id.marker_edit_waypoint_name);
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
waypointMarkerType.setAdapter(adapter);
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
waypointDeletePhotoBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (waypoint != null && waypoint.hasPhoto())
waypoint.setPhotoUrl(null);
waypointPhoto.setImageBitmap(null);
photoUri = null;
hideAndShowOptions();
}
});
Button cancel = findViewById(R.id.marker_edit_cancel);
cancel.setOnClickListener(new OnClickListener() {
@@ -95,7 +132,7 @@ public class MarkerEditActivity extends AbstractActivity {
@Override
protected void onStop() {
super.onStop();
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@Override
@@ -103,6 +140,64 @@ public class MarkerEditActivity extends AbstractActivity {
return R.layout.marker_edit;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.marker_edit, menu);
insertPhotoMenuItem = menu.findItem(R.id.marker_edit_insert_photo);
insertPhotoMenuItem.setVisible(hasCamera);
insertGalleryImgMenuItem = menu.findItem(R.id.marker_edit_insert_gallery_img);
hideAndShowOptions();
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.marker_edit_insert_photo:
createWaypointWithPicture();
return true;
case R.id.marker_edit_insert_gallery_img:
createWaypointWithGalleryImage();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
return;
} else if (resultCode == RESULT_OK) {
setWaypointImageView(photoUri);
}
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
return;
} else if (resultCode == RESULT_OK) {
Uri srcUri = data.getData();
try (ParcelFileDescriptor parcelFd = getContentResolver().openFileDescriptor(srcUri, "r")) {
FileDescriptor srcFd = parcelFd.getFileDescriptor();
File dstFile = new File(FileUtils.getImageUrl(this, getTrackId()));
FileUtils.copy(srcFd, dstFile);
photoUri = FileUtils.getUriForFile(this, dstFile);
setWaypointImageView(photoUri);
} catch(Exception e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
return;
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* Updates the UI based on the marker id.
*/
@@ -124,9 +219,7 @@ public class MarkerEditActivity extends AbstractActivity {
});
if (newMarker) {
statisticsSection.setVisibility(View.GONE);
waypointSection.setVisibility(View.VISIBLE);
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
if (nextWaypointNumber == -1) {
nextWaypointNumber = 0;
}
@@ -141,46 +234,87 @@ public class MarkerEditActivity extends AbstractActivity {
finish();
return;
}
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
statisticsSection.setVisibility(statistics ? View.VISIBLE : View.GONE);
waypointSection.setVisibility(statistics ? View.GONE : View.VISIBLE);
if (statistics) {
statisticsName.setText(waypoint.getName());
} else {
waypointName.setText(waypoint.getName());
waypointMarkerType.setText(waypoint.getCategory());
waypointDescription.setText(waypoint.getDescription());
waypointName.setText(waypoint.getName());
waypointMarkerType.setText(waypoint.getCategory());
waypointDescription.setText(waypoint.getDescription());
if (waypoint.hasPhoto()) {
photoUri = waypoint.getPhotoURI();
setWaypointImageView(photoUri);
}
}
hideAndShowOptions();
}
/**
* Checks and hide/shows all buttons/options about marker photo options.
*
* If a photo is set then one's options are shown, otherwise another ones are shown.
*/
private void hideAndShowOptions() {
boolean isPhotoSet = (waypoint != null && waypoint.hasPhoto()) || photoUri != null;
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
insertPhotoMenuItem.setVisible(!isPhotoSet);
insertGalleryImgMenuItem.setVisible(!isPhotoSet);
}
waypointDeletePhotoBtn.setVisibility(isPhotoSet ? View.VISIBLE : View.GONE);
}
/**
* Returns the trackId. If trackId == -1 then get it from waypoint object.
*/
private long getTrackId() {
return trackId == -1 ? waypoint.getTrackId() : trackId;
}
/**
* Sets the ImageView waypointPhoto with the uri photo.
*
* @param uri the uri photo.
*/
private void setWaypointImageView(Uri uri) {
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
FileDescriptor fd = pfd.getFileDescriptor();
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
waypointPhoto.setImageBitmap(bitmap);
hideAndShowOptions();
} catch(IOException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
}
}
private void createWaypointWithPicture() {
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
photoUri = intentAndPhotoUri.second;
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
}
private void createWaypointWithGalleryImage() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
}
/**
* Adds a marker.
*/
private void addMarker() {
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(
WaypointType.WAYPOINT,
false,
trackRecordingServiceConnection.addMarker(this,
waypointName.getText().toString(),
waypointMarkerType.getText().toString(),
waypointDescription.getText().toString(),
null,
null);
trackRecordingServiceConnection.addMarker(this, waypointCreationRequest);
photoUri != null ? photoUri.toString() : null);
}
/**
* Saves a marker.
*/
private void saveMarker() {
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
if (statistics) {
waypoint.setName(statisticsName.getText().toString());
} else {
waypoint.setName(waypointName.getText().toString());
waypoint.setCategory(waypointMarkerType.getText().toString());
waypoint.setDescription(waypointDescription.getText().toString());
}
waypoint.setName(waypointName.getText().toString());
waypoint.setCategory(waypointMarkerType.getText().toString());
waypoint.setDescription(waypointDescription.getText().toString());
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
new ContentProviderUtils(this).updateWaypoint(waypoint);
}
}
@@ -37,9 +37,9 @@ import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.WaypointsColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.util.ActivityUtils;
@@ -61,7 +61,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
private ContentProviderUtils contentProviderUtils;
@@ -70,12 +70,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private boolean recordingTrackPaused;
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
// Note that the key can be null
@@ -100,7 +96,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
// UI elements
private ListView listView;
// Callback when an item is selected in the contextual action mode
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
boolean isSingleSelection = ids.length == 1;
@@ -127,17 +123,14 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null;
final long trackFirstWaypointId = trackId != -1 ? contentProviderUtils.getFirstWaypointId(trackId) : -1;
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
listView = findViewById(R.id.marker_list);
listView.setEmptyView(findViewById(R.id.marker_list_empty));
@@ -166,6 +159,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
String category = cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
String photoUrl = cursor.getString(photoUrlIndex);
//TODO also show latitude and longitude in list
double latitude = cursor.getDouble(latitudeIndex);
double longitude = cursor.getDouble(longitudeIndex);
@@ -181,11 +175,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
if (track != null) {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + "!=?",
new String[]{String.valueOf(track.getId()), String.valueOf(trackFirstWaypointId)}, null);
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(track.getId())}, null);
} else {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.STARTTIME + " IS NULL", null, null);
null, null, null);
}
}
@@ -244,7 +238,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (track != null && item.getItemId() == R.id.marker_list_insert_marker) {
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId());
@@ -40,17 +40,17 @@ import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.SearchEngine;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
@@ -104,7 +104,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private boolean recordingTrackPaused;
// Callback when an item is selected in the contextual action mode
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
@@ -172,7 +172,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
searchEngine = new SearchEngine(contentProviderUtils);
arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) {
@@ -245,7 +245,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@Override
@@ -424,7 +424,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private Map<String, Object> prepareTrackForDisplay(Track track) {
Map<String, Object> resultMap = new HashMap<>();
TripStatistics tripStatistics = track.getTripStatistics();
TrackStatistics trackStatistics = track.getTrackStatistics();
String icon = track.getIcon();
String category = icon != null && !icon.equals("") ? null : track.getCategory();
@@ -433,10 +433,10 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon));
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track);
resultMap.put(NAME_FIELD, track.getName());
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatistics.getTotalTime()));
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatistics.getTotalDistance(), metricUnits));
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, trackStatistics.getTotalDistance(), metricUnits));
resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId()));
resultMap.put(START_TIME_FIELD, tripStatistics.getStartTime());
resultMap.put(START_TIME_FIELD, trackStatistics.getStartTime_ms());
resultMap.put(CATEGORY_FIELD, category);
resultMap.put(DESCRIPTION_FIELD, track.getDescription());
resultMap.put(PHOTO_URL_FIELD, null);
@@ -14,7 +14,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
*/
public class Startup extends Application {
private final static String TAG = Startup.class.getSimpleName();
private static final String TAG = Startup.class.getSimpleName();
@Override
public void onCreate() {
@@ -1,153 +0,0 @@
/*
* Copyright 2013q 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.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabHost.TabContentFactory;
import android.widget.TabWidget;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
/**
* This is a helper class that implements the management of tabs and all details
* of connecting a ViewPager with associated TabHost. It relies on a trick.
* Normally a tab host has a simple API for supplying a View or Intent that each
* tab will show. This is not sufficient for switching between pages. So instead
* we make the content part of the tab host 0dp high (it is not shown) and the
* TabsAdapter supplies its own dummy view to show as the tab content. It
* listens to changes in tabs, and takes care of switch to the correct paged in
* the ViewPager whenever the selected tab changes.
* <p>
* Copied from the FragmentTabsPager sample in the support library.
*
* @author Jimmy Shih
*/
//TODO Rework or better remove.
public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
private final Context context;
private final TabHost tabHost;
private final ViewPager viewPager;
private final ArrayList<TabInfo> tabInfos = new ArrayList<>();
public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager viewPager) {
super(activity.getSupportFragmentManager());
this.context = activity;
this.tabHost = tabHost;
this.viewPager = viewPager;
this.tabHost.setOnTabChangedListener(this);
this.viewPager.setAdapter(this);
this.viewPager.setOnPageChangeListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle bundle) {
tabSpec.setContent(new DummyTabFactory(context));
TabInfo info = new TabInfo(clss, bundle);
tabInfos.add(info);
tabHost.addTab(tabSpec);
notifyDataSetChanged();
}
@Override
public int getCount() {
return tabInfos.size();
}
@Override
@NonNull
public Fragment getItem(int position) {
TabInfo info = tabInfos.get(position);
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
}
@Override
public void onTabChanged(String tabId) {
int position = tabHost.getCurrentTab();
viewPager.setCurrentItem(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
/*
* Unfortunately when TabHost changes the current tab, it kindly also takes care of putting focus on it when not in touch mode.
* The jerk. This hack tries to prevent this from pulling focus out of our ViewPager.
*/
TabWidget tabWidget = tabHost.getTabWidget();
int oldFocusability = tabWidget.getDescendantFocusability();
tabWidget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
tabHost.setCurrentTab(position);
tabWidget.setDescendantFocusability(oldFocusability);
}
@Override
public void onPageScrollStateChanged(int state) {
}
/**
* An object to hold a tab's info.
*
* @author Jimmy Shih
*/
private static final class TabInfo {
private final Class<?> clss;
private final Bundle bundle;
TabInfo(Class<?> clss, Bundle bundle) {
this.clss = clss;
this.bundle = bundle;
}
}
/**
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the {@link TabHost} API.
*
* @author Jimmy Shih
*/
private static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context context;
DummyTabFactory(Context context) {
this.context = context;
}
@Override
public View createTabContent(String tag) {
View view = new View(context);
view.setMinimumWidth(0);
view.setMinimumHeight(0);
return view;
}
}
}
@@ -23,8 +23,8 @@ import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.TextView;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -61,7 +61,7 @@ public class TrackController {
public void run() {
if (isResumed && isRecording && !isPaused) {
totalTimeTextView.setText(StringUtils.formatElapsedTimeWithHour(System.currentTimeMillis() - totalTimeTimestamp + totalTime));
handlerUpdateTotalTime.postDelayed(this, UnitConversions.ONE_SECOND);
handlerUpdateTotalTime.postDelayed(this, UnitConversions.ONE_SECOND_MS);
}
}
};
@@ -115,7 +115,7 @@ public class TrackController {
totalTimeTextView.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
if (!isPaused) {
totalTimeTimestamp = System.currentTimeMillis();
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UnitConversions.ONE_SECOND);
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UnitConversions.ONE_SECOND_MS);
}
}
}
@@ -142,7 +142,7 @@ public class TrackController {
* Gets the total time for the current recording track.
*/
private long getTotalTime() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
return trackRecordingService != null ? trackRecordingService.getTotalTime() : 0L;
}
}
@@ -23,14 +23,13 @@ import android.content.Intent;
import android.os.Bundle;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.R;
/**
* An activity for delete tracks.
*
* @author Jimmy Shih
*/
public class DeleteActivity extends Activity {
public class TrackDeleteActivity extends Activity {
public static final String EXTRA_TRACK_IDS = "track_ids";
@@ -16,50 +16,44 @@
package de.dennisguse.opentracks;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Pair;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import com.google.android.material.tabs.TabLayout;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
import de.dennisguse.opentracks.fragments.ChartTimeFragment;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.StatsFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
/**
* An activity to show the track detail.
* An activity to show the track detail, record a new track or resumes an existing one.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
@@ -72,21 +66,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
private static final String TAG = TrackDetailActivity.class.getSimpleName();
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
private static final String PHOTO_URI_KEY = "photo_uri_key";
private static final String HAS_PHOTO_KEY = "has_photo_key";
private static final int CAMERA_REQUEST_CODE = 5;
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
// The following are set in onCreate
private boolean hasCamera;
private Uri photoUri;
private boolean hasPhoto;
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackDataHub trackDataHub;
private TabHost tabHost;
private ViewPager pager;
private TrackController trackController;
// From intent
@@ -104,23 +90,39 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
@Override
public void run() {
trackController.update(isRecording(), recordingTrackPaused);
if (hasPhoto && photoUri != null) {
hasPhoto = false;
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString());
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, waypointCreationRequest);
if (markerId != -1L) {
//TODO: Make configurable.
FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri);
}
}
}
});
if (recordingTrackId == -1L) {
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.d(TAG, "could not get TrackRecordingService");
return;
}
// Starts or resumes a track.
int msg;
if (trackId == -1L) {
// trackId isn't initialized -> leads a new recording.
trackId = service.startNewTrack();
recordingTrackId = trackId;
msg = R.string.track_detail_record_success;
} else {
// trackId is initialized -> resumes the track.
recordingTrackId = trackId;
service.resumeTrack(trackId);
msg = R.string.track_detail_resume_success;
}
// A recording track is on.
Toast.makeText(TrackDetailActivity.this, msg, Toast.LENGTH_SHORT).show();
trackDataHub.loadTrack(trackId);
trackController.update(true, false);
trackController.onResume(true, recordingTrackPaused);
}
}
};
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
@@ -158,9 +160,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
};
private MenuItem insertMarkerMenuItem;
private MenuItem insertPhotoMenuItem;
private MenuItem markerListMenuItem;
private MenuItem shareMenuItem;
private MenuItem resumeMenuItem;
private final OnClickListener recordListener = new OnClickListener() {
@Override
@@ -192,40 +194,52 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
contentProviderUtils = new ContentProviderUtils(this);
handleIntent(getIntent());
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
trackDataHub = TrackDataHub.newInstance(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
trackDataHub = new TrackDataHub(this);
tabHost = findViewById(R.id.tackdetail_tabhost);
tabHost.setup();
FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
@Override
public int getCount() {
return 3;
}
ViewPager viewPager = findViewById(R.id.pager);
TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager);
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
TabSpec chartTimeTabSpec = tabHost.newTabSpec(ChartTimeFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_time));
tabsAdapter.addTab(chartTimeTabSpec, ChartTimeFragment.class, null);
TabSpec chartDistanceTabSpec = tabHost.newTabSpec(ChartDistanceFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_distance));
tabsAdapter.addTab(chartDistanceTabSpec, ChartDistanceFragment.class, null);
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 1:
return ChartFragment.newInstance(false);
case 2:
return ChartFragment.newInstance(true);
default: //0
return new StatsFragment();
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.track_detail_stats_tab);
case 1:
return getString(R.string.settings_chart_by_time);
case 2:
return getString(R.string.settings_chart_by_distance);
}
return "Unknown Tab";
}
};
pager = findViewById(R.id.track_detail_activity_view_pager);
pager.setAdapter(adapter);
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
tabs.setupWithViewPager(pager);
if (savedInstanceState != null) {
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
}
// Set the background after all three tabs are added
for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) {
tabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.tab_indicator);
pager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
}
trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener);
@@ -276,11 +290,16 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
@Override
protected void onResume() {
super.onResume();
trackDataHub.loadTrack(trackId);
// Update UI
this.invalidateOptionsMenu();
trackController.onResume(isRecording(), recordingTrackPaused);
if (trackId != -1L) {
trackDataHub.loadTrack(trackId);
trackController.onResume(isRecording(), recordingTrackPaused);
} else {
startRecording();
}
}
@Override
@@ -293,30 +312,14 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop();
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
if (photoUri != null) {
outState.putParcelable(PHOTO_URI_KEY, photoUri);
}
outState.putBoolean(HAS_PHOTO_KEY, hasPhoto);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
return;
}
hasPhoto = resultCode == RESULT_OK;
}
super.onActivityResult(requestCode, resultCode, data);
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
}
@Override
@@ -336,10 +339,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
getMenuInflater().inflate(R.menu.track_detail, menu);
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
shareMenuItem = menu.findItem(R.id.track_detail_share);
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
resumeMenuItem = menu.findItem(R.id.track_detail_resume_track);
return super.onCreateOptionsMenu(menu);
}
@@ -365,13 +367,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
intent = Intent.createChooser(intent, null);
startActivity(intent);
return true;
case R.id.track_detail_insert_photo:
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
return false;
}
createWaypointWithPicture();
return true;
case R.id.track_detail_menu_show_on_map:
IntentUtils.showTrackOnMap(this, new long[]{trackId});
return true;
@@ -388,6 +383,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
case R.id.track_detail_delete:
deleteTracks(new long[]{trackId});
return true;
case R.id.track_detail_resume_track:
startRecording();
return true;
case R.id.track_detail_settings:
intent = IntentUtils.newIntent(this, SettingsActivity.class);
startActivity(intent);
@@ -397,20 +395,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
} else {
createWaypointWithPicture();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
return trackRecordingServiceConnection;
@@ -444,9 +428,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
return;
}
trackId = waypoint.getTrackId();
if (trackId == -1L) {
finish();
return;
}
}
if (trackId == -1L) {
finish();
return;
}
Track track = contentProviderUtils.getTrack(trackId);
@@ -468,25 +456,21 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
*/
private void updateMenuItems(boolean isPaused) {
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
shareMenuItem.setVisible(!isRecording());
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
String title;
if (isRecording()) {
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
resumeMenuItem.setVisible(false);
} else {
Track track = contentProviderUtils.getTrack(trackId);
title = track != null ? track.getName() : "";
// Only visible if there aren't other tracks that is recording.
resumeMenuItem.setVisible(recordingTrackId == -1L);
}
setTitle(title);
}
private void createWaypointWithPicture() {
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, trackId);
photoUri = intentAndPhotoUri.second;
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
}
public void chooseActivityType(String category) {
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
}
@@ -501,4 +485,15 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
private boolean isRecording() {
return trackId == recordingTrackId;
}
private void startRecording() {
trackRecordingServiceConnection.startAndBind(this);
/*
* If the binding has happened, then invoke the callback to start a new recording.
* If the binding hasn't happened, then invoking the callback will have no effect.
* But when the binding occurs, the callback will get invoked.
*/
bindChangedCallback.run();
}
}
@@ -30,8 +30,8 @@ import android.widget.Spinner;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -65,7 +65,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
if (trackId == -1L) {
Log.e(TAG, "invalid trackId");
@@ -175,7 +175,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
@Override
protected void onStop() {
super.onStop();
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@Override
@@ -50,11 +50,11 @@ import androidx.loader.content.Loader;
import java.util.Locale;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils;
@@ -127,10 +127,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private boolean recordingTrackPaused;
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
* Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
@@ -141,7 +137,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
trackRecordingServiceConnection.startAndBind();
trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
}
}
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
@@ -164,7 +160,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Menu items
private MenuItem searchMenuItem;
private MenuItem startGpsMenuItem;
private MenuItem deleteAllMenuItem;
private final OnClickListener stopListener = new OnClickListener() {
@Override
@@ -176,8 +171,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private boolean startGps = false; // true to start gps
private boolean startNewRecording = false; // true to start a new recording
// Callback when the trackRecordingServiceConnection binding changes.
private final Runnable bindChangedCallback = new Runnable() {
@Override
@@ -192,26 +185,15 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
}
});
if (!startGps && !startNewRecording) {
if (!startGps) {
return;
}
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.d(TAG, "service not available to start gps or a new recording");
return;
}
if (startNewRecording) {
startGps = false;
long trackId = service.startNewTrack();
startNewRecording = false;
Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
startActivity(intent);
Toast.makeText(TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
.show();
}
if (startGps) {
service.startGps();
startGps = false;
@@ -224,18 +206,19 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
if (!PreferencesUtils.isRecording(recordingTrackId)) {
// Not recording -> Recording
updateMenuItems(false, true);
startRecording();
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class);
startActivity(newIntent);
} else if (recordingTrackPaused) {
// Paused -> Resume
updateMenuItems(false, true);
trackRecordingServiceConnection.resumeTrack();
trackController.update(true, false);
} else {
// Recording -> Paused
updateMenuItems(false, true);
trackRecordingServiceConnection.pauseTrack();
trackController.update(true, true);
}
// Paused -> Resume
updateMenuItems(false, true);
trackRecordingServiceConnection.resumeTrack();
trackController.update(true, false);
} else {
// Recording -> Paused
updateMenuItems(false, true);
trackRecordingServiceConnection.pauseTrack();
trackController.update(true, true);
}
}
};
@@ -248,7 +231,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
@@ -344,7 +327,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@Override
@@ -371,7 +354,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
return super.onCreateOptionsMenu(menu);
}
@@ -407,14 +389,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Invoke trackRecordingService
if (startGps) {
trackRecordingServiceConnection.startAndBind();
trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run();
} else {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) {
trackRecordingService.stopGps();
}
trackRecordingServiceConnection.unbindAndStop();
trackRecordingServiceConnection.unbindAndStop(this);
}
// Update menu after starting or stopping gps
@@ -425,9 +407,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
startActivity(intent);
return true;
case R.id.track_list_delete_all:
deleteTracks(new long[]{-1L});
return true;
case R.id.track_list_settings:
intent = IntentUtils.newIntent(this, SettingsActivity.class);
startActivity(intent);
@@ -464,7 +443,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
/**
* Shows start up dialogs.
*/
public void showStartupDialogs() {
private void showStartupDialogs() {
// If stats_units_key is undefined, set it
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
@@ -487,7 +466,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
* @param isRecording true if recording
*/
private void updateMenuItems(boolean isGpsStarted, boolean isRecording) {
boolean hasTrack = listView != null && listView.getCount() != 0;
if (startGpsMenuItem != null) {
startGpsMenuItem.setVisible(!isRecording);
if (!isRecording) {
@@ -495,24 +473,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
}
}
if (deleteAllMenuItem != null) {
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
}
}
/**
* Starts a new recording.
*/
private void startRecording() {
startNewRecording = true;
trackRecordingServiceConnection.startAndBind();
/*
* If the binding has happened, then invoke the callback to start a new recording.
* If the binding hasn't happened, then invoking the callback will have no effect.
* But when the binding occurs, the callback will get invoked.
*/
bindChangedCallback.run();
}
/**
@@ -13,7 +13,7 @@ import androidx.annotation.Nullable;
*/
public class ContentResolverWrapper implements IContentResolver {
private ContentResolver contentResolver;
private final ContentResolver contentResolver;
public ContentResolverWrapper(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
@@ -0,0 +1,103 @@
package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.UnitConversions;
public class ChartPoint {
//X-axis
private double timeOrDistance;
//Y-axis
private double elevation;
private double speed;
private double pace;
private double heartRate = Double.NaN;
private double cadence = Double.NaN;
private double power = Double.NaN;
@VisibleForTesting
ChartPoint(double elevation) {
this.elevation = elevation;
}
public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, SensorDataSet sensorDataSet, boolean chartByDistance, boolean metricUnits) {
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
if (chartByDistance) {
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
} else {
timeOrDistance = trackStatistics.getTotalTime();
}
elevation = trackStatisticsUpdater.getSmoothedElevation();
if (!metricUnits) {
elevation *= UnitConversions.M_TO_FT;
}
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
if (sensorDataSet != null) {
if (sensorDataSet.hasHeartRate()) {
heartRate = sensorDataSet.getHeartRate();
}
if (sensorDataSet.hasCadence()) {
cadence = sensorDataSet.getCadence();
}
if (sensorDataSet.hasPower()) {
power = sensorDataSet.getPower();
}
}
}
public double getTimeOrDistance() {
return timeOrDistance;
}
public double getElevation() {
return elevation;
}
public double getSpeed() {
return speed;
}
public double getPace() {
return pace;
}
public boolean hasHeartRate() {
return Double.isNaN(heartRate);
}
public double getHeartRate() {
return heartRate;
}
public boolean hasCadence() {
return Double.isNaN(cadence);
}
public double getCadence() {
return cadence;
}
public boolean hasPower() {
return Double.isNaN(power);
}
public double getPower() {
return power;
}
}
@@ -23,7 +23,7 @@ import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.NonNull;
import java.text.NumberFormat;
@@ -34,7 +34,7 @@ import de.dennisguse.opentracks.stats.ExtremityMonitor;
*
* @author Sandor Dornbush
*/
class ChartValueSeries {
abstract class ChartValueSeries {
private static final float STROKE_WIDTH = 2f;
@@ -128,26 +128,26 @@ class ChartValueSeries {
}
/**
* Updates the series with a new value.
*
* @param value the new value
* Updates the series with a new {@link ChartPoint}.
*/
void update(double value) {
extremityMonitor.update(value);
void update(ChartPoint chartPoint) {
if (isChartPointValid(chartPoint)) {
extremityMonitor.update(extractDataFromChartPoint(chartPoint));
}
}
/**
* Gets the path.
*/
abstract double extractDataFromChartPoint(@NonNull ChartPoint chartPoint);
boolean isChartPointValid(@NonNull ChartPoint chartPoint) {
return !Double.isNaN(extractDataFromChartPoint(chartPoint));
}
protected abstract boolean drawIfChartPointHasNoData();
Path getPath() {
return path;
}
/**
* Draws the path on canvas.
*
* @param canvas the canvas
*/
void drawPath(Canvas canvas) {
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, strokePaint);
@@ -203,7 +203,6 @@ class ChartValueSeries {
/**
* Gets the interval value.
*/
@VisibleForTesting
int getInterval() {
return interval;
}
@@ -218,7 +217,6 @@ class ChartValueSeries {
/**
* Gets the max marker value.
*/
@VisibleForTesting
int getMaxMarkerValue() {
return maxMarkerValue;
}
@@ -33,14 +33,16 @@ import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Scroller;
import androidx.annotation.NonNull;
import androidx.core.view.GestureDetectorCompat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.MarkerDetailActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.ExtremityMonitor;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
@@ -56,18 +58,10 @@ import de.dennisguse.opentracks.util.UnitConversions;
*/
public class ChartView extends View {
public static final float MEDIUM_TEXT_SIZE = 18f;
public static final float SMALL_TEXT_SIZE = 12f;
static final float MEDIUM_TEXT_SIZE = 18f;
static final float SMALL_TEXT_SIZE = 12f;
public static final int Y_AXIS_INTERVALS = 5;
public static final int NUM_SERIES = 6;
public static final int ELEVATION_SERIES = 0;
public static final int SPEED_SERIES = 1;
public static final int PACE_SERIES = 2;
public static final int HEART_RATE_SERIES = 3;
public static final int CADENCE_SERIES = 4;
public static final int POWER_SERIES = 5;
static final int Y_AXIS_INTERVALS = 5;
private static final int TARGET_X_AXIS_INTERVALS = 4;
@@ -80,19 +74,27 @@ public class ChartView extends View {
private static final int SPACER = 4;
private static final int Y_AXIS_OFFSET = 16;
//TODO Determine from actual size of the used drawable
private static final float WAYPOINT_X_ANCHOR = 13f / 48f;
static {
X_FRACTION_FORMAT.setMaximumFractionDigits(1);
X_FRACTION_FORMAT.setMinimumFractionDigits(1);
}
private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES];
private final ArrayList<double[]> chartData = new ArrayList<>();
private final ArrayList<Waypoint> waypoints = new ArrayList<>();
private final List<ChartValueSeries> seriesList = new ArrayList<>();
private final ChartValueSeries speedSeries;
private final ChartValueSeries paceSeries;
private final List<ChartPoint> chartPoints = new ArrayList<>();
private final List<Waypoint> waypoints = new ArrayList<>();
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
private final Paint axisPaint;
private final Paint xAxisMarkerPaint;
private final Paint gridPaint;
private final Paint markerPaint;
private final Drawable pointer;
private final Drawable waypointMarker;
private final int markerWidth;
private final int markerHeight;
private final Scroller scroller;
@@ -116,7 +118,7 @@ public class ChartView extends View {
private boolean reportSpeed = true;
private boolean showPointer = false;
private GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() {
private final GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
@@ -146,7 +148,7 @@ public class ChartView extends View {
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
// Check if the y event is within markerHeight of the marker center
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) {
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) {
int minDistance = Integer.MAX_VALUE;
Waypoint nearestWaypoint = null;
synchronized (waypoints) {
@@ -170,7 +172,7 @@ public class ChartView extends View {
}
});
private ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
private final ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
@@ -186,58 +188,124 @@ public class ChartView extends View {
}
});
public ChartView(Context context, boolean chartByDistance) {
public ChartView(Context context, final boolean chartByDistance) {
super(context);
this.chartByDistance = chartByDistance;
series[ELEVATION_SERIES] = new ChartValueSeries(context,
seriesList.add(new ChartValueSeries(context,
Integer.MIN_VALUE,
Integer.MAX_VALUE,
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
R.string.description_elevation_metric,
R.string.description_elevation_imperial,
R.color.chart_elevation_fill,
R.color.chart_elevation_border);
series[SPEED_SERIES] = new ChartValueSeries(context,
R.color.chart_elevation_border) {
@Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getElevation();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return true;
}
});
speedSeries = new ChartValueSeries(context,
0,
Integer.MAX_VALUE,
new int[]{1, 5, 10, 20, 50, 100},
R.string.description_speed_metric,
R.string.description_speed_imperial,
R.color.chart_speed_fill,
R.color.chart_speed_border);
series[PACE_SERIES] = new ChartValueSeries(context,
R.color.chart_speed_border) {
@Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getSpeed();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return reportSpeed;
}
};
seriesList.add(speedSeries);
paceSeries = new ChartValueSeries(context,
0,
Integer.MAX_VALUE,
new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120},
R.string.description_pace_metric,
R.string.description_pace_imperial,
R.color.chart_pace_fill,
R.color.chart_pace_border);
series[HEART_RATE_SERIES] = new ChartValueSeries(context,
R.color.chart_pace_border) {
@Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getPace();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return !reportSpeed;
}
};
seriesList.add(paceSeries);
seriesList.add(new ChartValueSeries(context,
0,
Integer.MAX_VALUE,
new int[]{25, 50},
R.string.description_sensor_heart_rate,
R.string.description_sensor_heart_rate,
R.color.chart_heart_rate_fill,
R.color.chart_heart_rate_border);
series[CADENCE_SERIES] = new ChartValueSeries(context,
R.color.chart_heart_rate_border) {
@Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getHeartRate();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
});
seriesList.add(new ChartValueSeries(context,
0,
Integer.MAX_VALUE,
new int[]{5, 10, 25, 50},
R.string.description_sensor_cadence,
R.string.description_sensor_cadence,
R.color.chart_cadence_fill,
R.color.chart_cadence_border);
series[POWER_SERIES] = new ChartValueSeries(context,
R.color.chart_cadence_border) {
@Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getCadence();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
});
seriesList.add(new ChartValueSeries(context,
0,
1000,
new int[]{5, 50, 100, 200},
R.string.description_sensor_power,
R.string.description_sensor_power,
R.color.chart_power_fill,
R.color.chart_power_border);
R.color.chart_power_border) {
@Override
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.getPower();
}
@Override
protected boolean drawIfChartPointHasNoData() {
return false;
}
});
float scale = context.getResources().getDisplayMetrics().density;
@@ -256,7 +324,7 @@ public class ChartView extends View {
gridPaint.setAntiAlias(false);
gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0));
Paint markerPaint = new Paint();
markerPaint = new Paint();
markerPaint.setStyle(Style.STROKE);
markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray));
markerPaint.setAntiAlias(false);
@@ -264,7 +332,7 @@ public class ChartView extends View {
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
Drawable waypointMarker = MarkerUtils.getDefaultPhoto(context);
waypointMarker = MarkerUtils.getDefaultPhoto(context);
markerWidth = waypointMarker.getIntrinsicWidth();
markerHeight = waypointMarker.getIntrinsicHeight();
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
@@ -273,6 +341,10 @@ public class ChartView extends View {
setFocusable(true);
setClickable(true);
updateDimensions();
// either speedSeries or paceSeries should be enabled.
speedSeries.setEnabled(reportSpeed);
paceSeries.setEnabled(!reportSpeed);
}
@Override
@@ -280,13 +352,8 @@ public class ChartView extends View {
return true;
}
/**
* Sets the enabled value for a chart value series.
*
* @param index the chart value series index
*/
public void setChartValueSeriesEnabled(int index, boolean enabled) {
series[index].setEnabled(enabled);
public boolean getMetricUnits() {
return metricUnits;
}
/**
@@ -298,39 +365,48 @@ public class ChartView extends View {
metricUnits = value;
}
public boolean getReportSpeed() {
return reportSpeed;
}
/**
* Sets report speed.
*
* @param value true to report speed
* @param value report speed (true) or pace (false)
*/
public void setReportSpeed(boolean value) {
reportSpeed = value;
}
/**
* Sets show pointer.
*
* @param value true to show pointer
*/
public boolean applyReportSpeed() {
if (reportSpeed) {
if (!speedSeries.isEnabled()) {
speedSeries.setEnabled(true);
paceSeries.setEnabled(false);
return true;
}
} else {
if (!paceSeries.isEnabled()) {
speedSeries.setEnabled(false);
paceSeries.setEnabled(true);
return true;
}
}
return false;
}
public void setShowPointer(boolean value) {
showPointer = value;
}
/**
* Adds data points.
*
* @param dataPoints an array of data points to be added
*/
public void addDataPoints(ArrayList<double[]> dataPoints) {
synchronized (chartData) {
chartData.addAll(dataPoints);
for (int i = 0; i < dataPoints.size(); i++) {
double[] dataPoint = dataPoints.get(i);
xExtremityMonitor.update(dataPoint[0]);
for (int j = 0; j < series.length; j++) {
if (!Double.isNaN(dataPoint[j + 1])) {
series[j].update(dataPoint[j + 1]);
}
public void addChartPoints(List<ChartPoint> dataPoints) {
synchronized (chartPoints) {
chartPoints.addAll(dataPoints);
for (ChartPoint dataPoint : dataPoints) {
xExtremityMonitor.update(dataPoint.getTimeOrDistance());
for (ChartValueSeries i : seriesList) {
i.update(dataPoint);
}
}
updateDimensions();
@@ -342,8 +418,8 @@ public class ChartView extends View {
* Clears all data.
*/
public void reset() {
synchronized (chartData) {
chartData.clear();
synchronized (chartPoints) {
chartPoints.clear();
xExtremityMonitor.reset();
zoomLevel = 1;
updateDimensions();
@@ -351,50 +427,34 @@ public class ChartView extends View {
}
/**
* Resets scroll. To be called on the UI thread.
* Resets scroll.
* To be called on the UI thread.
*/
public void resetScroll() {
scrollTo(0, 0);
}
/**
* Adds a waypoint.
*
* @param waypoint the waypoint
*/
public void addWaypoint(Waypoint waypoint) {
synchronized (waypoints) {
waypoints.add(waypoint);
}
}
/**
* Clears the waypoints.
*/
public void clearWaypoints() {
synchronized (waypoints) {
waypoints.clear();
}
}
/**
* Returns true if can zoom in.
*/
public boolean canZoomIn() {
private boolean canZoomIn() {
return zoomLevel < MAX_ZOOM_LEVEL;
}
/**
* Returns true if can zoom out.
*/
public boolean canZoomOut() {
private boolean canZoomOut() {
return zoomLevel > MIN_ZOOM_LEVEL;
}
/**
* Zooms in one level.
*/
public void zoomIn() {
private void zoomIn() {
if (canZoomIn()) {
zoomLevel++;
updatePaths();
@@ -402,10 +462,7 @@ public class ChartView extends View {
}
}
/**
* Zooms out one level.
*/
public void zoomOut() {
private void zoomOut() {
if (canZoomOut()) {
zoomLevel--;
scroller.abortAnimation();
@@ -425,7 +482,7 @@ public class ChartView extends View {
*
* @param velocityX velocity of fling in pixels per second
*/
public void fling(int velocityX) {
private void fling(int velocityX) {
int maxWidth = effectiveWidth * (zoomLevel - 1);
scroller.fling(getScrollX(), 0, velocityX, 0, 0, maxWidth, 0, 0);
invalidate();
@@ -436,7 +493,7 @@ public class ChartView extends View {
*
* @param deltaX the number of pixels to scroll
*/
public void scrollBy(int deltaX) {
private void scrollBy(int deltaX) {
int scrollX = getScrollX() + deltaX;
if (scrollX < 0) {
scrollX = 0;
@@ -480,8 +537,7 @@ public class ChartView extends View {
@Override
protected void onDraw(Canvas canvas) {
synchronized (chartData) {
synchronized (chartPoints) {
canvas.save();
canvas.drawColor(Color.WHITE);
@@ -490,6 +546,7 @@ public class ChartView extends View {
clipToGraphArea(canvas);
drawDataSeries(canvas);
drawWaypoints(canvas);
drawGrid(canvas);
canvas.restore();
@@ -523,13 +580,36 @@ public class ChartView extends View {
* @param canvas the canvas
*/
private void drawDataSeries(Canvas canvas) {
for (ChartValueSeries chartValueSeries : series) {
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
chartValueSeries.drawPath(canvas);
}
}
}
/**
* Draws the waypoints.
*
* @param canvas the canvas
*/
private void drawWaypoints(Canvas canvas) {
synchronized (waypoints) {
for (Waypoint waypoint : waypoints) {
double xValue = getWaypointXValue(waypoint);
if (xValue > maxX) {
continue;
}
canvas.save();
float x = getX(getWaypointXValue(waypoint));
canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
canvas.translate(x - (markerWidth * WAYPOINT_X_ANCHOR), topBorder + spacer);
waypointMarker.draw(canvas);
canvas.restore();
}
}
}
/**
* Draws the grid.
*
@@ -537,9 +617,9 @@ public class ChartView extends View {
*/
private void drawGrid(Canvas canvas) {
// X axis grid
ArrayList<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval());
for (int i = 0; i < xAxisMarkerPositions.size(); i++) {
int x = getX(xAxisMarkerPositions.get(i));
List<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval());
for (double position : xAxisMarkerPositions) {
int x = getX(position);
canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint);
}
// Y axis grid
@@ -562,9 +642,8 @@ public class ChartView extends View {
int lines = titleDimensions[0];
int lineHeight = titleDimensions[1];
int count = 0;
for (int i = 0; i < series.length; i++) {
ChartValueSeries chartValueSeries = series[i];
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
count++;
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
Paint paint = chartValueSeries.getTitlePaint();
@@ -582,9 +661,8 @@ public class ChartView extends View {
private int[] getTitleDimensions() {
int lines = 0;
int lineHeight = 0;
for (int i = 0; i < series.length; i++) {
ChartValueSeries chartValueSeries = series[i];
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
lines++;
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
Rect rect = getRect(chartValueSeries.getTitlePaint(), title);
@@ -611,16 +689,13 @@ public class ChartView extends View {
canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint);
double interval = getXAxisInterval();
ArrayList<Double> markerPositions = getXAxisMarkerPositions(interval);
NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT;
for (int i = 0; i < markerPositions.size(); i++) {
drawXAxisMarker(canvas, markerPositions.get(i), numberFormat, spacer + yOffset);
for (double markerPosition : getXAxisMarkerPositions(interval)) {
drawXAxisMarker(canvas, markerPosition, numberFormat, spacer + yOffset);
}
}
/**
* Gets the x axis label.
*/
private String getXAxisLabel() {
Context context = getContext();
if (chartByDistance) {
@@ -644,9 +719,6 @@ public class ChartView extends View {
canvas.drawText(marker, getX(value), topBorder + effectiveHeight + spacing + rect.height(), xAxisMarkerPaint);
}
/**
* Gets the x axis interval.
*/
private double getXAxisInterval() {
double interval = maxX / zoomLevel / TARGET_X_AXIS_INTERVALS;
if (interval < 1) {
@@ -661,16 +733,13 @@ public class ChartView extends View {
return interval;
}
/**
* Gets the x axis marker positions.
*/
private ArrayList<Double> getXAxisMarkerPositions(double interval) {
ArrayList<Double> markers = new ArrayList<>();
private List<Double> getXAxisMarkerPositions(double interval) {
List<Double> markers = new ArrayList<>();
markers.add(0d);
for (int i = 1; i * interval < maxX; i++) {
markers.add(i * interval);
}
// At least 2 markers
if (markers.size() < 2) {
markers.add(maxX);
}
@@ -687,11 +756,12 @@ public class ChartView extends View {
int y = topBorder;
canvas.drawLine(x, y, x, y + effectiveHeight, axisPaint);
//TODO
int markerXPosition = x - spacer;
for (int i = 0; i < series.length; i++) {
int index = series.length - 1 - i;
ChartValueSeries chartValueSeries = series[index];
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(index)) {
for (int i = 0; i < seriesList.size(); i++) {
int index = seriesList.size() - 1 - i;
ChartValueSeries chartValueSeries = seriesList.get(index);
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
markerXPosition -= drawYAxisMarkers(chartValueSeries, canvas, markerXPosition) + spacer;
}
}
@@ -739,29 +809,29 @@ public class ChartView extends View {
* @param canvas the canvas
*/
private void drawPointer(Canvas canvas) {
int index = -1;
for (int i = 0; i < series.length; i++) {
ChartValueSeries chartValueSeries = series[i];
ChartValueSeries firstChartValueSeries = null;
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
index = i;
firstChartValueSeries = chartValueSeries;
break;
}
}
if (index != -1 && chartData.size() > 0) {
if (firstChartValueSeries != null && chartPoints.size() > 0) {
int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2;
int dy = getY(series[index], chartData.get(chartData.size() - 1)[index + 1])
- pointer.getIntrinsicHeight();
double value = firstChartValueSeries.extractDataFromChartPoint(chartPoints.get(chartPoints.size() - 1));
int dy = getY(firstChartValueSeries, value) - pointer.getIntrinsicHeight();
canvas.translate(dx, dy);
pointer.draw(canvas);
}
}
/**
* Updates paths. The path needs to be updated any time after the data or the dimensions change.
* Updates paths.
* The path needs to be updated any time after the data or the dimensions change.
*/
private void updatePaths() {
synchronized (chartData) {
for (ChartValueSeries chartValueSeries : series) {
synchronized (chartPoints) {
for (ChartValueSeries chartValueSeries : seriesList) {
chartValueSeries.getPath().reset();
}
drawPaths();
@@ -769,25 +839,23 @@ public class ChartView extends View {
}
}
/**
* Draws all paths.
*/
private void drawPaths() {
boolean[] hasMoved = new boolean[series.length];
boolean[] hasMoved = new boolean[seriesList.size()];
for (int i = 0; i < chartData.size(); i++) {
double[] dataPoint = chartData.get(i);
for (int j = 0; j < series.length; j++) {
double value = dataPoint[j + 1];
if (Double.isNaN(value)) {
for (ChartPoint dataPoint : chartPoints) {
for (int i = 0; i < seriesList.size(); i++) {
ChartValueSeries chartValueSeries = seriesList.get(i);
if (chartValueSeries.isChartPointValid(dataPoint)) {
continue;
}
ChartValueSeries chartValueSeries = series[j];
double value = chartValueSeries.extractDataFromChartPoint(dataPoint);
Path path = chartValueSeries.getPath();
int x = getX(dataPoint[0]);
int x = getX(dataPoint.getTimeOrDistance());
int y = getY(chartValueSeries, value);
if (!hasMoved[j]) {
hasMoved[j] = true;
if (!hasMoved[i]) {
hasMoved[i] = true;
path.moveTo(x, y);
} else {
path.lineTo(x, y);
@@ -796,37 +864,32 @@ public class ChartView extends View {
}
}
/**
* Closes all paths.
*/
private void closePaths() {
for (int i = 0; i < series.length; i++) {
int first = getFirstPopulatedChartDataIndex(i);
for (ChartValueSeries chartValueSeries : seriesList) {
int first = getFirstPopulatedChartDataIndex(chartValueSeries);
if (first != -1) {
int xCorner = getX(chartData.get(first)[0]);
int xCorner = getX(chartPoints.get(first).getTimeOrDistance());
int yCorner = topBorder + effectiveHeight;
ChartValueSeries chartValueSeries = series[i];
Path path = chartValueSeries.getPath();
// Bottom right corner
path.lineTo(getX(chartData.get(chartData.size() - 1)[0]), yCorner);
path.lineTo(getX(chartPoints.get(chartPoints.size() - 1).getTimeOrDistance()), yCorner);
// Bottom left corner
path.lineTo(xCorner, yCorner);
// Top right corner
path.lineTo(xCorner, getY(chartValueSeries, chartData.get(first)[i + 1]));
double value = chartValueSeries.extractDataFromChartPoint(chartPoints.get(first));
path.lineTo(xCorner, getY(chartValueSeries, value));
}
}
}
/**
* Finds the index of the first data point containing data for a series.
* Returns -1 if no data point contains data for the series.
*
* @param seriesIndex the series's index
* @return -1 if no data point contains data for the series.
*/
private int getFirstPopulatedChartDataIndex(int seriesIndex) {
for (int i = 0; i < chartData.size(); i++) {
if (!Double.isNaN(chartData.get(i)[seriesIndex + 1])) {
private int getFirstPopulatedChartDataIndex(ChartValueSeries chartValueSeries) {
for (int i = 0; i < chartPoints.size(); i++) {
if (chartValueSeries.isChartPointValid(chartPoints.get(i))) {
return i;
}
}
@@ -838,7 +901,7 @@ public class ChartView extends View {
*/
private void updateDimensions() {
maxX = xExtremityMonitor.hasData() ? xExtremityMonitor.getMax() : 1.0;
for (ChartValueSeries chartValueSeries : series) {
for (ChartValueSeries chartValueSeries : seriesList) {
chartValueSeries.updateDimension();
}
float density = getContext().getResources().getDisplayMetrics().density;
@@ -846,9 +909,8 @@ public class ChartView extends View {
yAxisOffset = (int) (density * Y_AXIS_OFFSET);
int markerLength = 0;
for (int i = 0; i < series.length; i++) {
ChartValueSeries chartValueSeries = series[i];
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
Rect rect = getRect(chartValueSeries.getMarkerPaint(), chartValueSeries.getLargestMarker());
markerLength += rect.width() + spacer;
}
@@ -941,22 +1003,12 @@ public class ChartView extends View {
/**
* Returns true if the index is allowed when the chartData is empty.
*
* @param index the index
*/
private boolean allowIfEmpty(int index) {
if (!chartData.isEmpty()) {
private boolean allowIfEmpty(ChartValueSeries chartValueSeries) {
if (!chartPoints.isEmpty()) {
return false;
}
switch (index) {
case ELEVATION_SERIES:
return true;
case SPEED_SERIES:
return reportSpeed;
case PACE_SERIES:
return !reportSpeed;
default:
return false;
}
return chartValueSeries.drawIfChartPointHasNoData();
}
}
@@ -1,79 +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.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.net.Uri;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Data source on the phone.
*
* @author Rodrigo Damazio
*/
class DataSource {
private final ContentResolver contentResolver;
private final SharedPreferences sharedPreferences;
DataSource(Context context) {
contentResolver = context.getContentResolver();
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
}
/**
* Registers a content observer.
*
* @param uri the uri
* @param observer the observer
*/
void registerContentObserver(Uri uri, ContentObserver observer) {
contentResolver.registerContentObserver(uri, false, observer);
}
/**
* Unregisters a content observer.
*
* @param observer the observer
*/
void unregisterContentObserver(ContentObserver observer) {
contentResolver.unregisterContentObserver(observer);
}
/**
* Registers a shared preference change listener.
*
* @param listener the listener
*/
void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
/**
* Unregisters a shared preference change listener.
*
* @param listener the listener
*/
void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
@@ -1,48 +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;
/**
* Listener to be invoked when {@link DataSource} changes.
*
* @author Jimmy Shih
*/
public interface DataSourceListener {
/**
* Notifies when the tracks table is updated.
*/
void notifyTracksTableUpdated();
/**
* Notifies when the waypoints table is updated.
*/
void notifyWaypointsTableUpdated();
/**
* Notifies when the track points table is updated.
*/
void notifyTrackPointsTableUpdated();
/**
* Notifies when a preference changes.
*
* @param key the preference key
*/
void notifyPreferenceChanged(String key);
}
@@ -1,230 +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.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.os.Handler;
import android.util.Log;
import java.util.EnumSet;
import java.util.Set;
/**
* Creates observers/listeners and manages their registration with {@link DataSource}.
* The observers/listeners calls {@link DataSourceListener} when data changes.
*
* @author Rodrigo Damazio
*/
class DataSourceManager {
private static final String TAG = DataSourceManager.class.getSimpleName();
private final DataSource dataSource;
private final DataSourceListener dataSourceListener;
// Registered listeners
private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
private final Handler handler;
private final TracksTableObserver tracksTableObserver;
private final WaypointsTableObserver waypointsTableObserver;
private final TrackPointsTableObserver trackPointsTableObserver;
private final PreferenceListener preferenceListener;
DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
this.dataSource = dataSource;
this.dataSourceListener = dataSourceListener;
handler = new Handler();
tracksTableObserver = new TracksTableObserver();
waypointsTableObserver = new WaypointsTableObserver();
trackPointsTableObserver = new TrackPointsTableObserver();
preferenceListener = new PreferenceListener();
}
/**
* Updates listeners with data source.
*
* @param listeners the listeners
*/
void updateListeners(EnumSet<TrackDataType> listeners) {
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
/*
* Map SAMPLED_OUT_POINT_UPDATES to POINT_UPDATES since they correspond to
* the same internal listener
*/
if (neededListeners.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)) {
neededListeners.remove(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE);
neededListeners.add(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE);
}
Log.d(TAG, "Updating listeners " + neededListeners);
// Unnecessary = registered - needed
Set<TrackDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
unnecessaryListeners.removeAll(neededListeners);
// Missing = needed - registered
Set<TrackDataType> missingListeners = EnumSet.copyOf(neededListeners);
missingListeners.removeAll(registeredListeners);
// Remove unnecessary listeners
for (TrackDataType trackDataType : unnecessaryListeners) {
unregisterListener(trackDataType);
}
// Add missing listeners
for (TrackDataType trackDataType : missingListeners) {
registerListener(trackDataType);
}
// Update registered listeners
registeredListeners.clear();
registeredListeners.addAll(neededListeners);
}
/**
* Registers a listener with data source.
*
* @param trackDataType the listener data type
*/
private void registerListener(TrackDataType trackDataType) {
switch (trackDataType) {
case TRACKS_TABLE:
dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver);
break;
case WAYPOINTS_TABLE:
dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
break;
case SAMPLED_IN_TRACK_POINTS_TABLE:
dataSource.registerContentObserver(
TrackPointsColumns.CONTENT_URI, trackPointsTableObserver);
break;
case SAMPLED_OUT_TRACK_POINTS_TABLE:
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
break;
case PREFERENCE:
dataSource.registerOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
break;
}
}
/**
* Unregisters a listener with data source.
*
* @param trackDataType listener data type
*/
private void unregisterListener(TrackDataType trackDataType) {
switch (trackDataType) {
case TRACKS_TABLE:
dataSource.unregisterContentObserver(tracksTableObserver);
break;
case WAYPOINTS_TABLE:
dataSource.unregisterContentObserver(waypointsTableObserver);
break;
case SAMPLED_IN_TRACK_POINTS_TABLE:
dataSource.unregisterContentObserver(trackPointsTableObserver);
break;
case SAMPLED_OUT_TRACK_POINTS_TABLE:
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
break;
case PREFERENCE:
dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
break;
}
}
/**
* Unregisters all listeners with data source.
*/
void unregisterAllListeners() {
for (TrackDataType trackDataType : TrackDataType.values()) {
unregisterListener(trackDataType);
}
}
/**
* Observer when the tracks table is updated.
*
* @author Jimmy Shih
*/
private class TracksTableObserver extends ContentObserver {
TracksTableObserver() {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
dataSourceListener.notifyTracksTableUpdated();
}
}
/**
* Observer when the waypoints table is updated.
*
* @author Jimmy Shih
*/
private class WaypointsTableObserver extends ContentObserver {
WaypointsTableObserver() {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
dataSourceListener.notifyWaypointsTableUpdated();
}
}
/**
* Observer when the track points table is updated.
*
* @author Jimmy Shih
*/
private class TrackPointsTableObserver extends ContentObserver {
TrackPointsTableObserver() {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
dataSourceListener.notifyTrackPointsTableUpdated();
}
}
/**
* Listener for preference changes.
*
* @author Jimmy Shih
*/
private class PreferenceListener implements OnSharedPreferenceChangeListener {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
dataSourceListener.notifyPreferenceChanged(key);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010 Google Inc.
* 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
@@ -13,16 +13,37 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
import de.dennisguse.opentracks.stats.TripStatistics;
import android.content.Context;
import android.util.Pair;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* An interface for an object that can generate descriptions of track and waypoint.
* Generates descriptions for tracks and waypoints.
*
* @author Sandor Dornbush
* @author Jimmy Shih
*/
public interface DescriptionGenerator {
public class DescriptionGenerator {
private static final String HTML_LINE_BREAK = "<br>";
private static final String HTML_PARAGRAPH_SEPARATOR = "<p>";
private static final String TEXT_LINE_BREAK = "\n";
private static final String TEXT_PARAGRAPH_SEPARATOR = "\n\n";
private final Context context;
public DescriptionGenerator(Context context) {
this.context = context;
}
/**
* Generates a track description.
@@ -30,12 +51,175 @@ public interface DescriptionGenerator {
* @param track the track
* @param html true to output html, false to output plain text
*/
String generateTrackDescription(Track track, boolean html);
public String generateTrackDescription(Track track, boolean html) {
String paragraphSeparator = html ? HTML_PARAGRAPH_SEPARATOR : TEXT_PARAGRAPH_SEPARATOR;
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
StringBuilder builder = new StringBuilder();
// Created by
String creator = html
? "<a href='" + context.getString(R.string.app_web_url) + "'>" + context.getString(R.string.app_name) + "</a>"
: context.getString(R.string.app_name);
builder.append(creator);
builder.append(paragraphSeparator);
writeString(track.getName(), builder, R.string.generic_name_line, lineBreak);
writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak);
writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak);
builder.append(generateTrackStatisticsDescription(track.getTrackStatistics(), html));
return builder.toString();
}
/**
* Generate a waypoint description from a trip statistics.
* Writes a string to a string builder.
*
* @param tripStatistics the trip statistics
* @param text the string
* @param builder the string builder
* @param resId the resource id containing one string placeholder
* @param lineBreak the line break
*/
String generateWaypointDescription(TripStatistics tripStatistics);
private void writeString(String text, StringBuilder builder, int resId, String lineBreak) {
if (text == null || text.length() == 0) {
text = context.getString(R.string.value_unknown);
}
builder.append(context.getString(resId, text));
builder.append(lineBreak);
}
/**
* Generates a description for a {@link TrackStatistics}.
*
* @param stats the track statistics
* @param html true to use "<br>" for line break instead of "\n"
*/
private String generateTrackStatisticsDescription(TrackStatistics stats, boolean html) {
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
StringBuilder builder = new StringBuilder();
// Total distance
writeDistance(stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
// Total time
writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak);
// Moving time
writeTime(stats.getMovingTime(), builder, R.string.description_moving_time, lineBreak);
// Average speed
writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak);
// Average moving speed
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, lineBreak);
// Max speed
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
// Average pace
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
// Average moving pace
writePace(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_pace_in_minute, lineBreak);
// Fastest pace
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
// Max elevation
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
// Min elevation
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
// Elevation gain
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
// Recorded time
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime_ms())));
builder.append(lineBreak);
return builder.toString();
}
/**
* Writes distance.
*
* @param distance distance in meters
* @param builder StringBuilder to append distance
* @param resId resource id of distance string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) {
double distanceInKm = distance * UnitConversions.M_TO_KM;
double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, distanceInKm, distanceInMi));
builder.append(lineBreak);
}
/**
* Writes time.
*
* @param time time in milliseconds.
* @param builder StringBuilder to append time
* @param resId resource id of time string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeTime(long time, StringBuilder builder, int resId, String lineBreak) {
builder.append(context.getString(resId, StringUtils.formatElapsedTime(time)));
builder.append(lineBreak);
}
/**
* Writes speed.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append speed
* @param resId resource id of speed string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
builder.append(lineBreak);
}
/**
* Writes pace.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append pace
* @param resId resource id of pace string
* @param lineBreak line break string
*/
@VisibleForTesting
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
builder.append(context.getString(resId, formattedPaceMetrics, formattedPaceImperial));
builder.append(lineBreak);
}
/**
* Writes elevation.
*
* @param elevation elevation in meters
* @param builder StringBuilder to append elevation
* @param resId resource id of elevation string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeElevation(double elevation, StringBuilder builder, int resId, String lineBreak) {
long elevationInM = Math.round(elevation);
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
builder.append(context.getString(resId, elevationInM, elevationInFt));
builder.append(lineBreak);
}
}
@@ -1,243 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
import android.content.Context;
import android.util.Pair;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* @author Jimmy Shih
*/
public class DescriptionGeneratorImpl implements DescriptionGenerator {
private static final String HTML_LINE_BREAK = "<br>";
private static final String HTML_PARAGRAPH_SEPARATOR = "<p>";
private static final String TEXT_LINE_BREAK = "\n";
private static final String TEXT_PARAGRAPH_SEPARATOR = "\n\n";
private Context context;
public DescriptionGeneratorImpl(Context context) {
this.context = context;
}
@Override
public String generateTrackDescription(Track track, boolean html) {
String paragraphSeparator = html ? HTML_PARAGRAPH_SEPARATOR : TEXT_PARAGRAPH_SEPARATOR;
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
StringBuilder builder = new StringBuilder();
// Created by
String creator = html
? "<a href='" + context.getString(R.string.app_web_url) + "'>" + context.getString(R.string.app_name) + "</a>"
: context.getString(R.string.app_name);
builder.append(creator);
builder.append(paragraphSeparator);
writeString(track.getName(), builder, R.string.generic_name_line, lineBreak);
writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak);
writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak);
builder.append(generateTripStatisticsDescription(track.getTripStatistics(), html));
return builder.toString();
}
@Override
public String generateWaypointDescription(TripStatistics tripStatistics) {
return generateTripStatisticsDescription(tripStatistics, false);
}
/**
* Writes a string to a string builder.
*
* @param text the string
* @param builder the string builder
* @param resId the resource id containing one string placeholder
* @param lineBreak the line break
*/
private void writeString(String text, StringBuilder builder, int resId, String lineBreak) {
if (text == null || text.length() == 0) {
text = context.getString(R.string.value_unknown);
}
builder.append(context.getString(resId, text));
builder.append(lineBreak);
}
/**
* Generates a description for a {@link TripStatistics}.
*
* @param stats the trip statistics
* @param html true to use "<br>" for line break instead of "\n"
*/
private String generateTripStatisticsDescription(TripStatistics stats, boolean html) {
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
StringBuilder builder = new StringBuilder();
// Total distance
writeDistance(stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
// Total time
writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak);
// Moving time
writeTime(stats.getMovingTime(), builder, R.string.description_moving_time, lineBreak);
// Average speed
writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak);
// Average moving speed
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, lineBreak);
// Max speed
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
// Average pace
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
// Average moving pace
writePace(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_pace_in_minute, lineBreak);
// Fastest pace
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
// Max elevation
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
// Min elevation
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
// Elevation gain
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
// Max grade
writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak);
// Min grade
writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak);
// Recorded time
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
builder.append(lineBreak);
return builder.toString();
}
/**
* Writes distance.
*
* @param distance distance in meters
* @param builder StringBuilder to append distance
* @param resId resource id of distance string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) {
double distanceInKm = distance * UnitConversions.M_TO_KM;
double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, distanceInKm, distanceInMi));
builder.append(lineBreak);
}
/**
* Writes time.
*
* @param time time in milliseconds.
* @param builder StringBuilder to append time
* @param resId resource id of time string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeTime(long time, StringBuilder builder, int resId, String lineBreak) {
builder.append(context.getString(resId, StringUtils.formatElapsedTime(time)));
builder.append(lineBreak);
}
/**
* Writes speed.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append speed
* @param resId resource id of speed string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
builder.append(lineBreak);
}
/**
* Writes pace.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append pace
* @param resId resource id of pace string
* @param lineBreak line break string
*/
@VisibleForTesting
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
builder.append(context.getString(resId, formattedPaceMetrics, formattedPaceImperial));
builder.append(lineBreak);
}
/**
* Writes elevation.
*
* @param elevation elevation in meters
* @param builder StringBuilder to append elevation
* @param resId resource id of elevation string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeElevation(double elevation, StringBuilder builder, int resId, String lineBreak) {
long elevationInM = Math.round(elevation);
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
builder.append(context.getString(resId, elevationInM, elevationInFt));
builder.append(lineBreak);
}
/**
* Writes grade.
*
* @param grade grade in fraction
* @param builder StringBuilder to append grade
* @param resId resource id grade string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) {
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L : Math.round(grade * 100);
builder.append(context.getString(resId, gradeInPercent));
builder.append(lineBreak);
}
}
@@ -1,20 +0,0 @@
package de.dennisguse.opentracks.content;
import android.location.Location;
import android.location.LocationManager;
/**
* Creates a new {@link SensorDataSetLocation}.
* An implementation can create new instances or reuse existing instances for optimization.
*/
public class LocationFactory {
/**
* The default {@link LocationFactory} which creates a location each time.
*/
public static LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory();
public Location createLocation() {
return new SensorDataSetLocation(LocationManager.GPS_PROVIDER);
}
}
@@ -1,22 +0,0 @@
package de.dennisguse.opentracks.content;
import android.database.Cursor;
import android.location.Location;
import java.util.Iterator;
/**
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
*/
public interface LocationIterator extends Iterator<Location>, AutoCloseable {
/**
* Gets the most recently retrieved track point id by {@link #next()}.
*/
long getLocationId();
/**
* Closes the iterator.
*/
void close();
}
@@ -28,7 +28,12 @@ import java.util.Locale;
import java.util.SortedSet;
import java.util.TreeSet;
import de.dennisguse.opentracks.stats.TripStatistics;
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 de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -101,7 +106,7 @@ public class SearchEngine {
public static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L;
/**
* Comparador for scored results.
* Comparator for scored results.
*/
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = new Comparator<ScoredResult>() {
@Override
@@ -242,10 +247,9 @@ public class SearchEngine {
score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory());
TripStatistics statistics = track.getTripStatistics();
score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude());
TrackStatistics statistics = track.getTrackStatistics();
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
long meanTimestamp = (statistics.getStartTime_ms() + statistics.getStopTime_ms()) / 2L;
score *= getTimeBoost(query, meanTimestamp);
// Score the currently-selected track lower (user is already there, wouldn't be searching for it).
@@ -1,52 +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.location.Location;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
* This class extends the standard Android location with extra information.
*
* @author Sandor Dornbush
*/
public class SensorDataSetLocation extends Location {
private SensorDataSet sensorDataSet = null;
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
super(location);
this.sensorDataSet = sensorDataSet;
}
public SensorDataSetLocation(String provider) {
super(provider);
}
public SensorDataSet getSensorDataSet() {
return sensorDataSet;
}
public void setSensorDataSet(SensorDataSet sensorDataSet) {
this.sensorDataSet = sensorDataSet;
}
public void reset() {
super.reset();
sensorDataSet = null;
}
}
@@ -16,9 +16,11 @@
package de.dennisguse.opentracks.content;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.location.Location;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
@@ -26,31 +28,44 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Track data hub. Receives data from {@link DataSource} and distributes it to {@link TrackDataListener} after some processing.
* Track data hub.
* Receives data from {@link de.dennisguse.opentracks.content.provider.CustomContentProvider} and distributes it to {@link TrackDataListener} after some processing.
*
* {@link TrackPoint}s are filtered/downsampled with a dynamic sampling frequency.
*
* @author Rodrigo Damazio
*/
public class TrackDataHub implements DataSourceListener {
public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeListener {
/**
* Target number of track points displayed by the map overlay.
* We may display more than this number of points.
*/
@Deprecated
private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
/**
* Maximum number of waypoints to displayed.
*/
@VisibleForTesting
@Deprecated
private static final int MAX_DISPLAYED_WAYPOINTS = 128;
private static final String TAG = TrackDataHub.class.getSimpleName();
private final Context context;
@@ -61,31 +76,26 @@ public class TrackDataHub implements DataSourceListener {
private boolean started;
private HandlerThread handlerThread;
private Handler handler;
private DataSource dataSource;
private DataSourceManager dataSourceManager;
// Preference values
private long selectedTrackId;
private long recordingTrackId;
private boolean recordingTrackPaused;
private boolean metricUnits;
private boolean reportSpeed;
private int recordingGpsAccuracy;
private int recordingDistanceInterval;
// Track points sampling state
private int numLoadedPoints;
private long firstSeenLocationId;
private long lastSeenLocationId;
private long firstSeenTrackPointId;
private long lastSeenTrackPointId;
// Registered listeners
private ContentObserver tracksTableObserver;
private ContentObserver waypointsTableObserver;
private ContentObserver trackPointsTableObserver;
public TrackDataHub(Context context) {
this(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
}
/**
* Constructor.
*
* @param context the context
* @param trackDataManager the track data manager
* @param contentProviderUtils the content tracks provider utils
* @param targetNumPoints the target number of points
*/
@VisibleForTesting
private TrackDataHub(Context context, TrackDataManager trackDataManager, ContentProviderUtils contentProviderUtils, int targetNumPoints) {
this.context = context;
@@ -95,28 +105,49 @@ public class TrackDataHub implements DataSourceListener {
resetSamplingState();
}
public synchronized static TrackDataHub newInstance(Context context) {
return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
}
public void start() {
if (started) {
Log.i(TAG, "TrackDataHub already started, ignoring start.");
return;
}
started = true;
handlerThread = new HandlerThread("TrackDataHubHandlerThread");
handlerThread = new HandlerThread(TAG);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
dataSource = newDataSource();
dataSourceManager = new DataSourceManager(dataSource, this);
notifyPreferenceChanged(null);
//register listeners
ContentResolver contentResolver = context.getContentResolver();
tracksTableObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
notifyTracksTableUpdate(trackDataManager.getListenerTracks());
}
};
contentResolver.registerContentObserver(TracksColumns.CONTENT_URI, false, tracksTableObserver);
waypointsTableObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
notifyWaypointsTableUpdate(trackDataManager.getListenerWaypoints());
}
};
contentResolver.registerContentObserver(WaypointsColumns.CONTENT_URI, false, waypointsTableObserver);
trackPointsTableObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
notifyTrackPointsTableUpdate(true, trackDataManager.getListenerTrackPoints_SampledIn(), trackDataManager.getListenerTrackPoints_SampledOut());
}
};
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
PreferencesUtils.register(context, this);
onSharedPreferenceChanged(null, null);
runInHandlerThread(new Runnable() {
@Override
public void run() {
if (dataSourceManager != null) {
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
if (started) {
loadDataForAll();
}
}
@@ -128,16 +159,22 @@ public class TrackDataHub implements DataSourceListener {
Log.i(TAG, "TrackDataHub not started, ignoring stop.");
return;
}
PreferencesUtils.unregister(context, this);
started = false;
dataSourceManager.unregisterAllListeners();
//Unregister listeners
ContentResolver contentResolver = context.getContentResolver();
contentResolver.unregisterContentObserver(tracksTableObserver);
contentResolver.unregisterContentObserver(waypointsTableObserver);
contentResolver.unregisterContentObserver(trackPointsTableObserver);
if (handlerThread != null) {
handlerThread.getLooper().quit();
handlerThread = null;
}
handler = null;
dataSource = null;
dataSourceManager = null;
}
public void loadTrack(final long trackId) {
@@ -158,15 +195,13 @@ public class TrackDataHub implements DataSourceListener {
* Registers a {@link TrackDataListener}.
*
* @param trackDataListener the track data listener
* @param trackDataTypes the track data types
*/
public void registerTrackDataListener(final TrackDataListener trackDataListener, final EnumSet<TrackDataType> trackDataTypes) {
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean waypointsTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
runInHandlerThread(new Runnable() {
@Override
public void run() {
trackDataManager.registerListener(trackDataListener, trackDataTypes);
if (dataSourceManager != null) {
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
if (started) {
loadDataForListener(trackDataListener);
}
}
@@ -182,22 +217,7 @@ public class TrackDataHub implements DataSourceListener {
runInHandlerThread(new Runnable() {
@Override
public void run() {
trackDataManager.unregisterListener(trackDataListener);
if (dataSourceManager != null) {
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
}
}
});
}
/**
* Reloads data for a {@link TrackDataListener}.
*/
public void reloadDataForListener(final TrackDataListener trackDataListener) {
runInHandlerThread(new Runnable() {
@Override
public void run() {
loadDataForListener(trackDataListener);
trackDataManager.unregisterTrackDataListener(trackDataListener);
}
});
}
@@ -217,38 +237,7 @@ public class TrackDataHub implements DataSourceListener {
}
@Override
public void notifyTracksTableUpdated() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE));
}
});
}
@Override
public void notifyWaypointsTableUpdated() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE));
}
});
}
@Override
public void notifyTrackPointsTableUpdated() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
notifyTrackPointsTableUpdate(true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
}
});
}
@Override
public void notifyPreferenceChanged(final String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
runInHandlerThread(new Runnable() {
@Override
public void run() {
@@ -258,46 +247,6 @@ public class TrackDataHub implements DataSourceListener {
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
}
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onMetricUnitsChanged(metricUnits)) {
loadDataForListener(trackDataListener);
}
}
}
}
if (PreferencesUtils.isKey(context, R.string.stats_rate_key, key)) {
reportSpeed = PreferencesUtils.isReportSpeed(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onReportSpeedChanged(reportSpeed)) {
loadDataForListener(trackDataListener);
}
}
}
}
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
loadDataForListener(trackDataListener);
}
}
}
}
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
if (key != null) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
loadDataForListener(trackDataListener);
}
}
}
}
}
});
}
@@ -307,26 +256,17 @@ public class TrackDataHub implements DataSourceListener {
*/
private void loadDataForAll() {
resetSamplingState();
if (trackDataManager.getNumberOfListeners() == 0) {
if (!trackDataManager.hasListeners()) {
return;
}
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
trackDataListener.onMetricUnitsChanged(metricUnits);
trackDataListener.onReportSpeedChanged(reportSpeed);
trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy);
trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval);
}
notifyTracksTableUpdate(trackDataManager.getListenerTracks());
notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE));
for (TrackDataListener listener : trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)) {
for (TrackDataListener listener : trackDataManager.getListenerTrackPoints_SampledIn()) {
listener.clearTrackPoints();
}
notifyTrackPointsTableUpdate(true,
trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE));
notifyTrackPointsTableUpdate(true, trackDataManager.getListenerTrackPoints_SampledIn(), trackDataManager.getListenerTrackPoints_SampledOut());
notifyWaypointsTableUpdate(trackDataManager.getListenerWaypoints());
}
/**
@@ -336,21 +276,13 @@ public class TrackDataHub implements DataSourceListener {
*/
private void loadDataForListener(TrackDataListener trackDataListener) {
Set<TrackDataListener> trackDataListeners = Collections.singleton(trackDataListener);
EnumSet<TrackDataType> trackDataTypes = trackDataManager.getTrackDataTypes(trackDataListener);
if (trackDataTypes.contains(TrackDataType.PREFERENCE)) {
trackDataListener.onMetricUnitsChanged(metricUnits);
trackDataListener.onReportSpeedChanged(reportSpeed);
trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy);
trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval);
}
if (trackDataTypes.contains(TrackDataType.TRACKS_TABLE)) {
if (trackDataManager.listensForTracks(trackDataListener)) {
notifyTracksTableUpdate(trackDataListeners);
}
boolean hasSampledIn = trackDataTypes.contains(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE);
boolean hasSampledOut = trackDataTypes.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE);
boolean hasSampledIn = trackDataManager.listensForTrackPoints_SampledIn(trackDataListener);
boolean hasSampledOut = trackDataManager.listensForTrackPoints_SampledOut(trackDataListener);
if (hasSampledIn || hasSampledOut) {
trackDataListener.clearTrackPoints();
boolean isOnlyListener = trackDataManager.getNumberOfListeners() == 1;
@@ -361,7 +293,7 @@ public class TrackDataHub implements DataSourceListener {
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
}
if (trackDataTypes.contains(TrackDataType.WAYPOINTS_TABLE)) {
if (trackDataManager.listensForWaypoints(trackDataListener)) {
notifyWaypointsTableUpdate(trackDataListeners);
}
}
@@ -396,8 +328,7 @@ public class TrackDataHub implements DataSourceListener {
trackDataListener.clearWaypoints();
}
try (Cursor cursor = contentProviderUtils.getWaypointCursor(
selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
if (cursor != null && cursor.moveToFirst()) {
do {
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
@@ -437,60 +368,60 @@ public class TrackDataHub implements DataSourceListener {
}
}
int localNumLoadedPoints = updateSamplingState ? numLoadedPoints : 0;
long localFirstSeenLocationId = updateSamplingState ? firstSeenLocationId : -1L;
long localLastSeenLocationId = updateSamplingState ? lastSeenLocationId : -1L;
long maxPointId = updateSamplingState ? -1L : lastSeenLocationId;
int localNumLoadedTrackPoints = updateSamplingState ? numLoadedPoints : 0;
long localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : -1L;
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
boolean includeNextPoint = false;
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenTrackPointIdId + 1, false)) {
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
long locationId = locationIterator.getLocationId();
while (trackPointIterator.hasNext()) {
TrackPoint trackPoint = trackPointIterator.next();
long trackPointId = trackPointIterator.getTrackPointId();
// Stop if past the last wanted point
if (maxPointId != -1L && locationId > maxPointId) {
if (maxPointId != -1L && trackPointId > maxPointId) {
break;
}
if (localFirstSeenLocationId == -1) {
localFirstSeenLocationId = locationId;
if (localFirstSeenTrackPointId == -1) {
localFirstSeenTrackPointId = trackPointId;
}
if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId);
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenTrackPointId);
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
if (!LocationUtils.isValidLocation(location)) { //This can be split markers (not anymore supported feature)
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) { //This can be split markers (not anymore supported feature)
includeNextPoint = true;
} else {
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
if (includeNextPoint || (localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(location);
trackDataListener.onSampledInTrackPoint(trackPoint);
}
} else {
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(location);
trackDataListener.onSampledOutTrackPoint(trackPoint);
}
}
}
localNumLoadedPoints++;
localLastSeenLocationId = locationId;
localNumLoadedTrackPoints++;
localLastSeenTrackPointIdId = trackPointId;
}
}
if (updateSamplingState) {
numLoadedPoints = localNumLoadedPoints;
firstSeenLocationId = localFirstSeenLocationId;
lastSeenLocationId = localLastSeenLocationId;
numLoadedPoints = localNumLoadedTrackPoints;
firstSeenTrackPointId = localFirstSeenTrackPointId;
lastSeenTrackPointId = localLastSeenTrackPointIdId;
}
for (TrackDataListener listener : sampledInListeners) {
@@ -503,16 +434,8 @@ public class TrackDataHub implements DataSourceListener {
*/
private void resetSamplingState() {
numLoadedPoints = 0;
firstSeenLocationId = -1L;
lastSeenLocationId = -1L;
}
/**
* Creates a {@link DataSource}.
*/
@VisibleForTesting
private DataSource newDataSource() {
return new DataSource(context);
firstSeenTrackPointId = -1L;
lastSeenTrackPointId = -1L;
}
/**
@@ -520,6 +443,7 @@ public class TrackDataHub implements DataSourceListener {
*
* @param runnable the runnable
*/
@Deprecated //TODO: Why actually catch this problem: I guess it would be better to fail hard.
@VisibleForTesting
private void runInHandlerThread(Runnable runnable) {
if (handler == null) {
@@ -529,41 +453,4 @@ public class TrackDataHub implements DataSourceListener {
}
handler.post(runnable);
}
/**
* Gets the value selectedTrackId.
*
* @return the selectedTrackId
*/
public long getSelectedTrackId() {
return selectedTrackId;
}
/**
* Gets the recordingGpsAccuracy.
*/
@VisibleForTesting
int getRecordingGpsAccuracy() {
return recordingGpsAccuracy;
}
/**
* Gets the metricUnits.
*
* @return the metricUnits
*/
@VisibleForTesting
boolean isMetricUnits() {
return metricUnits;
}
/**
* Gets the reportSpeed.
*
* @return the reportSpeed
*/
@VisibleForTesting
boolean isReportSpeed() {
return reportSpeed;
}
}
@@ -16,7 +16,9 @@
package de.dennisguse.opentracks.content;
import android.location.Location;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
/**
* Listener for track data changes.
@@ -40,20 +42,20 @@ public interface TrackDataListener {
/**
* Called when a sampled in track point is read.
*
* @param location the location
* @param trackPoint the trackPoint
*/
void onSampledInTrackPoint(Location location);
void onSampledInTrackPoint(TrackPoint trackPoint);
/**
* Called when a sampled out track point is read.
*
* @param location the location
* @param trackPoint the trackPoint
*/
void onSampledOutTrackPoint(Location location);
void onSampledOutTrackPoint(TrackPoint trackPoint);
/**
* Called when finish sending new track points.
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)} and {@link #onSampledOutTrackPoint(Location)}.
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint)} and {@link #onSampledOutTrackPoint(TrackPoint)}.
*/
void onNewTrackPointsDone();
@@ -74,36 +76,4 @@ public interface TrackDataListener {
* This gets called after every batch of calls to {@link #clearWaypoints()} and {@link #onNewWaypoint(Waypoint)}.
*/
void onNewWaypointsDone();
/**
* Called when the metric units preference value is change.
*
* @param metricUnits true to use metric units, false to use imperial units
* @return true to reload all the data, false otherwise.
*/
boolean onMetricUnitsChanged(boolean metricUnits);
/**
* Called when the report speed preference value is changed.
*
* @param reportSpeed true to report speed, false to report pace
* @return true to reload all the data, false otherwise.
*/
boolean onReportSpeedChanged(boolean reportSpeed);
/**
* Called when the recording gps accuracy preference value is changed.
*
* @param recordingGpsAccuracy the new value
* @return true to reload all the data, false otherwise
*/
boolean onRecordingGpsAccuracy(int recordingGpsAccuracy);
/**
* Called when the recording distance interval preference value is changed.
*
* @param recordingDistanceInterval the new value
* @return true to reload all the data, false otherwise.
*/
boolean onRecordingDistanceIntervalChanged(int recordingDistanceInterval);
}
@@ -16,13 +16,7 @@
package de.dennisguse.opentracks.content;
import android.util.Log;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
/**
@@ -34,84 +28,68 @@ class TrackDataManager {
private static final String TAG = TrackDataManager.class.getSimpleName();
private final Map<TrackDataListener, EnumSet<TrackDataType>> listenerToTypesMap = new HashMap<>();
private final Set<TrackDataListener> dataListenerTracks = new HashSet<>();
private final Set<TrackDataListener> dataListenerWaypoints = new HashSet<>();
private final Set<TrackDataListener> dataListenerTrackPoints_SampledIn = new HashSet<>();
private final Set<TrackDataListener> dataListenerTrackPoints_SampledOut = new HashSet<>();
private final Map<TrackDataType, Set<TrackDataListener>> typeToListenersMap = new EnumMap<>(TrackDataType.class);
TrackDataManager() {
for (TrackDataType trackDataType : TrackDataType.values()) {
typeToListenersMap.put(trackDataType, new LinkedHashSet<TrackDataListener>());
}
void registerTrackDataListener(final TrackDataListener trackDataListener, boolean tracksTable, boolean waypointsTable, boolean trackPointsTable_SampleIn, boolean trackPointsTable_SampleOut) {
if (tracksTable) dataListenerTracks.add(trackDataListener);
if (waypointsTable) dataListenerWaypoints.add(trackDataListener);
if (trackPointsTable_SampleIn) dataListenerTrackPoints_SampledIn.add(trackDataListener);
if (trackPointsTable_SampleOut) dataListenerTrackPoints_SampledOut.add(trackDataListener);
}
/**
* Registers a listener.
*
* @param listener the listener
* @param trackDataTypes the track data types the listener is interested
*/
void registerListener(TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
if (listenerToTypesMap.containsKey(listener)) {
Log.w(TAG, "Tried to register a listener that is already registered. Ignore.");
return;
}
listenerToTypesMap.put(listener, trackDataTypes);
for (TrackDataType trackDataType : trackDataTypes) {
typeToListenersMap.get(trackDataType).add(listener);
}
void unregisterTrackDataListener(TrackDataListener trackDataListener) {
dataListenerTracks.add(trackDataListener);
dataListenerWaypoints.add(trackDataListener);
dataListenerTrackPoints_SampledIn.add(trackDataListener);
dataListenerTrackPoints_SampledOut.add(trackDataListener);
}
/**
* Unregisters a listener.
*
* @param listener the listener
*/
void unregisterListener(TrackDataListener listener) {
EnumSet<TrackDataType> removedTypes = listenerToTypesMap.remove(listener);
if (removedTypes == null) {
Log.w(TAG, "Tried to unregister a listener that is not registered. Ignore.");
return;
}
// Remove the listener from the typeToListenersMap
for (TrackDataType trackDataType : removedTypes) {
typeToListenersMap.get(trackDataType).remove(listener);
}
boolean hasListeners() {
return dataListenerTracks.size() + dataListenerWaypoints.size() + dataListenerTrackPoints_SampledIn.size() + dataListenerTrackPoints_SampledOut.size() > 0;
}
/**
* Gets the number of {@link TrackDataListener}.
*/
int getNumberOfListeners() {
return listenerToTypesMap.size();
Set<TrackDataListener> listener = new HashSet<>();
listener.addAll(dataListenerTracks);
listener.addAll(dataListenerWaypoints);
listener.addAll(dataListenerTrackPoints_SampledIn);
listener.addAll(dataListenerTrackPoints_SampledOut);
return listener.size();
}
/**
* Gets the track data types for a listener.
*
* @param listener the listener
*/
EnumSet<TrackDataType> getTrackDataTypes(TrackDataListener listener) {
return listenerToTypesMap.get(listener);
boolean listensForTracks(TrackDataListener listener) {
return dataListenerTracks.contains(listener);
}
/**
* Gets the listeners for a {@link TrackDataType}.
*
* @param type the type
*/
Set<TrackDataListener> getListeners(TrackDataType type) {
return typeToListenersMap.get(type);
boolean listensForWaypoints(TrackDataListener listener) {
return dataListenerWaypoints.contains(listener);
}
/**
* Gets all the registered {@link TrackDataType}.
*/
EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
EnumSet<TrackDataType> types = EnumSet.noneOf(TrackDataType.class);
for (EnumSet<TrackDataType> value : listenerToTypesMap.values()) {
types.addAll(value);
}
return types;
boolean listensForTrackPoints_SampledIn(TrackDataListener listener) {
return dataListenerTrackPoints_SampledIn.contains(listener);
}
boolean listensForTrackPoints_SampledOut(TrackDataListener listener) {
return dataListenerTrackPoints_SampledOut.contains(listener);
}
Set<TrackDataListener> getListenerTracks() {
return dataListenerTracks;
}
Set<TrackDataListener> getListenerWaypoints() {
return dataListenerWaypoints;
}
Set<TrackDataListener> getListenerTrackPoints_SampledIn() {
return dataListenerTrackPoints_SampledIn;
}
Set<TrackDataListener> getListenerTrackPoints_SampledOut() {
return dataListenerTrackPoints_SampledOut;
}
}
@@ -1,30 +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;
/**
* Types of track data.
*
* @author Jimmy Shih
*/
public enum TrackDataType {
TRACKS_TABLE, // tracks table changes
WAYPOINTS_TABLE, // waypoints table changes
SAMPLED_IN_TRACK_POINTS_TABLE, // sampled-in track points table changes
SAMPLED_OUT_TRACK_POINTS_TABLE, // sampled-out track points table changes
PREFERENCE // preference changes
}
@@ -1,85 +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 de.dennisguse.opentracks.content.Waypoint.WaypointType;
/**
* A request for the service to create a waypoint at the current location.
*
* @author Sandor Dornbush
*/
public class WaypointCreationRequest {
public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest(WaypointType.WAYPOINT, false);
public final static WaypointCreationRequest DEFAULT_STATISTICS = new WaypointCreationRequest(WaypointType.STATISTICS, false);
public final static WaypointCreationRequest DEFAULT_START_TRACK = new WaypointCreationRequest(WaypointType.STATISTICS, true);
private WaypointType type;
// true if this marker contains the track statistics
private boolean isTrackStatistics;
private String name;
private String category;
private String description;
private String iconUrl;
private String photoUrl;
private WaypointCreationRequest(WaypointType type, boolean isTrackStatistics) {
this(type, isTrackStatistics, null, null, null, null, null);
}
public WaypointCreationRequest(WaypointType type, boolean isTrackStatistics, String name, String category, String description, String iconUrl, String photoUrl) {
this.type = type;
this.isTrackStatistics = isTrackStatistics;
this.name = name;
this.category = category;
this.description = description;
this.iconUrl = iconUrl;
this.photoUrl = photoUrl;
}
public WaypointType getType() {
return type;
}
public boolean isTrackStatistics() {
return isTrackStatistics;
}
public String getName() {
return name;
}
public String getCategory() {
return category;
}
public String getDescription() {
return description;
}
public String getIconUrl() {
return iconUrl;
}
public String getPhotoUrl() {
return photoUrl;
}
}
@@ -14,14 +14,14 @@
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.data;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* A track.
@@ -35,19 +35,13 @@ public class Track {
private String name = "";
private String description = "";
private String category = "";
private long startId = -1L;
private long stopId = -1L;
/*
* The number of location points (present even if the points themselves are not loaded)
*/
private int numberOfPoints = 0;
private String icon = "";
private TripStatistics tripStatistics = new TripStatistics();
private TrackStatistics trackStatistics = new TrackStatistics();
// Location points (which may not have been loaded)
private List<Location> locations = new ArrayList<>();
private List<TrackPoint> trackPoints = new ArrayList<>();
public Track() {
}
@@ -84,30 +78,6 @@ public class Track {
this.category = category;
}
public long getStartId() {
return startId;
}
public void setStartId(long startId) {
this.startId = startId;
}
public long getStopId() {
return stopId;
}
public void setStopId(long stopId) {
this.stopId = stopId;
}
public int getNumberOfPoints() {
return numberOfPoints;
}
public void setNumberOfPoints(int numberOfPoints) {
this.numberOfPoints = numberOfPoints;
}
public String getIcon() {
return icon;
}
@@ -116,23 +86,24 @@ public class Track {
this.icon = icon;
}
public TripStatistics getTripStatistics() {
return tripStatistics;
public TrackStatistics getTrackStatistics() {
return trackStatistics;
}
public void setTripStatistics(TripStatistics tripStatistics) {
this.tripStatistics = tripStatistics;
public void setTrackStatistics(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
}
public void addLocation(Location location) {
locations.add(location);
@VisibleForTesting
public void addTrackPoint(TrackPoint location) {
trackPoints.add(location);
}
public List<Location> getLocations() {
return locations;
public List<TrackPoint> getTrackPoints() {
return trackPoints;
}
public void setLocations(ArrayList<Location> locations) {
this.locations = locations;
public void setTrackPoints(ArrayList<TrackPoint> trackPoints) {
this.trackPoints = trackPoints;
}
}

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