Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9074fa1b6c | |||
| 5721a615a2 | |||
| fadcf1cd41 | |||
| a50b1ac78e | |||
| 9a7d4fdb36 | |||
| 9fb51a689d | |||
| 65bda35001 | |||
| 443ded86a5 | |||
| 83b5ebb02c | |||
| 240d8f6b6c | |||
| 39c5422341 | |||
| 4893a2b743 | |||
| 3408ea97f4 | |||
| ddb17f61cb | |||
| bbe182cf82 | |||
| b7b7bdf19b | |||
| 5681938a9e | |||
| 74835dcef5 | |||
| ed0dc05366 | |||
| 653eb8320a | |||
| 54b9c4370b | |||
| fa9cb3903c | |||
| 0f1d8500d1 | |||
| 20d7fd2c03 | |||
| 1d838344dc | |||
| fd826199e2 | |||
| f485d90ef2 | |||
| ec39e67a09 | |||
| 1a86db0a8b | |||
| 6a1fada22f | |||
| 2898e06246 | |||
| 0d87c65b49 | |||
| 9b97cdfcd1 | |||
| 95feae846a | |||
| b09eeaede3 | |||
| 35dec1634c | |||
| e49a425106 | |||
| adbad04867 | |||
| 850bd2f887 | |||
| d240b457e2 | |||
| 6d800caab1 | |||
| 7bf153db39 | |||
| 1b69f29751 | |||
| dfc3837739 | |||
| 30e847866e | |||
| fbc902e830 | |||
| 208f1d71ba | |||
| 38a15a96b3 | |||
| 46f8753746 | |||
| 70bc8f33b1 | |||
| 6cd27ab096 | |||
| 5581b404f7 | |||
| 2ef74eb908 | |||
| 16c654141f | |||
| dfc785f2fd | |||
| f621cdf032 | |||
| 8b04d9c4a6 | |||
| 364b4702e3 | |||
| a98c16de28 | |||
| 881bf1982a | |||
| f340622977 | |||
| 194379295d | |||
| 901cffee97 | |||
| d93c2ec5f3 | |||
| 306522863a | |||
| e54d85e04d | |||
| cb30ad27c3 | |||
| 8685c9cc88 | |||
| a6f03d0f23 | |||
| e3c812b155 | |||
| f149de9ce3 | |||
| 3447dd2569 | |||
| b2b8c7fd54 | |||
| 486bcd0aec | |||
| c8a33201f5 | |||
| 4a37c2cc7e | |||
| d23db5bafd | |||
| ac528f9a10 | |||
| 256eadd49d | |||
| 64e58934a5 | |||
| 92dfadfdba | |||
| cbce70143b | |||
| f4b9e3159e | |||
| 928781391d | |||
| 05cd672464 | |||
| d3109bbc37 | |||
| eabdb7c721 | |||
| ad990e8cd7 | |||
| db3eef0104 | |||
| c4f1ba7962 | |||
| 7451fbd413 | |||
| b6c55de55a | |||
| dd3ee6d99e | |||
| bbb6a7c22c | |||
| dc1b50a7bf | |||
| 21bfb88e0b | |||
| aaab8adce9 | |||
| df502f835c | |||
| dd6a4cd153 | |||
| 76656997ef | |||
| 32070a0bd6 | |||
| b9420d4dde | |||
| 1b810e1294 | |||
| f16d59bff6 | |||
| b5bf3aadc5 | |||
| f6a27a1522 | |||
| 83758fcd72 | |||
| 070917bc0b | |||
| 543a04dfdd | |||
| b4d5eb0eec | |||
| 9f84cc4952 | |||
| 09fe35f1ca | |||
| 27e50b2dd9 | |||
| 971460848b | |||
| 8bc2937bb4 | |||
| 0e9b68c2a1 | |||
| ba298c2afb | |||
| 79f5ed4dc3 | |||
| bbb2f874ee | |||
| 9af3759b07 | |||
| 1f6fe6bf0c | |||
| 7b291e85d7 | |||
| 0f72066bc0 | |||
| 70e0e7b216 | |||
| 7e50bb1b6d | |||
| a3eb107068 | |||
| 6133ef121a | |||
| c6d24e9534 | |||
| 441d3f721f | |||
| 3c212e4bcc | |||
| 50eb71bc1f | |||
| 0806821b5f | |||
| 36d0f17953 | |||
| 700adb7972 | |||
| a156369f8b | |||
| c0f6dc511b | |||
| 63e1461eba | |||
| 804e5f10c8 | |||
| 6cc7271a41 | |||
| f66194189b | |||
| 5d97d6d492 | |||
| f3a308e669 | |||
| a230504925 | |||
| 90e19e282c | |||
| b585bfe44c | |||
| 475e04eeb4 | |||
| f89b2b0efb | |||
| b94f3c176b | |||
| d1d729d287 | |||
| e23bbcc405 | |||
| 4129985b0f | |||
| 5cb7b623d9 | |||
| 3c52efadd6 | |||
| 73b4977051 | |||
| 422c2c02ce | |||
| d692329975 | |||
| 931574bc11 | |||
| caddbd8501 | |||
| 8ece2c8612 | |||
| 3a219321d1 | |||
| 35cb924a17 | |||
| b67e5bbd20 | |||
| 1db275230e | |||
| 5ac72092fb | |||
| bdd9ded1e8 | |||
| 759aeb6ed6 | |||
| 12023d51ae | |||
| c426785b90 | |||
| 09d960c7a1 | |||
| f37fe4a503 | |||
| 013e56bef0 | |||
| d6835ba766 | |||
| d7890cbc81 | |||
| 2b6886c534 | |||
| e77590a125 | |||
| ca330b2d80 | |||
| 20f1524701 | |||
| 96b2a15643 | |||
| ad59392e02 | |||
| 29f92b887c | |||
| 46328211c2 | |||
| 3760130d1f | |||
| 1094827fac | |||
| 36fcb44af5 | |||
| a683fbac4b | |||
| 64ad9019d1 | |||
| 1e80d5c8a8 | |||
| ac765b1ee8 | |||
| 850c4f0a9a | |||
| 4c32999752 | |||
| 76823b167a | |||
| 9994cfd260 | |||
| 1750236474 | |||
| fed6e31f9e | |||
| 9bafc7d4a1 | |||
| be58b48d20 | |||
| 303abac980 | |||
| d1a459efc2 | |||
| 96069e6f09 | |||
| 301a721329 | |||
| e8c6801a32 | |||
| 355f3ef614 | |||
| eaa6083586 | |||
| a0c5d02c51 | |||
| 76f9f5fc82 | |||
| 8e2598e93b | |||
| c0672c41df | |||
| 4c6601fbc5 | |||
| b99c87d1bd | |||
| 66ffbfe951 | |||
| 8cb01a1027 | |||
| 193e5eccbb | |||
| bf7fc28bb0 | |||
| e35cbc343a | |||
| 1c7b053c8c | |||
| 88cdcd4b47 | |||
| 1432fed8f9 | |||
| a14307b9a4 | |||
| 3bdce5aa08 | |||
| d7526f93b8 |
@@ -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
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report issues of Opentracks features
|
||||
about: Report issues of OpenTracks features
|
||||
title: ''
|
||||
labels: potential bug
|
||||
assignees: ''
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Thanks for your contribution.
|
||||
|
||||
**Describe the pull request**
|
||||
A clear and concise description of what the pull request changes/adds.
|
||||
|
||||
**Link to the the issue**
|
||||
(If available): The link to the issue that this pull request solves.
|
||||
|
||||
**License agreement**
|
||||
By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)).
|
||||
@@ -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: [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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/).
|
||||
@@ -2,17 +2,39 @@
|
||||
|
||||
_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
|
||||
* __Cycling (speed and cadence)__: 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 +48,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>
|
||||
|
||||
@@ -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).
|
||||
@@ -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 `build.gradle`
|
||||
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/).
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,19 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
//Useful for debugging.
|
||||
//versionCode getVersionCode()
|
||||
manifestPlaceholders = [
|
||||
applicationLogo: "@drawable/ic_logo_color_24dp",
|
||||
applicationName: "@string/app_name"
|
||||
]
|
||||
|
||||
versionCode 3369
|
||||
//versionCode getVersionCode() //Useful for debugging.
|
||||
versionName "v3.7.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
|
||||
@@ -69,11 +77,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 +101,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 +113,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'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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,10 @@
|
||||
v3.2.8: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Chart: zoom via gestures
|
||||
|
||||
Bugfixes:
|
||||
- Export via KMZ did not export picture filenames correctly
|
||||
- Sharing multiple tracks did not include KMZ files
|
||||
- MarkerDetailActivity's UI was not shown
|
||||
- Settings crashed if preference of heart rate was not initialized
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.2.9: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Option to keep screen on while recording
|
||||
- Markers: removed split markers completely
|
||||
- Voice announcement now work without READ_PHONE_STATE permission
|
||||
|
||||
Bugfixes:
|
||||
- Statistics: heart rate was not shown
|
||||
- Statistics: time was briefly reset to 0s
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.2.10: OpenTracks
|
||||
|
||||
Changes:
|
||||
- ChartView is not configurable anymore
|
||||
- After reboot: removed automatic restart of an unfinished recording
|
||||
|
||||
Bugfix:
|
||||
- Speech announcements use MAY_DUCK (again)
|
||||
- ChartView shows markers (again)
|
||||
- Fixed landscape layout of TrackListActivity
|
||||
- Request legacy file API behavior (necessary for Android 10)
|
||||
@@ -0,0 +1,12 @@
|
||||
v3.2.11: OpenTracks
|
||||
|
||||
Bug:
|
||||
- Max speed is identical to avg speed
|
||||
|
||||
Bugfix:
|
||||
- Crash on heart rate sensor selection
|
||||
|
||||
Developer:
|
||||
- Replaced TabHost by TabsLayout
|
||||
- Removed special first waypoint
|
||||
- Removed duplicate translations
|
||||
@@ -0,0 +1,13 @@
|
||||
v3.3.0: OpenTracks
|
||||
|
||||
Breaking changes:
|
||||
- Pictures are now stored in app-private storage (do not show up other picture apps)
|
||||
NOTE: Pictures taking with previous version cannot be shown
|
||||
- Localization for Spanish (Latin America) only works on Android 7.0 or later
|
||||
|
||||
Changes:
|
||||
- Import/Export via Storage Access Framework (avoids READ/WRITE_EXTERNAL_STORAGE permission)
|
||||
|
||||
Developer:
|
||||
- Cleaned internal data structures
|
||||
- Use separate icons/app names for debug version
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.6.1: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- KMZ import: store correct picture path
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.6.2: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Charts were not shown correctly (introduced in v3.6.0)
|
||||
@@ -0,0 +1,13 @@
|
||||
v3.7.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Report speed/pace by activity type
|
||||
- Added workaround for GPS week rollover
|
||||
- (Experimental) Support for Bluetooth LE cycling sensors (cadence and speed)
|
||||
- KMZ/KML import enhanced
|
||||
|
||||
Bugfix:
|
||||
- KMZ import: only import used images
|
||||
|
||||
Developer:
|
||||
- Refactored TrackRecordingService
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 168 KiB |
@@ -0,0 +1,9 @@
|
||||
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
|
||||
|
||||
Fonctionnalités :
|
||||
|
||||
* Suivi d'activité : enregistre vos activités sportives et de plein air
|
||||
* Photos et repères : ajout de photos et de repères pendant l'enregistrement de l'activité
|
||||
* Rythme cardiaque : support des capteurs Bluetooth LE
|
||||
* Export : export des traces au format KML ou GPX
|
||||
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
|
||||
@@ -1,5 +1,6 @@
|
||||
#Sun Apr 12 18:41:12 CEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
|
||||
|
After Width: | Height: | Size: 125 KiB |
@@ -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,9 +18,7 @@ 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.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
* Commons utilities for creating stubs of track, location.
|
||||
@@ -37,50 +35,31 @@ public class TrackStubUtils {
|
||||
private static final float INITIAL_ACCURACY = 5;
|
||||
private static final float INITIAL_SPEED = 10;
|
||||
private static final float INITIAL_BEARING = 3.0f;
|
||||
// Used to change the value of latitude, longitude, and altitude.
|
||||
private static final double DIFFERENCE = 0.01;
|
||||
|
||||
/**
|
||||
* Gets a a {@link Track} stub with specified number of locations.
|
||||
*
|
||||
* @param numberOfLocations the number of locations for the track
|
||||
* @return a track stub.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MyTracks location with default values.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks;
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -23,6 +24,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
/**
|
||||
* Tests {@link ChartValueSeries}.
|
||||
*
|
||||
@@ -42,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
|
||||
@@ -61,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());
|
||||
@@ -71,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());
|
||||
@@ -81,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);
|
||||
}
|
||||
}
|
||||
@@ -1,914 +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.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* A unit test for {@link ContentProviderUtilsImpl}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CustomContentProviderUtilsImplTest {
|
||||
private static final String NAME_PREFIX = "test name";
|
||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||
private static final String TEST_DESC = "Test Desc!";
|
||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||
private 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;
|
||||
|
||||
@Mock
|
||||
private Cursor cursorMock;
|
||||
|
||||
@Mock
|
||||
private ContentResolver contentResolverMock;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
||||
providerUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noPoints() {
|
||||
testIterator(1, 0, 1, false, ContentProviderUtils.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.
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchAscending() {
|
||||
testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchDescending() {
|
||||
testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchAscending() {
|
||||
testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchDescending() {
|
||||
testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
|
||||
long lastPointId = initializeTrack(trackId, numPoints);
|
||||
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
|
||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
||||
while (it.hasNext()) {
|
||||
Location 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());
|
||||
}
|
||||
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);
|
||||
Assert.assertNotNull(track);
|
||||
|
||||
Location[] locations = new Location[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;
|
||||
}
|
||||
providerUtils.bulkInsertTrackPoint(locations, numPoints, id);
|
||||
|
||||
// Load all inserted locations.
|
||||
long lastPointId = -1;
|
||||
int counter = 0;
|
||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
lastPointId = it.getLocationId();
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
||||
Assert.assertEquals(numPoints, track.getNumberOfPoints());
|
||||
Assert.assertEquals(numPoints, counter);
|
||||
|
||||
return lastPointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTrack() {
|
||||
int startColumnIndex = 1;
|
||||
int columnIndex = startColumnIndex;
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex++);
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// Name
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
long trackId = System.currentTimeMillis();
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
// Name
|
||||
String name = NAME_PREFIX + trackId;
|
||||
when(cursorMock.getString(columnIndex++)).thenReturn(name);
|
||||
|
||||
Track track = providerUtils.createTrack(cursorMock);
|
||||
Assert.assertEquals(trackId, track.getId());
|
||||
Assert.assertEquals(name, track.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)}
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteAllTracks() {
|
||||
// Insert track, points and waypoint at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
Waypoint waypoint = new Waypoint();
|
||||
providerUtils.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);
|
||||
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);
|
||||
// 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);
|
||||
Assert.assertEquals(0, tracksPointsCursor.getCount());
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(0, waypointCursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteTrack() {
|
||||
// Insert three tracks, points of two tracks and way point of one track.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
|
||||
providerUtils.insertTrack(track);
|
||||
insertTrackWithLocations(getTrack(trackId + 1, 10));
|
||||
insertTrackWithLocations(getTrack(trackId + 2, 10));
|
||||
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setTrackId(trackId);
|
||||
providerUtils.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);
|
||||
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);
|
||||
// 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);
|
||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(0, waypointCursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getAllTracks()}
|
||||
*/
|
||||
@Test
|
||||
public void testGetAllTracks() {
|
||||
int initialTrackNumber = providerUtils.getAllTracks().size();
|
||||
long trackId = System.currentTimeMillis();
|
||||
providerUtils.insertTrack(getTrack(trackId, 0));
|
||||
List<Track> allTracks = providerUtils.getAllTracks();
|
||||
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
|
||||
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getLastTrack()}
|
||||
*/
|
||||
@Test
|
||||
public void testGetLastTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
providerUtils.insertTrack(getTrack(trackId, 0));
|
||||
Assert.assertEquals(trackId, providerUtils.getLastTrack().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrack(long)}
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
providerUtils.insertTrack(getTrack(trackId, 0));
|
||||
Assert.assertNotNull(providerUtils.getTrack(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)}
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 0);
|
||||
String nameOld = "name1";
|
||||
String nameNew = "name2";
|
||||
track.setName(nameOld);
|
||||
providerUtils.insertTrack(track);
|
||||
Assert.assertEquals(nameOld, providerUtils.getTrack(trackId).getName());
|
||||
track.setName(nameNew);
|
||||
providerUtils.updateTrack(track);
|
||||
Assert.assertEquals(nameNew, providerUtils.getTrack(trackId).getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateContentValues_waypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
// Bottom
|
||||
long startTime = 1000L;
|
||||
// AverageSpeed
|
||||
double minGrade = -20.11;
|
||||
TripStatistics statistics = new TripStatistics();
|
||||
statistics.setStartTime(startTime);
|
||||
statistics.setStopTime(2500L);
|
||||
statistics.setTotalTime(1500L);
|
||||
statistics.setMovingTime(700L);
|
||||
statistics.setTotalDistance(750.0);
|
||||
statistics.setTotalElevationGain(50.0);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
|
||||
|
||||
long waypointId = System.currentTimeMillis();
|
||||
waypoint.setId(waypointId);
|
||||
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
|
||||
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
|
||||
Assert.assertEquals(22 * 1000000, contentValues.get(WaypointsColumns.LONGITUDE));
|
||||
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
|
||||
Assert.assertEquals(startTime, contentValues.get(WaypointsColumns.STARTTIME));
|
||||
Assert.assertEquals(minGrade, contentValues.get(WaypointsColumns.MINGRADE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateWaypoint() {
|
||||
int startColumnIndex = 1;
|
||||
int columnIndex = startColumnIndex;
|
||||
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns._ID)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns.NAME)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns.TRACKID)).thenReturn(columnIndex++);
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// Name
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// trackIdIndex
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
long id = System.currentTimeMillis();
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(id);
|
||||
// Name
|
||||
String name = NAME_PREFIX + id;
|
||||
when(cursorMock.getString(columnIndex++)).thenReturn(name);
|
||||
// trackIdIndex
|
||||
long trackId = 11L;
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
|
||||
Waypoint waypoint = providerUtils.createWaypoint(cursorMock);
|
||||
Assert.assertEquals(id, waypoint.getId());
|
||||
Assert.assertEquals(name, waypoint.getName());
|
||||
Assert.assertEquals(trackId, waypoint.getTrackId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||
* 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);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setDescription(TEST_DESC);
|
||||
waypoint1.setTrackId(trackId);
|
||||
waypoint1.setType(WaypointType.STATISTICS);
|
||||
providerUtils.insertWaypoint(waypoint1);
|
||||
|
||||
// Delete
|
||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||
|
||||
@Override
|
||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateTrackDescription(Track aTrack, boolean html) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
|
||||
|
||||
Assert.assertNull(providerUtils.getWaypoint(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||
* 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);
|
||||
|
||||
TripStatistics statistics = new TripStatistics();
|
||||
statistics.setStartTime(1000L);
|
||||
statistics.setStopTime(2500L);
|
||||
statistics.setTotalTime(1500L);
|
||||
statistics.setMovingTime(700L);
|
||||
statistics.setTotalDistance(750.0);
|
||||
statistics.setTotalElevationGain(50.0);
|
||||
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);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setDescription(Long.toString(trackId));
|
||||
waypoint1.setTrackId(trackId);
|
||||
waypoint1.setType(WaypointType.STATISTICS);
|
||||
waypoint1.setTripStatistics(statistics);
|
||||
long waypoint1Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
|
||||
|
||||
Waypoint waypoint2 = new Waypoint();
|
||||
waypoint2.setDescription(Long.toString(trackId));
|
||||
waypoint2.setTrackId(trackId);
|
||||
waypoint2.setType(WaypointType.STATISTICS);
|
||||
waypoint2.setTripStatistics(statistics);
|
||||
long waypoint2Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
|
||||
|
||||
|
||||
// Delete
|
||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||
@Override
|
||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
||||
}
|
||||
|
||||
@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(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
||||
.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetFirstWaypointId() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setTrackId(trackId);
|
||||
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 ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetNextWaypointNumber() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.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);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and
|
||||
* {@link ContentProviderUtilsImpl#getWaypoint(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertAndGetWaypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
waypoint.setTrackId(trackId);
|
||||
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
|
||||
|
||||
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(waypointId).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateWaypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
// Insert at first.
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
waypoint.setTrackId(trackId);
|
||||
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
|
||||
|
||||
// Update
|
||||
waypoint = providerUtils.getWaypoint(waypointId);
|
||||
waypoint.setDescription(TEST_DESC_NEW);
|
||||
providerUtils.updateWaypoint(waypoint);
|
||||
|
||||
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(waypointId).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], int, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testBulkInsertTrackPoint() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTrackPoint() {
|
||||
// Set index.
|
||||
int index = 1;
|
||||
// Id
|
||||
when(cursorMock.getColumnIndex(TrackPointsColumns._ID)).thenReturn(index++);
|
||||
// Longitude
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).thenReturn(index++);
|
||||
// Latitude
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).thenReturn(index++);
|
||||
// Time
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME))
|
||||
.thenReturn(index++);
|
||||
// Speed
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).thenReturn(index++);
|
||||
// Sensor
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).thenReturn(index++);
|
||||
|
||||
// Set return value of isNull().
|
||||
index = 2;
|
||||
// Longitude
|
||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
||||
// Latitude
|
||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
||||
// Time
|
||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
||||
// Speed
|
||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
||||
// Sensor
|
||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
||||
|
||||
// Set return value of isNull().
|
||||
index = 2;
|
||||
// Longitude
|
||||
int longitude = 11;
|
||||
when(cursorMock.getInt(index++)).thenReturn(longitude * 1000000);
|
||||
// Latitude.
|
||||
int latitude = 22;
|
||||
when(cursorMock.getInt(index++)).thenReturn(latitude * 1000000);
|
||||
// Time
|
||||
long time = System.currentTimeMillis();
|
||||
when(cursorMock.getLong(index++)).thenReturn(time);
|
||||
// Speed
|
||||
float speed = 2.2f;
|
||||
when(cursorMock.getFloat(index++)).thenReturn(speed);
|
||||
// Sensor
|
||||
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);
|
||||
Assert.assertEquals(time, location.getTime(), 0.01);
|
||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertTrackPoint() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
providerUtils.insertTrackPoint(createLocation(22), trackId);
|
||||
Assert.assertEquals(11, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetFirstTrackPointId() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
Assert.assertNotEquals(-1L, providerUtils.getFirstTrackPointId(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetLastTrackPointId() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
Assert.assertTrue(providerUtils.getFirstTrackPointId(trackId) < providerUtils.getLastTrackPointId(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetLastValidTrackPoint() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
Location lastLocation = providerUtils.getLastValidTrackPoint(trackId);
|
||||
checkLocation(9, lastLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_desc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
||||
Assert.assertEquals(2, cursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_asc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
||||
Assert.assertEquals(2, cursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_desc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[9];
|
||||
|
||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
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.assertFalse(locationIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_asc() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[0];
|
||||
|
||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
Assert.assertTrue(locationIterator.hasNext());
|
||||
Location location = locationIterator.next();
|
||||
Assert.assertEquals(startTrackPointId + i, locationIterator.getLocationId());
|
||||
|
||||
checkLocation(i, location);
|
||||
}
|
||||
Assert.assertFalse(locationIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a track which is used for testing.
|
||||
*
|
||||
* @param id the id of the track
|
||||
* @param numPoints the location number in the track
|
||||
* @return the simulated track
|
||||
*/
|
||||
private Track getTrack(long id, int numPoints) {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id);
|
||||
track.setNumberOfPoints(numPoints);
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
track.addLocation(createLocation(i));
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*
|
||||
* @param i the index to set the value of location.
|
||||
* @return created location
|
||||
*/
|
||||
private Location createLocation(int i) {
|
||||
Location loc = new Location("test");
|
||||
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
loc.setAccuracy((float) i / 100.0f);
|
||||
loc.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
return loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the value of a location.
|
||||
*
|
||||
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
||||
* @param 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((float) i / 100.0f, location.getAccuracy(), 0.01);
|
||||
Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a track with locations into the database.
|
||||
*
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
@@ -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,48 @@ 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();
|
||||
public void testGenerateTrackDescriptionWithoutMaxMinElevation() {
|
||||
Track track = new Track();
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setMaxElevation(550);
|
||||
stats.setMinElevation(-500);
|
||||
stats.setMaxElevation(Double.POSITIVE_INFINITY);
|
||||
stats.setMinElevation(Double.NEGATIVE_INFINITY);
|
||||
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()));
|
||||
stats.setStartTime_ms(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
String expected = //"Created by"
|
||||
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
|
||||
+ "Name: -<br>"
|
||||
+ "Activity type: hiking<br>"
|
||||
+ "Description: -<br>"
|
||||
+ "Total distance: 20.00 km (12.4 mi)<br>"
|
||||
+ "Total time: 10:00<br>"
|
||||
+ "Moving time: 05:00<br>"
|
||||
+ "Average speed: 120.00 km/h (74.6 mi/h)<br>"
|
||||
+ "Average moving speed: 240.00 km/h (149.1 mi/h)<br>"
|
||||
+ "Max speed: 360.00 km/h (223.7 mi/h)<br>"
|
||||
+ "Average pace: 0:30 min/km (0:48 min/mi)<br>"
|
||||
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
|
||||
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
|
||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||
|
||||
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteDistance() {
|
||||
@@ -138,7 +134,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 +144,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 +154,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 +164,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 +172,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,67 +56,57 @@ public class SearchEngineTest {
|
||||
private ContentProviderUtils providerUtils;
|
||||
private SearchEngine engine;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
||||
providerUtils = new ContentProviderUtils(context);
|
||||
engine = new SearchEngine(providerUtils);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
public void tearDown() {
|
||||
providerUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
private long insertTrack(String title, String description, String category, double distance, long hoursAgo) {
|
||||
private long insertTrack(String title, String description, String category, long hoursAgo) {
|
||||
Track track = new Track();
|
||||
track.setName(title);
|
||||
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);
|
||||
}
|
||||
|
||||
private long insertTrack(String title, String description, String category) {
|
||||
return insertTrack(title, description, category, 0, -1);
|
||||
}
|
||||
|
||||
private long insertTrack(String title, double distance) {
|
||||
return insertTrack(title, "", "", distance, -1);
|
||||
return insertTrack(title, description, category, -1);
|
||||
}
|
||||
|
||||
private long insertTrack(String title, long hoursAgo) {
|
||||
return insertTrack(title, "", "", 0.0, hoursAgo);
|
||||
return insertTrack(title, "", "", hoursAgo);
|
||||
}
|
||||
|
||||
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 +147,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 +172,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 +193,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 +202,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 +210,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 +223,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 +237,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 +250,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 +263,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,100 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Create a track without any trackPoints.
|
||||
*/
|
||||
public static Track createTrack(long trackId) {
|
||||
Track track = new Track();
|
||||
track.setId(trackId);
|
||||
track.setName("Test: " + trackId);
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a track which is used for testing.
|
||||
*
|
||||
* @param trackId the trackId of the track
|
||||
* @param numPoints the trackPoints number in the track
|
||||
*/
|
||||
public static Pair<Track, TrackPoint[]> createTrack(long trackId, int numPoints) {
|
||||
Track track = createTrack(trackId);
|
||||
|
||||
TrackPoint[] trackPoints = new TrackPoint[numPoints];
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
trackPoints[i] = (createTrackPoint(i));
|
||||
}
|
||||
|
||||
return new Pair<>(track, trackPoints);
|
||||
}
|
||||
|
||||
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, long trackId, int numPoints) {
|
||||
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
|
||||
|
||||
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
|
||||
|
||||
return pair.first;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a track with locations into the database.
|
||||
*
|
||||
* @param track track to be inserted
|
||||
* @param trackPoints trackPoints to be inserted
|
||||
*/
|
||||
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, TrackPoint[] trackPoints) {
|
||||
contentProviderUtils.insertTrack(track);
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Waypoint with a photo.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param trackId The track id.
|
||||
* @param location The location.
|
||||
* @return the Waypoint created.
|
||||
*/
|
||||
public static Waypoint createWaypointWithPhoto(Context context, long trackId, Location location) throws IOException {
|
||||
String photoUrl = "";
|
||||
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
|
||||
dstFile.createNewFile();
|
||||
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
|
||||
photoUrl = photoUri.toString();
|
||||
|
||||
return new Waypoint("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,883 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.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 de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* A unit test for {@link ContentProviderUtils}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CustomContentProviderUtilsTest {
|
||||
private static final String NAME_PREFIX = "test name";
|
||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||
private static final String TEST_DESC = "Test Desc!";
|
||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@Mock
|
||||
private Cursor cursorMock;
|
||||
|
||||
@Mock
|
||||
private ContentResolver contentResolverMock;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noPoints() {
|
||||
testIterator(1, 0, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchAscending() {
|
||||
testIterator(1, 50, 100, false);
|
||||
testIterator(2, 50, 50, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchDescending() {
|
||||
testIterator(1, 50, 100, true);
|
||||
testIterator(2, 50, 50, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchAscending() {
|
||||
testIterator(1, 50, 11, false);
|
||||
testIterator(2, 50, 25, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchDescending() {
|
||||
testIterator(1, 50, 11, true);
|
||||
testIterator(2, 50, 25, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
testIterator(1, 20000, 2000, false);
|
||||
}
|
||||
|
||||
private void testIterator(long trackId, int numPoints, int batchSize, boolean descending) {
|
||||
long lastPointId = initializeTrack(trackId, numPoints);
|
||||
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
||||
List<TrackPoint> locations = new ArrayList<>(numPoints);
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending)) {
|
||||
while (it.hasNext()) {
|
||||
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.getTrackPointId());
|
||||
}
|
||||
Assert.assertEquals(numPoints, locations.size());
|
||||
}
|
||||
}
|
||||
|
||||
private long initializeTrack(long id, int numPoints) {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
track = contentProviderUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
|
||||
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);
|
||||
trackPoints[i] = new TrackPoint(loc);
|
||||
}
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
|
||||
|
||||
// Load all inserted trackPoints.
|
||||
long lastPointId = -1;
|
||||
int counter = 0;
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false)) {
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
lastPointId = it.getTrackPointId();
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
||||
Assert.assertEquals(numPoints, counter);
|
||||
|
||||
return lastPointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#createTrack(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTrack() {
|
||||
int startColumnIndex = 1;
|
||||
int columnIndex = startColumnIndex;
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex++);
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// Name
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
long trackId = System.currentTimeMillis();
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
// Name
|
||||
String name = NAME_PREFIX + trackId;
|
||||
when(cursorMock.getString(columnIndex++)).thenReturn(name);
|
||||
|
||||
Track track = contentProviderUtils.createTrack(cursorMock);
|
||||
Assert.assertEquals(trackId, track.getId());
|
||||
Assert.assertEquals(name, track.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteAllTracks() {
|
||||
// Insert track, points and waypoint at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
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_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.
|
||||
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_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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteAllTracks_withWaypointAndPhoto() throws IOException {
|
||||
// Insert track, points and waypoint with photo at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||
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_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());
|
||||
// Check waypoint has photo and it's in the external storage.
|
||||
Assert.assertTrue(waypoint.hasPhoto());
|
||||
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||
Assert.assertTrue(dir.isDirectory());
|
||||
Assert.assertEquals(1, dir.list().length);
|
||||
Assert.assertTrue(dir.exists());
|
||||
// Delete all.
|
||||
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_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());
|
||||
Assert.assertFalse(dir.exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteTrack() {
|
||||
// Insert three tracks, points of two tracks and way point of one track.
|
||||
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 0);
|
||||
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
|
||||
|
||||
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId + 1));
|
||||
waypoint.setTrackId(trackId);
|
||||
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_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.
|
||||
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_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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteTrack_withWaypointPhoto() throws IOException {
|
||||
// Insert three tracks.
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
|
||||
|
||||
// Insert a waypoint in tracks trackId and trackId + 1.
|
||||
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint1.getLocation());
|
||||
contentProviderUtils.insertWaypoint(waypoint1);
|
||||
File dir1 = FileUtils.getPhotoDir(context, trackId);
|
||||
|
||||
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId + 1);
|
||||
Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId + 1, trackPoint2.getLocation());
|
||||
contentProviderUtils.insertWaypoint(waypoint2);
|
||||
File dir2 = FileUtils.getPhotoDir(context, trackId + 1);
|
||||
|
||||
// Check.
|
||||
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_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(30, tracksPointsCursor.getCount());
|
||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(2, waypointCursor.getCount());
|
||||
Assert.assertTrue(waypoint1.hasPhoto());
|
||||
Assert.assertTrue(dir1.isDirectory());
|
||||
Assert.assertEquals(1, dir1.list().length);
|
||||
Assert.assertTrue(dir1.exists());
|
||||
Assert.assertTrue(dir2.isDirectory());
|
||||
Assert.assertEquals(1, dir2.list().length);
|
||||
Assert.assertTrue(dir2.exists());
|
||||
// Delete one track.
|
||||
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_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(1, waypointCursor.getCount());
|
||||
Assert.assertFalse(dir1.exists());
|
||||
Assert.assertTrue(dir2.exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getAllTracks()}
|
||||
*/
|
||||
@Test
|
||||
public void testGetAllTracks() {
|
||||
// given
|
||||
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
|
||||
long trackId = System.currentTimeMillis();
|
||||
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
|
||||
|
||||
// when
|
||||
List<Track> allTracks = contentProviderUtils.getAllTracks();
|
||||
|
||||
// then
|
||||
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
|
||||
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getLastTrack()}
|
||||
*/
|
||||
@Test
|
||||
public void testGetLastTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
|
||||
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getTrack(long)}
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
|
||||
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#updateTrack(Track)}
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateTrack() {
|
||||
// given
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrack(trackId);
|
||||
String nameOld = "name1";
|
||||
String nameNew = "name2";
|
||||
track.setName(nameOld);
|
||||
|
||||
// when / then
|
||||
contentProviderUtils.insertTrack(track);
|
||||
Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
|
||||
track.setName(nameNew);
|
||||
contentProviderUtils.updateTrack(track);
|
||||
Assert.assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#createContentValues(Waypoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateContentValues_waypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
|
||||
// Bottom
|
||||
long startTime = 1000L;
|
||||
// AverageSpeed
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setStartTime_ms(startTime);
|
||||
statistics.setStopTime_ms(2500L);
|
||||
statistics.setTotalTime(1500L);
|
||||
statistics.setMovingTime(700L);
|
||||
statistics.setTotalDistance(750.0);
|
||||
statistics.setTotalElevationGain(50.0);
|
||||
statistics.setMaxSpeed(60.0);
|
||||
statistics.setMaxElevation(1250.0);
|
||||
statistics.setMinElevation(1200.0);
|
||||
|
||||
track.first.setTrackStatistics(statistics);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
|
||||
Waypoint waypoint = new Waypoint(track.second[0]);
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
||||
|
||||
long waypointId = System.currentTimeMillis();
|
||||
waypoint.setId(waypointId);
|
||||
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
|
||||
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
|
||||
Assert.assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(WaypointsColumns.LONGITUDE));
|
||||
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#createWaypoint(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateWaypoint() {
|
||||
int startColumnIndex = 1;
|
||||
int columnIndex = startColumnIndex;
|
||||
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns._ID)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns.NAME)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns.TRACKID)).thenReturn(columnIndex++);
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// Name
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// trackIdIndex
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
long id = System.currentTimeMillis();
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(id);
|
||||
// Name
|
||||
String name = NAME_PREFIX + id;
|
||||
when(cursorMock.getString(columnIndex++)).thenReturn(name);
|
||||
// trackIdIndex
|
||||
long trackId = 11L;
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
|
||||
Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
|
||||
Assert.assertEquals(id, waypoint.getId());
|
||||
Assert.assertEquals(name, waypoint.getName());
|
||||
Assert.assertEquals(trackId, waypoint.getTrackId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
|
||||
* when there is only one waypoint in the track.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteWaypoint_onlyOneWayPoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint1.setDescription(TEST_DESC);
|
||||
waypoint1.setTrackId(trackId);
|
||||
contentProviderUtils.insertWaypoint(waypoint1);
|
||||
|
||||
// Check insert was done.
|
||||
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
|
||||
|
||||
// Get waypoint id that needs to delete.
|
||||
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
||||
|
||||
// Delete
|
||||
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
|
||||
|
||||
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
|
||||
* when there is only one waypoint in the track.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteWaypoint_onlyOneWayPointWithPhotoUrl() throws IOException {
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
// Insert at first.
|
||||
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||
contentProviderUtils.insertWaypoint(waypoint1);
|
||||
|
||||
// Check insert was done.
|
||||
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
|
||||
|
||||
// Get waypoint id that needs to delete.
|
||||
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
||||
|
||||
// Check waypoint has photo and it's in the external storage.
|
||||
Assert.assertTrue(waypoint1.hasPhoto());
|
||||
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||
Assert.assertTrue(dir.isDirectory());
|
||||
Assert.assertEquals(1, dir.list().length);
|
||||
Assert.assertTrue(dir.exists());
|
||||
|
||||
// Delete
|
||||
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
|
||||
|
||||
// Check waypoint doesn't exists and photo folder was deleted.
|
||||
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||
Assert.assertFalse(dir.exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, long)} when there is more than one waypoint in the track.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteWaypoint_hasNextWayPoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
// Track track = TestDataUtil.createTrackAndInsert(trackId, 10);
|
||||
//
|
||||
// TrackStatistics statistics = new TrackStatistics();
|
||||
// statistics.setStartTime_ms(1000L);
|
||||
// statistics.setStopTime_ms(2500L);
|
||||
// statistics.setTotalTime(1500L);
|
||||
// statistics.setMovingTime(700L);
|
||||
// statistics.setTotalDistance(750.0);
|
||||
// statistics.setTotalElevationGain(50.0);
|
||||
// statistics.setMaxSpeed(60.0);
|
||||
// statistics.setMaxElevation(1250.0);
|
||||
// statistics.setMinElevation(1200.0);
|
||||
//
|
||||
// track.setTrackStatistics(statistics);
|
||||
// TestDataUtil.insertTrackWithLocations(contentProviderUtils, track);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint1.setDescription(MOCK_DESC);
|
||||
waypoint1.setTrackId(trackId);
|
||||
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
||||
|
||||
Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint2.setDescription(MOCK_DESC);
|
||||
waypoint2.setTrackId(trackId);
|
||||
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
|
||||
|
||||
// Delete
|
||||
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
|
||||
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||
|
||||
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetNextWaypointNumber() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint1.setTrackId(trackId);
|
||||
Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint2.setTrackId(trackId);
|
||||
Waypoint waypoint3 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint3.setTrackId(trackId);
|
||||
Waypoint waypoint4 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint4.setTrackId(trackId);
|
||||
contentProviderUtils.insertWaypoint(waypoint1);
|
||||
contentProviderUtils.insertWaypoint(waypoint2);
|
||||
contentProviderUtils.insertWaypoint(waypoint3);
|
||||
contentProviderUtils.insertWaypoint(waypoint4);
|
||||
|
||||
Assert.assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
|
||||
* {@link ContentProviderUtils#getWaypoint(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertAndGetWaypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
waypoint.setTrackId(trackId);
|
||||
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||
|
||||
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateWaypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
waypoint.setTrackId(trackId);
|
||||
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||
|
||||
// Update
|
||||
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||
waypoint.setDescription(TEST_DESC_NEW);
|
||||
contentProviderUtils.updateWaypoint(waypoint);
|
||||
|
||||
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testBulkInsertTrackPoint() {
|
||||
// given
|
||||
long trackId = System.currentTimeMillis();
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
|
||||
|
||||
// when / then
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
|
||||
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
contentProviderUtils.bulkInsertTrackPoint(Arrays.copyOfRange(track.second, 0, 8), trackId);
|
||||
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
|
||||
*/
|
||||
//TODO incomplete
|
||||
@Test
|
||||
public void testCreateTrackPoint() {
|
||||
// given
|
||||
when(cursorMock.getColumnIndex(TrackPointsColumns._ID)).thenReturn(1);
|
||||
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).thenReturn(2);
|
||||
when(cursorMock.isNull(2)).thenReturn(false);
|
||||
int longitude = 11;
|
||||
when(cursorMock.getInt(2)).thenReturn(longitude * 1000000);
|
||||
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).thenReturn(3);
|
||||
when(cursorMock.isNull(3)).thenReturn(false);
|
||||
int latitude = 22;
|
||||
when(cursorMock.getInt(3)).thenReturn(latitude * 1000000);
|
||||
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME)).thenReturn(4);
|
||||
when(cursorMock.isNull(4)).thenReturn(false);
|
||||
long time = System.currentTimeMillis();
|
||||
when(cursorMock.getLong(4)).thenReturn(time);
|
||||
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).thenReturn(5);
|
||||
when(cursorMock.isNull(5)).thenReturn(false);
|
||||
float speed = 2.2f;
|
||||
when(cursorMock.getFloat(5)).thenReturn(speed);
|
||||
|
||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).thenReturn(6);
|
||||
when(cursorMock.isNull(6)).thenReturn(false);
|
||||
|
||||
// when
|
||||
TrackPoint trackPoint = contentProviderUtils.createTrackPoint(cursorMock);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(longitude, trackPoint.getLongitude(), 0.01);
|
||||
Assert.assertEquals(latitude, trackPoint.getLatitude(), 0.01);
|
||||
Assert.assertEquals(time, trackPoint.getTime(), 0.01);
|
||||
Assert.assertEquals(speed, trackPoint.getSpeed(), 0.01);
|
||||
Assert.assertFalse(trackPoint.hasHeartRate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertTrackPoint() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
|
||||
Assert.assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAndLoadTrackPoint() {
|
||||
// given
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
|
||||
trackPoint.setHeartRate_bpm(1F);
|
||||
trackPoint.setCyclingCadence_rpm(2F);
|
||||
trackPoint.setPower(3F);
|
||||
|
||||
// when
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertTrue(trackPoints.get(10).hasHeartRate());
|
||||
Assert.assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
|
||||
Assert.assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
|
||||
Assert.assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetLastValidTrackPoint() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
checkLocation(9, lastTrackPoint.getLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_desc() {
|
||||
// given
|
||||
long trackId = System.currentTimeMillis();
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
|
||||
long[] trackpointIds = new long[track.second.length];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||
}
|
||||
|
||||
// when
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(2, cursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_asc() {
|
||||
// given
|
||||
long trackId = System.currentTimeMillis();
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
|
||||
long[] trackpointIds = new long[track.second.length];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||
}
|
||||
|
||||
// when
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(2, cursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_desc() {
|
||||
// given
|
||||
long trackId = System.currentTimeMillis();
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
|
||||
long[] trackpointIds = new long[track.second.length];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[9];
|
||||
// when
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true);
|
||||
|
||||
// then
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
Assert.assertTrue(trackPointIterator.hasNext());
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
Assert.assertEquals(startTrackPointId - i, trackPointIterator.getTrackPointId());
|
||||
checkLocation((trackpointIds.length - 1) - i, trackPoint.getLocation());
|
||||
}
|
||||
Assert.assertFalse(trackPointIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_asc() {
|
||||
// given
|
||||
long trackId = System.currentTimeMillis();
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
|
||||
long[] trackpointIds = new long[track.second.length];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[0];
|
||||
|
||||
// when
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false);
|
||||
|
||||
// then
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
Assert.assertTrue(trackPointIterator.hasNext());
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
Assert.assertEquals(startTrackPointId + i, trackPointIterator.getTrackPointId());
|
||||
|
||||
checkLocation(i, trackPoint.getLocation());
|
||||
}
|
||||
Assert.assertFalse(trackPointIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the value of a location.
|
||||
*
|
||||
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(long, int)}
|
||||
* @param location the location to be checked
|
||||
*/
|
||||
private void checkLocation(int i, Location location) {
|
||||
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 * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package de.dennisguse.opentracks.content.sensor;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.util.UintUtils;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SensorDataCyclingTest {
|
||||
|
||||
@Test
|
||||
public void compute_cadence() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024); // 1s
|
||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 2048); // 2s
|
||||
|
||||
// when
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_cadence_sameCount() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024);
|
||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 2048);
|
||||
|
||||
// when
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(0, current.getCadence_rpm(), 0.01);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void compute_cadence_sameTime() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024);
|
||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 1024);
|
||||
|
||||
// when
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
Assert.assertFalse(current.hasCadence_rpm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_cadence_rollOverTime() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, UintUtils.UINT16_MAX - 1024);
|
||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 0);
|
||||
|
||||
// when
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_cadence_rollOverCount() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed_rollOverCount() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2000);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(2, current.getSpeed_mps(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -17,19 +17,22 @@
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.ChartView;
|
||||
import de.dennisguse.opentracks.TrackStubUtils;
|
||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
@@ -44,293 +47,274 @@ public class ChartFragmentTest {
|
||||
|
||||
private ChartFragment chartFragment;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
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() {
|
||||
// given
|
||||
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);
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
|
||||
// Input incorrect state.
|
||||
// Creates SensorData.
|
||||
SensorDataSet sensorDataSet = new SensorDataSet(SensorDataSet.DATA_UNAVAILABLE, SensorDataSet.DATA_UNAVAILABLE);
|
||||
sensorDataSetLocation.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);
|
||||
// then
|
||||
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();
|
||||
// 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);
|
||||
public void testCreatePendingPoint_sensorCorrect() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setHeartRate_bpm(100f);
|
||||
trackPoint.setCyclingCadence_rpm(101f);
|
||||
trackPoint.setPower(102f);
|
||||
|
||||
// Creates SensorData.
|
||||
SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102);
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
|
||||
// Creates SensorDataSet.
|
||||
sensorDataSetLocation.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);
|
||||
// then
|
||||
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(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,220 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
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() {
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
track.first.setIcon(TRACK_ICON);
|
||||
track.first.setCategory(TRACK_CATEGORY);
|
||||
track.first.setDescription(TRACK_DESCRIPTION);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Waypoint waypoint = new Waypoint(track.second[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);
|
||||
//TODO 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);
|
||||
//TODO 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,11 +25,11 @@ 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;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.eq;
|
||||
@@ -74,9 +72,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 +83,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(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 +107,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(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 +139,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 +151,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(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 +167,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 +205,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(), 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(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(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);
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.os.IBinder;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.FlakyTest;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
@@ -42,13 +43,13 @@ 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.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
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 +57,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 +69,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 +85,26 @@ public class TrackRecordingServiceTest {
|
||||
};
|
||||
customContentProvider.attachInfo(context, null);
|
||||
|
||||
providerUtils = ContentProviderUtils.Factory.get(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);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
// 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,134 +121,19 @@ 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();
|
||||
// given
|
||||
List<Track> tracks = contentProviderUtils.getAllTracks();
|
||||
Assert.assertTrue(tracks.isEmpty());
|
||||
|
||||
// when
|
||||
Intent startIntent = createStartIntent(context);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
// Test if we start in no-recording mode by default.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
@@ -264,19 +142,92 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_oldTracks() throws Exception {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, false);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
// when
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||
Intent startIntent = createStartIntent(context);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
public void testRecording_serviceRestart_whileRecording() throws Exception {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
//when
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
|
||||
// then
|
||||
Assert.assertTrue(service.isRecording());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_pauseAndResume() throws Exception {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
insertLocation(service);
|
||||
|
||||
// when
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
|
||||
//when
|
||||
service.resumeTrack(trackId);
|
||||
insertLocation(service);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(5, trackPoints.size());
|
||||
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
|
||||
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
|
||||
Assert.assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(3).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_resumeStoppedTrack() throws Exception {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
insertLocation(service);
|
||||
service.endCurrentTrack();
|
||||
|
||||
Assert.assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
insertLocation(service);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(4, trackPoints.size());
|
||||
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
|
||||
Assert.assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@FlakyTest(detail = "Sometimes fails on CI.")
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
|
||||
// when
|
||||
// Just set recording track to a bogus value.
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||
@@ -289,12 +240,17 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
|
||||
long trackId = service.getRecordingTrackId();
|
||||
|
||||
// when
|
||||
long newTrackId = service.startNewTrack();
|
||||
|
||||
// then
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||
|
||||
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
@@ -304,116 +260,99 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
// when
|
||||
// Ending the current track when there is no recording should not result in any error.
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(PreferencesUtils.isRecording(context));
|
||||
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.
|
||||
}
|
||||
|
||||
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);
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
|
||||
Location location = new Location("gps");
|
||||
location.setLongitude(35.0f);
|
||||
location.setLatitude(45.0f);
|
||||
location.setAccuracy(5);
|
||||
location.setSpeed(10);
|
||||
location.setTime(System.currentTimeMillis());
|
||||
location.setBearing(3.0f);
|
||||
trackRecordingService.insertTrackPoint(location);
|
||||
|
||||
Thread.sleep(200);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
// when
|
||||
long waypointId = service.insertWaypoint(null, null, null, null);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(-1L, waypointId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
insertLocation(service);
|
||||
long trackId = service.getRecordingTrackId();
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
// when
|
||||
long waypointId = service.insertWaypoint(null, null, null, null);
|
||||
|
||||
// then
|
||||
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();
|
||||
}
|
||||
|
||||
private void addTrack(Track track, boolean isRecording) {
|
||||
Assert.assertTrue(track.getId() >= 0);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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");
|
||||
TrackStatistics trackStatistics = new TrackStatistics();
|
||||
trackStatistics.setStopTime_ms(stopTime);
|
||||
dummyTrack.setTrackStatistics(trackStatistics);
|
||||
addTrack(dummyTrack, isRecording);
|
||||
}
|
||||
|
||||
private static void insertLocation(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||
insertLocation(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
static void insertLocation(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||
insertLocation(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private static void insertLocation(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
|
||||
Location location = new Location("gps");
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
location.setAccuracy(accuracy);
|
||||
location.setSpeed(speed);
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
trackRecordingService.insertLocation(location);
|
||||
|
||||
//TODO Needed?
|
||||
Thread.sleep(200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Tests insert location.
|
||||
* <p>
|
||||
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
|
||||
* So, if it is executed on a real device, data might be lost.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackRecordingServiceTestLocation {
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private TrackRecordingServiceInterface service;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws TimeoutException {
|
||||
// Set up the mock content resolver
|
||||
ContentProvider customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
customContentProvider.attachInfo(context, null);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||
//Disable executorService to not insert locations from GPS via LocationManager
|
||||
service.enableLocationExecutor(false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Reset service (if some previous test failed)
|
||||
service.enableLocationExecutor(true);
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
// Ensure that the database is empty after every test
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_movingAccurate() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0005, 35.0, 5, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(6, trackPoints.size());
|
||||
Assert.assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_movingInaccurate() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.3, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 99.0, 35.0, Long.MAX_VALUE, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(1, trackPoints.size());
|
||||
Assert.assertEquals(45.0, trackPoints.get(0).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000005, 35.0, 5, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(2, trackPoints.size());
|
||||
Assert.assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
// @MediumTest
|
||||
// @Test
|
||||
// public void testOnLocationChangedAsync_repeatedTime() throws Exception {
|
||||
// // when
|
||||
// TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15, 5);
|
||||
// TrackRecordingServiceTest.insertLocation(service, 55.0, 35.0, 5, 15, 5);
|
||||
// TrackRecordingServiceTest.insertLocation(service, 65.0, 35.0, 5, 15, 5);
|
||||
//
|
||||
// service.endCurrentTrack();
|
||||
//
|
||||
// // then
|
||||
// Assert.assertFalse(service.isRecording());
|
||||
//
|
||||
// List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
// Assert.assertEquals(1, trackPoints.size());
|
||||
// Assert.assertEquals(45.0, trackPoints.get(0).getLatitude(), 0.01);
|
||||
// }
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_idle() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 6, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(3, trackPoints.size());
|
||||
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(6, trackPoints.get(2).getAccuracy(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 6, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(4, trackPoints.size());
|
||||
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(5, trackPoints.get(2).getAccuracy(), 0.01); //TODO Check why this trackPoint is inserted.
|
||||
Assert.assertEquals(6, trackPoints.get(3).getAccuracy(), 0.01);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
service.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SensorDataSet getSensorData() {
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataHeartRate("sensorName", "sensorAddress", 5f));
|
||||
return sensorDataSet;
|
||||
}
|
||||
});
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 0, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(6, trackPoints.size());
|
||||
Assert.assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(4, trackPoints.get(4).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(5, trackPoints.get(5).getAccuracy(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_segment() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, 5, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(7, trackPoints.size());
|
||||
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
|
||||
Assert.assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(4).getLatitude(), 0.01);
|
||||
Assert.assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_firstTrackPointInvalid() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
service.insertLocation(TrackPoint.createPause().getLocation());
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 0, 0);
|
||||
service.insertLocation(TrackPoint.createPause().getLocation());
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(1, trackPoints.size());
|
||||
Assert.assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -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 = ContentProviderUtils.Factory.get(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
|
||||
@@ -151,27 +147,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, Integer.parseInt(context.getResources().getString(R.string.split_frequency_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByTime() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||
@@ -211,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));
|
||||
@@ -240,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,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, "airplane");
|
||||
|
||||
// then
|
||||
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
|
||||
public class BluetoothUtilsTest {
|
||||
|
||||
@Test
|
||||
public void parseHeartRate_uint8() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||
|
||||
// when
|
||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(60, heartRate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseHeartRate_uint16() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||
|
||||
// when
|
||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(257, heartRate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||
|
||||
// when
|
||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
Assert.assertNull(sensor.getSpeed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
Assert.assertNull(sensor.getCadence());
|
||||
Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankWheel() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
}
|
||||
@@ -15,102 +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));
|
||||
String expected = "Swim_ming-^across_the_ pacific (ocean).";
|
||||
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanitizeFileNameWithSuffix() {
|
||||
String name = "FileName.jpeg";
|
||||
Assert.assertEquals(name, FileUtils.sanitizeFileName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
|
||||
* Verifies that they are allowed.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
public class TrackPointUtilsTest {
|
||||
|
||||
@Test
|
||||
public void fixTime_none() {
|
||||
// given
|
||||
long time = System.currentTimeMillis();
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
trackPoint.setTime(time);
|
||||
|
||||
// when
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(time, trackPoint.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fixTime_0() {
|
||||
// given
|
||||
long time = System.currentTimeMillis();
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
trackPoint.setTime(0);
|
||||
|
||||
// when
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(time, trackPoint.getTime(), 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fixTime_gpsWeekRollover() {
|
||||
// given
|
||||
long time = System.currentTimeMillis();
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
trackPoint.setTime(time - 1024 * UnitConversions.ONE_WEEK_MS);
|
||||
|
||||
// when
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(time, trackPoint.getTime());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,22 +15,23 @@ limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="3149"
|
||||
android:versionName="v3.2.7"> <!-- For FDroid. -->
|
||||
android:installLocation="auto">
|
||||
<!-- 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 +84,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 +100,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 +116,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"
|
||||
@@ -145,44 +135,21 @@ limitations under the License.
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
<!-- Permissions to access sensors -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.faketouch"
|
||||
android:required="false" />
|
||||
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.compass"
|
||||
android:required="false" />
|
||||
<!-- Permission to read/write external storage -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony.gsm"
|
||||
android:required="false" />
|
||||
<!-- Required features -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<!-- Optional features -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen.multitouch"
|
||||
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_PHONE_STATE" />
|
||||
|
||||
<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();
|
||||
@@ -69,7 +69,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -61,7 +61,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
return;
|
||||
}
|
||||
|
||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
||||
|
||||
markerIds = new ArrayList<>();
|
||||
@@ -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,19 +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);
|
||||
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() {
|
||||
@@ -96,7 +132,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,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.
|
||||
*/
|
||||
@@ -125,9 +219,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
});
|
||||
|
||||
if (newMarker) {
|
||||
statisticsSection.setVisibility(View.GONE);
|
||||
waypointSection.setVisibility(View.VISIBLE);
|
||||
int nextWaypointNumber = trackId == -1L ? -1 : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
||||
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
|
||||
if (nextWaypointNumber == -1) {
|
||||
nextWaypointNumber = 0;
|
||||
}
|
||||
@@ -136,52 +228,93 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypointMarkerType.setText("");
|
||||
waypointDescription.setText("");
|
||||
} else {
|
||||
waypoint = ContentProviderUtils.Factory.get(this).getWaypoint(markerId);
|
||||
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
|
||||
if (waypoint == null) {
|
||||
Log.d(TAG, "waypoint is null");
|
||||
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());
|
||||
}
|
||||
ContentProviderUtils.Factory.get(this).updateWaypoint(waypoint);
|
||||
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,15 +37,15 @@ 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.Waypoint.WaypointType;
|
||||
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;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -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 = ContentProviderUtils.Factory.get(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));
|
||||
@@ -152,7 +145,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE);
|
||||
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
|
||||
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
||||
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
|
||||
@@ -161,13 +153,13 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
|
||||
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
|
||||
|
||||
boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)] == WaypointType.STATISTICS;
|
||||
int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin;
|
||||
int iconId = MarkerUtils.ICON_ID;
|
||||
String name = cursor.getString(nameIndex);
|
||||
long time = cursor.getLong(timeIndex);
|
||||
String category = statistics ? null : cursor.getString(categoryIndex);
|
||||
String description = statistics ? null : cursor.getString(descriptionIndex);
|
||||
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);
|
||||
|
||||
@@ -183,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,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,21 +40,21 @@ 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.Waypoint.WaypointType;
|
||||
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;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
@@ -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);
|
||||
@@ -170,9 +170,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
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
|
||||
@@ -393,11 +393,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
}
|
||||
}
|
||||
|
||||
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
|
||||
|
||||
resultMap.put(IS_RECORDING_FIELD, false);
|
||||
resultMap.put(IS_PAUSED_FIELD, true);
|
||||
resultMap.put(ICON_ID_FIELD, statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin);
|
||||
resultMap.put(ICON_ID_FIELD, MarkerUtils.ICON_ID);
|
||||
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker);
|
||||
resultMap.put(NAME_FIELD, waypoint.getName());
|
||||
// Display the marker's track name in the total time field
|
||||
@@ -405,8 +403,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
resultMap.put(TOTAL_DISTANCE_FIELD, null);
|
||||
resultMap.put(MARKER_COUNT_FIELD, 0);
|
||||
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
|
||||
resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory());
|
||||
resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription());
|
||||
resultMap.put(CATEGORY_FIELD, waypoint.getCategory());
|
||||
resultMap.put(DESCRIPTION_FIELD, waypoint.getDescription());
|
||||
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
|
||||
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
|
||||
resultMap.put(MARKER_ID_FIELD, waypoint.getId());
|
||||
@@ -426,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();
|
||||
|
||||
@@ -435,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,51 +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.StatsUtils;
|
||||
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
|
||||
@@ -73,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
|
||||
@@ -105,38 +90,62 @@ 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) {
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||
setLockscreenPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (key == null) return;
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -151,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
|
||||
@@ -185,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 = ContentProviderUtils.Factory.get(this);
|
||||
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);
|
||||
@@ -227,11 +248,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this);
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
@@ -242,6 +265,17 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -256,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
|
||||
@@ -273,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
|
||||
@@ -316,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);
|
||||
}
|
||||
@@ -345,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;
|
||||
@@ -368,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);
|
||||
@@ -377,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;
|
||||
@@ -413,22 +417,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
return trackDataHub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track id.
|
||||
*/
|
||||
// TODO Seems to be no used.
|
||||
@Deprecated
|
||||
public long getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
private void setBluetoothHeartRateSensorName(String sensorName) {
|
||||
StatsUtils.setBluetoothHeartRateSensorName(this, sensorName, isRecording());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the data in the intent.
|
||||
*/
|
||||
private void handleIntent(Intent intent) {
|
||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
@@ -440,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);
|
||||
@@ -464,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);
|
||||
}
|
||||
@@ -497,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");
|
||||
@@ -73,7 +73,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
return;
|
||||
}
|
||||
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.e(TAG, "No track for " + trackId);
|
||||
@@ -175,7 +175,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -50,11 +49,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 +126,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 +136,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 +159,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 +170,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 +184,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 +205,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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -245,10 +227,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
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 +326,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
super.onStop();
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
trackRecordingServiceConnection.unbind();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -371,7 +353,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);
|
||||
}
|
||||
@@ -400,21 +381,16 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
} else {
|
||||
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||
|
||||
// Show toast
|
||||
Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT);
|
||||
toast.setGravity(Gravity.CENTER, 0, 0);
|
||||
toast.show();
|
||||
|
||||
// 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 +401,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 +437,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 +460,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 +467,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.data.TrackPoint;
|
||||
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, TrackPoint trackPoint, 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 (trackPoint != null) {
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRate = trackPoint.getHeartRate_bpm();
|
||||
}
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
cadence = trackPoint.getCyclingCadence_rpm();
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
power = trackPoint.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;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -27,41 +27,41 @@ import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
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.content.Waypoint;
|
||||
import de.dennisguse.opentracks.MarkerDetailActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
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;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* Visualization of the chart.
|
||||
* Provides support for zooming (via pinch), scrolling, flinging, and selecting shown markers (single touch).
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -74,25 +74,31 @@ 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;
|
||||
private double maxX = 1.0;
|
||||
private VelocityTracker velocityTracker = null;
|
||||
private float lastMotionEventX = -1;
|
||||
private int zoomLevel = 1;
|
||||
|
||||
private int leftBorder = BORDER;
|
||||
@@ -112,63 +118,194 @@ public class ChartView extends View {
|
||||
private boolean reportSpeed = true;
|
||||
private boolean showPointer = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public ChartView(Context context, boolean chartByDistance) {
|
||||
private final GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
if (!scroller.isFinished()) {
|
||||
scroller.abortAnimation();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (Math.abs(distanceX) > 0) {
|
||||
int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
|
||||
if (availableToScroll > 0) {
|
||||
scrollBy(Math.min(availableToScroll, (int) distanceX));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
fling((int) -velocityX);
|
||||
return true;
|
||||
}
|
||||
|
||||
@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 / 2f) < markerHeight) {
|
||||
int minDistance = Integer.MAX_VALUE;
|
||||
Waypoint nearestWaypoint = null;
|
||||
synchronized (waypoints) {
|
||||
for (Waypoint waypoint : waypoints) {
|
||||
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearestWaypoint = waypoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestWaypoint != null && minDistance < markerWidth) {
|
||||
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId());
|
||||
getContext().startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
private final ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
float scaleFactor = detector.getScaleFactor();
|
||||
if (scaleFactor >= 1.1f) {
|
||||
zoomIn();
|
||||
return true;
|
||||
} else if (scaleFactor <= 0.9) {
|
||||
zoomOut();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
@@ -187,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);
|
||||
@@ -195,18 +332,19 @@ public class ChartView extends View {
|
||||
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
||||
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
||||
|
||||
Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin);
|
||||
markerWidth = statisticsMarker.getIntrinsicWidth();
|
||||
markerHeight = statisticsMarker.getIntrinsicHeight();
|
||||
statisticsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
Drawable waypointMarker = getResources().getDrawable(R.drawable.ic_marker_blue_pushpin);
|
||||
waypointMarker = MarkerUtils.getDefaultPhoto(context);
|
||||
markerWidth = waypointMarker.getIntrinsicWidth();
|
||||
markerHeight = waypointMarker.getIntrinsicHeight();
|
||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
scroller = new Scroller(context);
|
||||
setFocusable(true);
|
||||
setClickable(true);
|
||||
updateDimensions();
|
||||
|
||||
// either speedSeries or paceSeries should be enabled.
|
||||
speedSeries.setEnabled(reportSpeed);
|
||||
paceSeries.setEnabled(!reportSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -214,23 +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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets chart by distance.
|
||||
* It is expected that after changing this value, data will be reloaded.
|
||||
*
|
||||
* @param value true for by distance, false for by time
|
||||
*/
|
||||
public void setChartByDistance(boolean value) {
|
||||
chartByDistance = value;
|
||||
public boolean getMetricUnits() {
|
||||
return metricUnits;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,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();
|
||||
@@ -286,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();
|
||||
@@ -295,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();
|
||||
@@ -346,10 +462,7 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms out one level.
|
||||
*/
|
||||
public void zoomOut() {
|
||||
private void zoomOut() {
|
||||
if (canZoomOut()) {
|
||||
zoomLevel--;
|
||||
scroller.abortAnimation();
|
||||
@@ -369,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();
|
||||
@@ -380,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;
|
||||
@@ -411,86 +524,20 @@ public class ChartView extends View {
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (velocityTracker == null) {
|
||||
velocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
velocityTracker.addMovement(event);
|
||||
float x = event.getX();
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Stop the fling
|
||||
if (!scroller.isFinished()) {
|
||||
scroller.abortAnimation();
|
||||
}
|
||||
lastMotionEventX = x;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (lastMotionEventX == -1) {
|
||||
break;
|
||||
}
|
||||
// Scroll to follow the motion event
|
||||
int deltaX = (int) (lastMotionEventX - x);
|
||||
lastMotionEventX = x;
|
||||
if (deltaX < 0) {
|
||||
if (getScrollX() > 0) {
|
||||
scrollBy(deltaX);
|
||||
}
|
||||
} else if (deltaX > 0) {
|
||||
int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
|
||||
if (availableToScroll > 0) {
|
||||
scrollBy(Math.min(availableToScroll, deltaX));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
// Check if the y event is within markerHeight of the marker center
|
||||
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) {
|
||||
int minDistance = Integer.MAX_VALUE;
|
||||
Waypoint nearestWaypoint = null;
|
||||
synchronized (waypoints) {
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
Waypoint waypoint = waypoints.get(i);
|
||||
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearestWaypoint = waypoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestWaypoint != null && minDistance < markerWidth) {
|
||||
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId());
|
||||
getContext().startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
VelocityTracker myVelocityTracker = velocityTracker;
|
||||
myVelocityTracker.computeCurrentVelocity(1000);
|
||||
int initialVelocity = (int) myVelocityTracker.getXVelocity();
|
||||
if (Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) {
|
||||
fling(-initialVelocity);
|
||||
}
|
||||
if (velocityTracker != null) {
|
||||
velocityTracker.recycle();
|
||||
velocityTracker = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
boolean isZoom = detectorZoom.onTouchEvent(event);
|
||||
boolean isScrollTab = detectorScrollFlingTab.onTouchEvent(event);
|
||||
return isZoom || isScrollTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
updateEffectiveDimensionsIfChanged(
|
||||
View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec));
|
||||
updateEffectiveDimensionsIfChanged(View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec));
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
synchronized (chartData) {
|
||||
|
||||
synchronized (chartPoints) {
|
||||
canvas.save();
|
||||
|
||||
canvas.drawColor(Color.WHITE);
|
||||
@@ -499,6 +546,7 @@ public class ChartView extends View {
|
||||
|
||||
clipToGraphArea(canvas);
|
||||
drawDataSeries(canvas);
|
||||
drawWaypoints(canvas);
|
||||
drawGrid(canvas);
|
||||
|
||||
canvas.restore();
|
||||
@@ -532,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.
|
||||
*
|
||||
@@ -546,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
|
||||
@@ -571,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();
|
||||
@@ -591,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);
|
||||
@@ -620,21 +689,17 @@ 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) {
|
||||
return metricUnits ? context.getString(R.string.unit_kilometer)
|
||||
: context.getString(R.string.unit_mile);
|
||||
return metricUnits ? context.getString(R.string.unit_kilometer) : context.getString(R.string.unit_mile);
|
||||
} else {
|
||||
return context.getString(R.string.description_time);
|
||||
}
|
||||
@@ -654,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) {
|
||||
@@ -671,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);
|
||||
}
|
||||
@@ -697,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;
|
||||
}
|
||||
}
|
||||
@@ -734,8 +794,7 @@ public class ChartView extends View {
|
||||
* @param yValue the y value
|
||||
* @return the marker width.
|
||||
*/
|
||||
private float drawYAxisMarker(
|
||||
ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) {
|
||||
private float drawYAxisMarker(ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) {
|
||||
String marker = chartValueSeries.formatMarker(yValue);
|
||||
Paint paint = chartValueSeries.getMarkerPaint();
|
||||
Rect rect = getRect(paint, marker);
|
||||
@@ -750,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();
|
||||
@@ -780,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);
|
||||
@@ -807,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;
|
||||
}
|
||||
}
|
||||
@@ -849,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;
|
||||
@@ -857,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;
|
||||
}
|
||||
@@ -870,8 +921,7 @@ public class ChartView extends View {
|
||||
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
||||
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
||||
// border + x axis marker + spacer + .5 x axis label
|
||||
bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer
|
||||
+ (xAxisLabelRect.height() / 2));
|
||||
bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer + (xAxisLabelRect.height() / 2));
|
||||
rightBorder = (int) (density * BORDER + xAxisLabelRect.width() + spacer);
|
||||
updateEffectiveDimensions();
|
||||
}
|
||||
@@ -953,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,367 +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.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
|
||||
/**
|
||||
* Utilities to access data from the app's content provider.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public interface ContentProviderUtils {
|
||||
|
||||
/**
|
||||
* Maximum number of waypoints that will be loaded at one time.
|
||||
*/
|
||||
int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
||||
|
||||
/**
|
||||
* The authority (the first part of the URI) for the app's content provider.
|
||||
*/
|
||||
String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
||||
|
||||
/**
|
||||
* The base URI for the app's content provider.
|
||||
*/
|
||||
String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
||||
|
||||
/**
|
||||
* The default {@link LocationFactory} which creates a location each time.
|
||||
*/
|
||||
LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() {
|
||||
@Override
|
||||
public Location createLocation() {
|
||||
return new SensorDataSetLocation(LocationManager.GPS_PROVIDER);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears a track: removes waypoints and trackpoints.
|
||||
* Only keeps the track id.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
void clearTrack(Context context, long trackId);
|
||||
|
||||
/**
|
||||
* Creates a {@link Track} from a cursor.
|
||||
*
|
||||
* @param cursor the cursor pointing to the track
|
||||
*/
|
||||
Track createTrack(Cursor cursor);
|
||||
|
||||
/**
|
||||
* Deletes all tracks (including waypoints and track points).
|
||||
*/
|
||||
void deleteAllTracks(Context context);
|
||||
|
||||
/**
|
||||
* Deletes a track.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
void deleteTrack(Context context, long trackId);
|
||||
|
||||
/**
|
||||
* Gets all the tracks.
|
||||
* If no track exists, an empty list is returned.
|
||||
* NOTE: the returned tracks do not have any track points attached.
|
||||
*/
|
||||
List<Track> getAllTracks();
|
||||
|
||||
/**
|
||||
* Gets the last track or null.
|
||||
*/
|
||||
Track getLastTrack();
|
||||
|
||||
/**
|
||||
* Gets a track by a track id or null
|
||||
* Note that the returned track doesn't have any track points attached.
|
||||
*
|
||||
* @param trackId the track id.
|
||||
*/
|
||||
Track getTrack(long trackId);
|
||||
|
||||
/**
|
||||
* Gets a track cursor.
|
||||
* The caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param selection the selection. Can be null
|
||||
* @param selectionArgs the selection arguments. Can be null
|
||||
* @param sortOrder the sort order. Can be null
|
||||
*/
|
||||
Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder);
|
||||
|
||||
/**
|
||||
* Inserts a track.
|
||||
* NOTE: This doesn't insert any track points.
|
||||
*
|
||||
* @param track the track
|
||||
* @return the content provider URI of the inserted track.
|
||||
*/
|
||||
Uri insertTrack(Track track);
|
||||
|
||||
/**
|
||||
* Updates a track.
|
||||
* NOTE: This doesn't update any track points.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
void updateTrack(Track track);
|
||||
|
||||
/**
|
||||
* Creates a waypoint from a cursor.
|
||||
*
|
||||
* @param cursor the cursor pointing to the waypoint
|
||||
*/
|
||||
Waypoint createWaypoint(Cursor cursor);
|
||||
|
||||
/**
|
||||
* Deletes a waypoint.
|
||||
* If deleting a statistics waypoint, this will also correct the next statistics waypoint after the deleted one to reflect the deletion.
|
||||
* The generator is used to update the next statistics waypoint.
|
||||
*
|
||||
* @param waypointId the waypoint id
|
||||
* @param descriptionGenerator the description generator. Can be null for waypoint marker
|
||||
*/
|
||||
void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator);
|
||||
|
||||
/**
|
||||
* Gets the first waypoint id for a track.
|
||||
* The first waypoint is special as it contains the stats for the track.
|
||||
* Returns -1L if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
long getFirstWaypointId(long trackId);
|
||||
|
||||
/**
|
||||
* Gets the last waypoint for a type. Returns null if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param waypointType the waypoint type
|
||||
*/
|
||||
Waypoint getLastWaypoint(long trackId, WaypointType waypointType);
|
||||
|
||||
/**
|
||||
* Gets the next waypoint number for a type.
|
||||
* Returns -1 if not able to get the next waypoint number.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param waypointType the waypoint type
|
||||
*/
|
||||
int getNextWaypointNumber(long trackId, WaypointType waypointType);
|
||||
|
||||
/**
|
||||
* Gets a waypoint from a waypoint id.
|
||||
* Returns null if not found.
|
||||
*
|
||||
* @param waypointId the waypoint id
|
||||
*/
|
||||
Waypoint getWaypoint(long waypointId);
|
||||
|
||||
/**
|
||||
* Gets a waypoint cursor.
|
||||
* he caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param selection the selection. Can be null
|
||||
* @param selectionArgs the selection arguments. Can be null
|
||||
* @param sortOrder the sort order. Can be null
|
||||
* @param maxWaypoints the maximum number of waypoints to return. -1 for no
|
||||
* limit
|
||||
*/
|
||||
Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints);
|
||||
|
||||
/**
|
||||
* Gets a waypoint cursor for a track.
|
||||
* The caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param minWaypointId the minimum waypoint id. -1L to ignore
|
||||
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
|
||||
*/
|
||||
Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints);
|
||||
|
||||
/**
|
||||
* Gets the number of waypoints for a track.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
int getWaypointCount(long trackId);
|
||||
|
||||
/**
|
||||
* Inserts a waypoint.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
* @return the content provider URI of the inserted waypoint.
|
||||
*/
|
||||
Uri insertWaypoint(Waypoint waypoint);
|
||||
|
||||
/**
|
||||
* Updates a waypoint.
|
||||
* Returns true if successful.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
boolean updateWaypoint(Waypoint waypoint);
|
||||
|
||||
/**
|
||||
* Inserts multiple track points.
|
||||
*
|
||||
* @param locations an array of locations
|
||||
* @param length the number of locations (from the beginning of the array) to
|
||||
* insert, or -1 for all of them
|
||||
* @param trackId the track id
|
||||
* @return the number of points inserted
|
||||
*/
|
||||
int bulkInsertTrackPoint(Location[] locations, int length, long trackId);
|
||||
|
||||
/**
|
||||
* Creates a location object from a cursor.
|
||||
*
|
||||
* @param cursor the cursor pointing to the location
|
||||
*/
|
||||
Location createTrackPoint(Cursor cursor);
|
||||
|
||||
/**
|
||||
* Gets the first location id for a track.
|
||||
* Returns -1L if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
long getFirstTrackPointId(long trackId);
|
||||
|
||||
/**
|
||||
* Gets the last location id for a track.
|
||||
* Returns -1L if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
long getLastTrackPointId(long trackId);
|
||||
|
||||
/**
|
||||
* Gets the track point id of a location.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param location the location
|
||||
* @return track point id if the location is in the track. -1L otherwise.
|
||||
*/
|
||||
long getTrackPointId(long trackId, Location location);
|
||||
|
||||
/**
|
||||
* Gets the first valid location for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
Location getFirstValidTrackPoint(long trackId);
|
||||
|
||||
/**
|
||||
* Gets the last valid location for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
Location getLastValidTrackPoint(long trackId);
|
||||
|
||||
/**
|
||||
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
||||
* @param maxLocations maximum number of locations to return. -1 for no limit
|
||||
* @param descending true to sort the result in descending order (latest location first)
|
||||
*/
|
||||
Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending);
|
||||
|
||||
/**
|
||||
* Creates a new read-only iterator over a given track's points.
|
||||
* It provides a lightweight way of iterating over long tracks without failing due to the underlying cursor limitations.
|
||||
* Since it's a read-only iterator, {@link Iterator#remove()} always throws {@link UnsupportedOperationException}.
|
||||
* Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link LocationFactory#createLocation()} and populates it with information retrieved from the record.
|
||||
* When done with iteration, {@link LocationIterator#close()} must be called.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
||||
* @param descending true to sort the result in descending order (latest location first)
|
||||
* @param locationFactory the location factory
|
||||
*/
|
||||
LocationIterator getTrackPointLocationIterator(long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory);
|
||||
|
||||
/**
|
||||
* Inserts a track point.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackId the track id
|
||||
* @return the content provider URI of the inserted track point
|
||||
*/
|
||||
Uri insertTrackPoint(Location location, long trackId);
|
||||
|
||||
/**
|
||||
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
|
||||
*/
|
||||
interface LocationIterator extends Iterator<Location>, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Gets the most recently retrieved track point id by {@link #next()}.
|
||||
*/
|
||||
long getLocationId();
|
||||
|
||||
/**
|
||||
* Closes the iterator.
|
||||
*/
|
||||
void close();
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory for creating new {@link Location}.
|
||||
*/
|
||||
interface LocationFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Location}.
|
||||
* An implementation can create new instances or reuse existing instances for optimization.
|
||||
*/
|
||||
Location createLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory which can produce instances of {@link ContentProviderUtils}, and can be overridden for testing.
|
||||
*/
|
||||
class Factory {
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link ContentProviderUtils}.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static ContentProviderUtils get(Context context) {
|
||||
return new ContentProviderUtilsImpl(context.getContentResolver());
|
||||
}
|
||||
}
|
||||
}
|
||||