Compare commits
226 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 8932578b90 | |||
| 631ac9881d | |||
| 78334ed797 | |||
| b06da89654 | |||
| d6c2e78993 | |||
| 67d1ac551d | |||
| dcb6be717d | |||
| f2c04eec1a | |||
| a08311cf43 | |||
| 1e28f3aa86 | |||
| 426af41f2c | |||
| fd08a219f9 | |||
| 25b6aec0e9 | |||
| 3e40209d15 | |||
| 54ff94f905 | |||
| 7a4faf6b0d | |||
| 8922b00a5f | |||
| 702efa8693 | |||
| e7a8c897a3 | |||
| c59d1d87bd | |||
| a706efb8b3 | |||
| e391e83a00 | |||
| 2940bf20d8 | |||
| 081db5a58b | |||
| b34805272f | |||
| 98443dba99 | |||
| dd6e733d8c | |||
| 92d3439c3b | |||
| bd155cd600 | |||
| e14e53c0ae | |||
| 77a6a63174 | |||
| 40f2463f09 | |||
| e5d1048c8d | |||
| bf3b529fb2 | |||
| d0fde46027 | |||
| 0f348dff10 | |||
| d3f5080e73 | |||
| a453c8ec1e | |||
| 2a60aa8f59 | |||
| 41e7870509 | |||
| 57f66b77ab | |||
| 53572406e9 | |||
| a288255ab1 | |||
| 353c958ae0 | |||
| bc5ab0d7e5 | |||
| 9d915b5640 | |||
| 3aaee270eb | |||
| d53a7156e2 | |||
| 3af24ea3d0 | |||
| 5483212b06 | |||
| 419279fe5e | |||
| 501e899b27 | |||
| 3bb9edb009 | |||
| e52b1188a3 | |||
| 9e877e4762 | |||
| 5711757110 | |||
| e1c33dad43 | |||
| 4b2400774c | |||
| afd4a59622 | |||
| bdce437470 | |||
| 33479cda11 | |||
| 385cee3dc7 | |||
| d0bbd7655b | |||
| a83b8f62fe | |||
| 0cd9176703 | |||
| d8e526923d | |||
| 3a6f417662 | |||
| 7d807a7f11 | |||
| e9d5718495 | |||
| 0dd7f74a11 |
@@ -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
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report issues of OpenTracks features
|
||||
title: ''
|
||||
labels: potential bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
If applicable:
|
||||
* add screenshots
|
||||
* add logcat output
|
||||
|
||||
**Technical information**
|
||||
- Device: [e.g. Nexus 7 (2013)]
|
||||
- OS: [e.g. LineageOS 15.1]
|
||||
- Version [e.g. v3.2.4]
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for OpenTracks
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -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: [21, 29]
|
||||
target: [default]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
arch: x86_64
|
||||
script: ./gradlew connectedCheck
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RenderSettings">
|
||||
<option name="showDecorations" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,12 +1,46 @@
|
||||
#See https://docs.travis-ci.com/user/languages/android/
|
||||
#See https://github.com/ankidroid/Anki-Android/blob/master/.travis.yml
|
||||
language: android
|
||||
dist: trusty
|
||||
|
||||
env:
|
||||
global:
|
||||
- ADB_INSTALL_TIMEOUT=8
|
||||
- ABI=armeabi-v7a
|
||||
- EMU_FLAVOR=default
|
||||
# PATH order is incredibly important. e.g. the 'emulator' script exists in more than one place!
|
||||
- ANDROID_HOME=/usr/local/android-sdk
|
||||
- TOOLS=${ANDROID_HOME}/tools
|
||||
- PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH}
|
||||
matrix:
|
||||
- API=27 ABI=x86_64
|
||||
|
||||
android:
|
||||
components:
|
||||
# installing tools to start, then use `sdkmanager` below to get the rest
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- android-29
|
||||
|
||||
# Emulator Management: Create, Start and Wait
|
||||
install:
|
||||
- echo y | sdkmanager "platform-tools"
|
||||
- echo y | sdkmanager --channel=4 "emulator"
|
||||
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
- android-wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
|
||||
script:
|
||||
- ./gradlew build
|
||||
#TODO: When all tests are passing, CI should also execute them.
|
||||
#- ./gradlew connectedCheck
|
||||
- ./gradlew 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,12 +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. Push commits
|
||||
7. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||
8. Push the tagged commit to public repository
|
||||
@@ -2,17 +2,38 @@
|
||||
|
||||
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
||||
|
||||
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
|
||||
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="60">
|
||||
</a>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Free</th>
|
||||
<th>Paid</th>
|
||||
<th>Donations</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
|
||||
<img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="60" align="middle">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
|
||||
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://liberapay.com/dennis.guse/donate">
|
||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Features:
|
||||
* __Tracking:__ track your sport and outdoor activities
|
||||
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
||||
* __Heart rate:__ supports bluetooth LE sensors
|
||||
* __Heart rate:__ supports Bluetooth LE sensors
|
||||
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
||||
* __No Internet access:__ Internet is not used
|
||||
* __Voice annoucements__
|
||||
* __Voice announcements__
|
||||
* __No advertising__
|
||||
* __Privacy:__
|
||||
* _does not contain any in-app analytics_
|
||||
@@ -26,9 +47,17 @@ _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>
|
||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
||||
</div>
|
||||
|
||||
## Project history
|
||||
|
||||
|
||||
@@ -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 `AndroidManifest.xml` (used by FDroid)
|
||||
4. Create changelog (`_version code_.txt`)
|
||||
5. Create commit with all changes
|
||||
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||
7. Push commits and tags to public repository
|
||||
8. Add changelog to tag (Github releases)
|
||||
|
||||
## F-Droid
|
||||
Application id: de.dennisguse.opentracks
|
||||
|
||||
It if it properly tagged F-Droid takes care of building the app and publishing.
|
||||
Usually, it takes two days before the update is published (build, signed, and published).
|
||||
|
||||
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
|
||||
|
||||
## Google Play Store
|
||||
Application id: de.dennisguse.opentracks.playstore
|
||||
|
||||
Requirement: `gem install fastlane`
|
||||
|
||||
1. Build signed apk
|
||||
(requires keystore)
|
||||
2. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
|
||||
3. Update store meta data (e.g., description and screenshots)
|
||||
`fastlane supply --version_code VERSION_CODE --skip_upload_apk`
|
||||
(use the version code from the manifest)
|
||||
(requires API key)
|
||||
|
||||
## Translations (localization)
|
||||
|
||||
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,14 @@ def getVersionName = { ->
|
||||
try {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'describe', '--tags', '--dirty'
|
||||
commandLine 'git', 'describe', '--tags'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return stdout.toString().trim()
|
||||
exec {
|
||||
commandLine 'git', 'log', '--format="%h"', '-n 1'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return stdout.toString().replace('\n', '-').replace('"', '').trim()
|
||||
}
|
||||
catch (ignored) {
|
||||
return null
|
||||
@@ -50,6 +54,11 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
manifestPlaceholders = [
|
||||
applicationLogo: "@drawable/ic_logo_color_24dp",
|
||||
applicationName: "@string/app_name"
|
||||
]
|
||||
|
||||
//Useful for debugging.
|
||||
//versionCode getVersionCode()
|
||||
|
||||
@@ -65,25 +74,31 @@ 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 ->
|
||||
variant.resValue "string", "applicationId", variant.applicationId
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable "AppCompatResource" // TODO Disable complaint to use androidx's SearchView as it is null.
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
@@ -95,4 +110,5 @@ dependencies {
|
||||
|
||||
testImplementation 'androidx.test:core:1.2.0'
|
||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
||||
|
||||
Funktionen & Features:
|
||||
|
||||
@@ -1 +1 @@
|
||||
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
v3.2.3: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Sharing: use Android Sharesheet
|
||||
- Sharing: share multiple tracks as individual files
|
||||
- Use Android ShareSheet
|
||||
- Heart rate sensor (Bluetooth LE): automatic reconnect
|
||||
|
||||
Bugfix:
|
||||
- Heart rate sensor (Bluetooth LE): disconnect properly
|
||||
- Heart rate wasn't always shown while recording
|
||||
|
||||
Developer:
|
||||
- Enabled TravisCI instrumentation tests
|
||||
@@ -0,0 +1,5 @@
|
||||
v3.2.4: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Settings activity crashes
|
||||
Removed possibility to change voice/split frequency from the context menu
|
||||
@@ -0,0 +1,15 @@
|
||||
v3.2.5: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Disable activity change
|
||||
- Add permission for Android 10: recording location data
|
||||
|
||||
Bugfix:
|
||||
- Fix reset settings
|
||||
|
||||
Developer:
|
||||
- Preferences
|
||||
- if Settings cannot be loaded reset all preferences
|
||||
- moved access to preferences to PreferencesUtil
|
||||
- moved default of preferences from code to xml
|
||||
- use SummaryProvider
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.2.6: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Heart rate monitor: scan for available devices
|
||||
- Remove shortcut to Bluetooth settings
|
||||
|
||||
Developer:
|
||||
- Preferences: moved default of preferences from code to xml
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.2.7: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Notification show GPS accuracy
|
||||
- Show heads-up notification if GPS accuracy drops beyond threshold
|
||||
- Always show chart by time as well as by distance
|
||||
- Added help (in Settings)
|
||||
|
||||
Bugfix:
|
||||
- TrackDetailActivity: disabled non-working landscape mode (temporary)
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.2.8: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Chart: zoom via gestures
|
||||
|
||||
Bugfixes:
|
||||
- Export via KMZ did not export picture filenames correctly
|
||||
- Sharing multiple tracks did not include KMZ files
|
||||
- MarkerDetailActivity's UI was not shown
|
||||
- Settings crashed if preference of heart rate was not initialized
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.2.9: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Option to keep screen on while recording
|
||||
- Markers: removed split markers completely
|
||||
- Voice announcement now work without READ_PHONE_STATE permission
|
||||
|
||||
Bugfixes:
|
||||
- Statistics: heart rate was not shown
|
||||
- Statistics: time was briefly reset to 0s
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.2.10: OpenTracks
|
||||
|
||||
Changes:
|
||||
- ChartView is not configurable anymore
|
||||
- After reboot: removed automatic restart of an unfinished recording
|
||||
|
||||
Bugfix:
|
||||
- Speech announcements use MAY_DUCK (again)
|
||||
- ChartView shows markers (again)
|
||||
- Fixed landscape layout of TrackListActivity
|
||||
- Request legacy file API behavior (necessary for Android 10)
|
||||
@@ -0,0 +1,12 @@
|
||||
v3.2.11: OpenTracks
|
||||
|
||||
Bug:
|
||||
- Max speed is identical to avg speed
|
||||
|
||||
Bugfix:
|
||||
- Crash on heart rate sensor selection
|
||||
|
||||
Developer:
|
||||
- Replaced TabHost by TabsLayout
|
||||
- Removed special first waypoint
|
||||
- Removed duplicate translations
|
||||
@@ -0,0 +1,13 @@
|
||||
v3.3.0: OpenTracks
|
||||
|
||||
Breaking changes:
|
||||
- Pictures are now stored in app-private storage (do not show up other picture apps)
|
||||
NOTE: Pictures taking with previous version cannot be shown
|
||||
- Localization for Spanish (Latin America) only works on Android 7.0 or later
|
||||
|
||||
Changes:
|
||||
- Import/Export via Storage Access Framework (avoids READ/WRITE_EXTERNAL_STORAGE permission)
|
||||
|
||||
Developer:
|
||||
- Cleaned internal data structures
|
||||
- Use separate icons/app names for debug version
|
||||
@@ -0,0 +1,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
|
||||
|
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,8 @@
|
||||
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
||||
|
||||
Funciones:
|
||||
* Tracker deportivo: haz un seguimiento de tus actividades al aire libre
|
||||
* Marca lugares de interés, incluso agrega fotos a los marcadores
|
||||
* Pulsómetro (compatible con dispositivos Bluetooth LE)
|
||||
* Exporta tus rutas como KML o GPX
|
||||
* Completamente offline, no necesitas internet
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks
|
||||
@@ -0,0 +1,9 @@
|
||||
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
|
||||
|
||||
Fonctionnalités :
|
||||
|
||||
* Suivi d'activité : enregistre vos activités sportives et de plein air
|
||||
* Photos et repères : ajout de photos et de repères pendant l'enregistrement de l'activité
|
||||
* Rythme cardiaque : support des capteurs Bluetooth LE
|
||||
* Export : export des traces au format KML ou GPX
|
||||
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
|
||||
|
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,8 +18,8 @@ package de.dennisguse.opentracks;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
|
||||
/**
|
||||
@@ -60,16 +60,16 @@ public class TrackStubUtils {
|
||||
*
|
||||
* @return a track stub.
|
||||
*/
|
||||
public static SensorDataSetLocation createSensorDataSetLocation() {
|
||||
public static TrackPoint createSensorDataSetLocation() {
|
||||
return createSensorDataSetLocation(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SensorDataSetLocation} stub with specified values.
|
||||
* Creates a {@link TrackPoint} stub with specified values.
|
||||
*
|
||||
* @return a SensorDataSetLocation stub.
|
||||
*/
|
||||
private static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, double altitude) {
|
||||
private static TrackPoint createSensorDataSetLocation(double latitude, double longitude, double altitude) {
|
||||
// Initial Location
|
||||
Location loc = new Location(LOCATION_PROVIDER);
|
||||
loc.setLatitude(latitude);
|
||||
@@ -81,6 +81,6 @@ public class TrackStubUtils {
|
||||
loc.setBearing(INITIAL_BEARING);
|
||||
SensorDataSet sd = new SensorDataSet(Float.NaN, Float.NaN);
|
||||
|
||||
return new SensorDataSetLocation(loc, sd);
|
||||
return new TrackPoint(loc, sd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks;
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -23,6 +23,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
/**
|
||||
* Tests {@link ChartValueSeries}.
|
||||
*
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.content.CustomContentProvider.DatabaseHelper;
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider}.
|
||||
*
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
public class CustomContentProviderTest {
|
||||
|
||||
private static final String DATABASE_NAME = "test.db";
|
||||
|
||||
private SQLiteDatabase db;
|
||||
private CustomContentProvider customContentProvider;
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
|
||||
customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider.DatabaseHelper#onCreate(SQLiteDatabase)}.
|
||||
*/
|
||||
@Test
|
||||
public void testDatabaseHelper_OnCreate() {
|
||||
Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME));
|
||||
Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME));
|
||||
Assert.assertTrue(hasTable(WaypointsColumns.TABLE_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
|
||||
*/
|
||||
@Test
|
||||
public void testOnCreate() {
|
||||
Assert.assertTrue(customContentProvider.onCreate(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider#getType(Uri)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetType() {
|
||||
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
|
||||
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI));
|
||||
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table, containing one column.
|
||||
*
|
||||
* @param table the table name
|
||||
*/
|
||||
private void createTable(String table) {
|
||||
db.execSQL("CREATE TABLE " + table + " (test INTEGER)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a table in database.
|
||||
*
|
||||
* @param table the table name
|
||||
*/
|
||||
private void dropTable(String table) {
|
||||
db.execSQL("Drop TABLE " + table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the table exists.
|
||||
*
|
||||
* @param table the table name
|
||||
*/
|
||||
private boolean hasTable(String table) {
|
||||
try {
|
||||
db.rawQuery("select count(*) from " + table, null);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the column in the table exists.
|
||||
*
|
||||
* @param table the table name
|
||||
* @param column the column name
|
||||
*/
|
||||
private boolean hasColumn(String table, String column) {
|
||||
try {
|
||||
db.execSQL("SElECT count(*) from " + table + " order by " + column);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("no such column")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up upgrade.
|
||||
*
|
||||
* @param oldVersion thd old database version
|
||||
*/
|
||||
private void setupUpgrade(int oldVersion) {
|
||||
dropTable(TracksColumns.TABLE_NAME);
|
||||
dropTable(TrackPointsColumns.TABLE_NAME);
|
||||
dropTable(WaypointsColumns.TABLE_NAME);
|
||||
createTable(TracksColumns.TABLE_NAME);
|
||||
createTable(TrackPointsColumns.TABLE_NAME);
|
||||
createTable(WaypointsColumns.TABLE_NAME);
|
||||
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
databaseHelper.onUpgrade(db, oldVersion, CustomContentProvider.DATABASE_VERSION);
|
||||
}
|
||||
}
|
||||
@@ -27,29 +27,30 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link DescriptionGeneratorImpl}.
|
||||
* Tests for {@link DescriptionGenerator}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DescriptionGeneratorImplTest {
|
||||
public class DescriptionGeneratorTest {
|
||||
|
||||
private static final long START_TIME = 1288721514000L;
|
||||
private DescriptionGeneratorImpl descriptionGenerator;
|
||||
private DescriptionGenerator descriptionGenerator;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
descriptionGenerator = new DescriptionGeneratorImpl(ApplicationProvider.getApplicationContext());
|
||||
descriptionGenerator = new DescriptionGenerator(ApplicationProvider.getApplicationContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, boolean)}.
|
||||
* Tests {@link DescriptionGenerator#generateTrackDescription(Track, boolean)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGenerateTrackDescription() {
|
||||
@@ -68,7 +69,7 @@ public class DescriptionGeneratorImplTest {
|
||||
track.setTripStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
String expected = //"Created by"
|
||||
"<a href='https://github.com/dennisguse/opentracks'>OpenTracks</a><p>"
|
||||
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
|
||||
+ "Name: -<br>"
|
||||
+ "Activity type: hiking<br>"
|
||||
+ "Description: -<br>"
|
||||
@@ -91,44 +92,9 @@ public class DescriptionGeneratorImplTest {
|
||||
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#generateWaypointDescription(TripStatistics)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGenerateWaypointDescription() {
|
||||
Waypoint waypoint = new Waypoint();
|
||||
TripStatistics stats = new TripStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setMaxElevation(550);
|
||||
stats.setMinElevation(-500);
|
||||
stats.setTotalElevationGain(6000);
|
||||
stats.setMaxGrade(0.42);
|
||||
stats.setMinGrade(0.11);
|
||||
stats.setStartTime(START_TIME);
|
||||
waypoint.setTripStatistics(stats);
|
||||
String expected = "Total distance: 20.00 km (12.4 mi)\n"
|
||||
+ "Total time: 10:00\n"
|
||||
+ "Moving time: 05:00\n"
|
||||
+ "Average speed: 120.00 km/h (74.6 mi/h)\n"
|
||||
+ "Average moving speed: 240.00 km/h (149.1 mi/h)\n"
|
||||
+ "Max speed: 360.00 km/h (223.7 mi/h)\n"
|
||||
+ "Average pace: 0:30 min/km (0:48 min/mi)\n"
|
||||
+ "Average moving pace: 0:15 min/km (0:24 min/mi)\n"
|
||||
+ "Fastest pace: 0:10 min/km (0:16 min/mi)\n"
|
||||
+ "Max elevation: 550 m (1804 ft)\n"
|
||||
+ "Min elevation: -500 m (-1640 ft)\n"
|
||||
+ "Elevation gain: 6000 m (19685 ft)\n"
|
||||
+ "Max grade: 42 %\n"
|
||||
+ "Min grade: 11 %\n"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "\n";
|
||||
Assert.assertEquals(expected, descriptionGenerator.generateWaypointDescription(waypoint.getTripStatistics()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteDistance() {
|
||||
@@ -138,7 +104,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeTime(long, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteTime() {
|
||||
@@ -148,7 +114,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteSpeed() {
|
||||
@@ -158,7 +124,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeElevation(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteElevation() {
|
||||
@@ -168,7 +134,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWritePace() {
|
||||
@@ -178,7 +144,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade() {
|
||||
@@ -188,7 +154,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with a NaN.
|
||||
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with a NaN.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade_nan() {
|
||||
@@ -198,7 +164,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with an infinite number.
|
||||
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with an infinite number.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade_infinite() {
|
||||
@@ -36,6 +36,9 @@ import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
|
||||
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
/**
|
||||
@@ -58,7 +61,7 @@ public class SearchEngineTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
||||
providerUtils = new ContentProviderUtils(context);
|
||||
engine = new SearchEngine(providerUtils);
|
||||
}
|
||||
|
||||
@@ -80,10 +83,6 @@ public class SearchEngineTest {
|
||||
stats.setStopTime(NOW);
|
||||
}
|
||||
|
||||
int latitude = (int) ((HERE.getLatitude() + distance) * 1E6);
|
||||
int longitude = (int) ((HERE.getLongitude() + distance) * 1E6);
|
||||
stats.setBounds(latitude, longitude, latitude, longitude);
|
||||
|
||||
Uri uri = providerUtils.insertTrack(track);
|
||||
return ContentUris.parseId(uri);
|
||||
}
|
||||
@@ -209,20 +208,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.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
public class TestDataUtil {
|
||||
|
||||
public static final double INITIAL_LATITUDE = 37.0;
|
||||
public static final double INITIAL_LONGITUDE = -57.0;
|
||||
public static final double ALTITUDE_INTERVAL = 2.5;
|
||||
|
||||
/**
|
||||
* Simulates a track which is used for testing.
|
||||
*
|
||||
* @param id the id of the track
|
||||
* @param numPoints the location number in the track
|
||||
* @return the simulated track
|
||||
*/
|
||||
public static Track getTrack(long id, int numPoints) {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id);
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
track.addLocation(createLocation(i));
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*
|
||||
* @param i the index to set the value of location.
|
||||
* @return created location
|
||||
*/
|
||||
public static Location createLocation(int i) {
|
||||
Location loc = new Location("test");
|
||||
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
loc.setAccuracy((float) i / 100.0f);
|
||||
loc.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
loc.setTime(i + 1);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
@@ -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 Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
|
||||
*/
|
||||
@Test
|
||||
public void testOnCreate() {
|
||||
Assert.assertTrue(customContentProvider.onCreate(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider#getType(Uri)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetType() {
|
||||
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
|
||||
Assert.assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build()));
|
||||
|
||||
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
|
||||
Assert.assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
|
||||
|
||||
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
|
||||
Assert.assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks.content;
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
@@ -35,32 +35,32 @@ 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.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.TripStatistics;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* A unit test for {@link ContentProviderUtilsImpl}.
|
||||
* A unit test for {@link ContentProviderUtils}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CustomContentProviderUtilsImplTest {
|
||||
public class CustomContentProviderUtilsTest {
|
||||
private static final String NAME_PREFIX = "test name";
|
||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||
private static final String TEST_DESC = "Test Desc!";
|
||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||
private static final double INITIAL_LATITUDE = 37.0;
|
||||
private static final double INITIAL_LONGITUDE = -57.0;
|
||||
private static final double ALTITUDE_INTERVAL = 2.5;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils providerUtils;
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@Mock
|
||||
private Cursor cursorMock;
|
||||
@@ -70,22 +70,22 @@ public class CustomContentProviderUtilsImplTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
||||
providerUtils.deleteAllTracks(context);
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noPoints() {
|
||||
testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 0, 1, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_customFactory() {
|
||||
final Location location = new Location("test_location");
|
||||
final TrackPoint location = new TrackPoint("test_location");
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
testIterator(1, 15, 4, false, new LocationFactory() {
|
||||
testIterator(1, 15, 4, false, new TrackPointFactory() {
|
||||
@Override
|
||||
public Location createLocation() {
|
||||
public TrackPoint createLocation() {
|
||||
counter.incrementAndGet();
|
||||
return location;
|
||||
}
|
||||
@@ -106,45 +106,45 @@ public class CustomContentProviderUtilsImplTest {
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchAscending() {
|
||||
testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 100, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchDescending() {
|
||||
testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 100, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchAscending() {
|
||||
testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 11, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchDescending() {
|
||||
testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 11, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 20000, 2000, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
|
||||
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, TrackPointFactory trackPointFactory) {
|
||||
long lastPointId = initializeTrack(trackId, numPoints);
|
||||
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
|
||||
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending, trackPointFactory)) {
|
||||
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());
|
||||
: lastPointId - numPoints + locations.size(), it.getTrackPointId());
|
||||
}
|
||||
Assert.assertEquals(numPoints, locations.size());
|
||||
}
|
||||
@@ -155,9 +155,8 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id);
|
||||
track.setNumberOfPoints(numPoints);
|
||||
providerUtils.insertTrack(track);
|
||||
track = providerUtils.getTrack(id);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
track = contentProviderUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
|
||||
Location[] locations = new Location[numPoints];
|
||||
@@ -169,28 +168,27 @@ public class CustomContentProviderUtilsImplTest {
|
||||
loc.setAltitude(i * 2.5);
|
||||
locations[i] = loc;
|
||||
}
|
||||
providerUtils.bulkInsertTrackPoint(locations, numPoints, id);
|
||||
contentProviderUtils.bulkInsertTrackPoint(locations, numPoints, id);
|
||||
|
||||
// Load all inserted locations.
|
||||
long lastPointId = -1;
|
||||
int counter = 0;
|
||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
lastPointId = it.getLocationId();
|
||||
lastPointId = it.getTrackPointId();
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
||||
Assert.assertEquals(numPoints, track.getNumberOfPoints());
|
||||
Assert.assertEquals(numPoints, counter);
|
||||
|
||||
return lastPointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}.
|
||||
* Tests the method {@link ContentProviderUtils#createTrack(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTrack() {
|
||||
@@ -211,134 +209,132 @@ public class CustomContentProviderUtilsImplTest {
|
||||
String name = NAME_PREFIX + trackId;
|
||||
when(cursorMock.getString(columnIndex++)).thenReturn(name);
|
||||
|
||||
Track track = providerUtils.createTrack(cursorMock);
|
||||
Track track = contentProviderUtils.createTrack(cursorMock);
|
||||
Assert.assertEquals(trackId, track.getId());
|
||||
Assert.assertEquals(name, track.getName());
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)}
|
||||
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteAllTracks() {
|
||||
// Insert track, points and waypoint at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
Waypoint waypoint = new Waypoint();
|
||||
providerUtils.insertWaypoint(waypoint);
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(1, tracksCursor.getCount());
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(10, tracksPointsCursor.getCount());
|
||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(1, waypointCursor.getCount());
|
||||
// Delete all.
|
||||
providerUtils.deleteAllTracks(context);
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
// Check whether all have been deleted.
|
||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(0, tracksCursor.getCount());
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(0, tracksPointsCursor.getCount());
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(0, waypointCursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}.
|
||||
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteTrack() {
|
||||
// Insert three tracks, points of two tracks and way point of one track.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
|
||||
providerUtils.insertTrack(track);
|
||||
insertTrackWithLocations(getTrack(trackId + 1, 10));
|
||||
insertTrackWithLocations(getTrack(trackId + 2, 10));
|
||||
contentProviderUtils.insertTrack(track);
|
||||
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 1, 10));
|
||||
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 2, 10));
|
||||
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setTrackId(trackId);
|
||||
providerUtils.insertWaypoint(waypoint);
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(3, tracksCursor.getCount());
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(1, waypointCursor.getCount());
|
||||
// Delete one track.
|
||||
providerUtils.deleteTrack(context, trackId);
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
// Check whether all data of a track has been deleted.
|
||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(2, tracksCursor.getCount());
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(0, waypointCursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getAllTracks()}
|
||||
* Tests the method {@link ContentProviderUtils#getAllTracks()}
|
||||
*/
|
||||
@Test
|
||||
public void testGetAllTracks() {
|
||||
int initialTrackNumber = providerUtils.getAllTracks().size();
|
||||
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
|
||||
long trackId = System.currentTimeMillis();
|
||||
providerUtils.insertTrack(getTrack(trackId, 0));
|
||||
List<Track> allTracks = providerUtils.getAllTracks();
|
||||
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
|
||||
List<Track> allTracks = contentProviderUtils.getAllTracks();
|
||||
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
|
||||
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getLastTrack()}
|
||||
* Tests the method {@link ContentProviderUtils#getLastTrack()}
|
||||
*/
|
||||
@Test
|
||||
public void testGetLastTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
providerUtils.insertTrack(getTrack(trackId, 0));
|
||||
Assert.assertEquals(trackId, providerUtils.getLastTrack().getId());
|
||||
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
|
||||
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrack(long)}
|
||||
* Tests the method {@link ContentProviderUtils#getTrack(long)}
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
providerUtils.insertTrack(getTrack(trackId, 0));
|
||||
Assert.assertNotNull(providerUtils.getTrack(trackId));
|
||||
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
|
||||
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)}
|
||||
* Tests the method {@link ContentProviderUtils#updateTrack(Track)}
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateTrack() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 0);
|
||||
Track track = TestDataUtil.getTrack(trackId, 0);
|
||||
String nameOld = "name1";
|
||||
String nameNew = "name2";
|
||||
track.setName(nameOld);
|
||||
providerUtils.insertTrack(track);
|
||||
Assert.assertEquals(nameOld, providerUtils.getTrack(trackId).getName());
|
||||
contentProviderUtils.insertTrack(track);
|
||||
Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
|
||||
track.setName(nameNew);
|
||||
providerUtils.updateTrack(track);
|
||||
Assert.assertEquals(nameNew, providerUtils.getTrack(trackId).getName());
|
||||
contentProviderUtils.updateTrack(track);
|
||||
Assert.assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}.
|
||||
* Tests the method {@link ContentProviderUtils#createContentValues(Waypoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateContentValues_waypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
// Bottom
|
||||
long startTime = 1000L;
|
||||
// AverageSpeed
|
||||
@@ -355,12 +351,13 @@ public class CustomContentProviderUtilsImplTest {
|
||||
statistics.setMinElevation(1200.0);
|
||||
statistics.setMaxGrade(15.0);
|
||||
statistics.setMinGrade(minGrade);
|
||||
statistics.setBounds(-10000, 20000, 30000, -40000);
|
||||
|
||||
track.setTripStatistics(statistics);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
waypoint.setType(WaypointType.STATISTICS);
|
||||
waypoint.setTripStatistics(statistics);
|
||||
|
||||
Location location = new Location("test");
|
||||
location.setLatitude(22);
|
||||
@@ -368,9 +365,9 @@ public class CustomContentProviderUtilsImplTest {
|
||||
location.setAccuracy((float) 1 / 100.0f);
|
||||
location.setAltitude(2.5);
|
||||
waypoint.setLocation(location);
|
||||
providerUtils.insertWaypoint(waypoint);
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
||||
|
||||
long waypointId = System.currentTimeMillis();
|
||||
waypoint.setId(waypointId);
|
||||
@@ -378,12 +375,10 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
|
||||
Assert.assertEquals(22 * 1000000, contentValues.get(WaypointsColumns.LONGITUDE));
|
||||
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
|
||||
Assert.assertEquals(startTime, contentValues.get(WaypointsColumns.STARTTIME));
|
||||
Assert.assertEquals(minGrade, contentValues.get(WaypointsColumns.MINGRADE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}.
|
||||
* Tests the method {@link ContentProviderUtils#createWaypoint(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateWaypoint() {
|
||||
@@ -410,59 +405,44 @@ public class CustomContentProviderUtilsImplTest {
|
||||
long trackId = 11L;
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
|
||||
Waypoint waypoint = providerUtils.createWaypoint(cursorMock);
|
||||
Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
|
||||
Assert.assertEquals(id, waypoint.getId());
|
||||
Assert.assertEquals(name, waypoint.getName());
|
||||
Assert.assertEquals(trackId, waypoint.getTrackId());
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||
* {@link ContentProviderUtils#deleteWaypoint(long)}
|
||||
* when there is only one waypoint in the track.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteWaypoint_onlyOneWayPoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setDescription(TEST_DESC);
|
||||
waypoint1.setTrackId(trackId);
|
||||
waypoint1.setType(WaypointType.STATISTICS);
|
||||
providerUtils.insertWaypoint(waypoint1);
|
||||
contentProviderUtils.insertWaypoint(waypoint1);
|
||||
|
||||
// Delete
|
||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||
contentProviderUtils.deleteWaypoint(1);
|
||||
|
||||
@Override
|
||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateTrackDescription(Track aTrack, boolean html) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
|
||||
|
||||
Assert.assertNull(providerUtils.getWaypoint(1));
|
||||
Assert.assertNull(contentProviderUtils.getWaypoint(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||
* {@link ContentProviderUtils#deleteWaypoint(long)}
|
||||
* when there is more than one waypoint in the track.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteWaypoint_hasNextWayPoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
|
||||
TripStatistics statistics = new TripStatistics();
|
||||
statistics.setStartTime(1000L);
|
||||
@@ -476,184 +456,113 @@ public class CustomContentProviderUtilsImplTest {
|
||||
statistics.setMinElevation(1200.0);
|
||||
statistics.setMaxGrade(15.0);
|
||||
statistics.setMinGrade(-25.0);
|
||||
statistics.setBounds(-10000, 20000, 30000, -40000);
|
||||
|
||||
track.setTripStatistics(statistics);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setDescription(Long.toString(trackId));
|
||||
waypoint1.setDescription(MOCK_DESC);
|
||||
waypoint1.setTrackId(trackId);
|
||||
waypoint1.setType(WaypointType.STATISTICS);
|
||||
waypoint1.setTripStatistics(statistics);
|
||||
long waypoint1Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
|
||||
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
||||
|
||||
Waypoint waypoint2 = new Waypoint();
|
||||
waypoint2.setDescription(Long.toString(trackId));
|
||||
waypoint2.setDescription(MOCK_DESC);
|
||||
waypoint2.setTrackId(trackId);
|
||||
waypoint2.setType(WaypointType.STATISTICS);
|
||||
waypoint2.setTripStatistics(statistics);
|
||||
long waypoint2Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
|
||||
|
||||
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
|
||||
|
||||
// Delete
|
||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||
@Override
|
||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
||||
}
|
||||
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||
contentProviderUtils.deleteWaypoint(waypoint1Id);
|
||||
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||
|
||||
@Override
|
||||
public String generateTrackDescription(Track aTrack, boolean html) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Assert.assertNotNull(providerUtils.getWaypoint(waypoint1Id));
|
||||
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
|
||||
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
|
||||
|
||||
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
||||
.getDescription());
|
||||
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetFirstWaypointId() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setTrackId(trackId);
|
||||
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)}.
|
||||
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetNextWaypointNumber() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setType(WaypointType.STATISTICS);
|
||||
waypoint1.setTrackId(trackId);
|
||||
Waypoint waypoint2 = new Waypoint();
|
||||
waypoint2.setType(WaypointType.WAYPOINT);
|
||||
waypoint2.setTrackId(trackId);
|
||||
Waypoint waypoint3 = new Waypoint();
|
||||
waypoint3.setType(WaypointType.STATISTICS);
|
||||
waypoint3.setTrackId(trackId);
|
||||
Waypoint waypoint4 = new Waypoint();
|
||||
waypoint4.setType(WaypointType.WAYPOINT);
|
||||
waypoint4.setTrackId(trackId);
|
||||
providerUtils.insertWaypoint(waypoint1);
|
||||
providerUtils.insertWaypoint(waypoint2);
|
||||
providerUtils.insertWaypoint(waypoint3);
|
||||
providerUtils.insertWaypoint(waypoint4);
|
||||
contentProviderUtils.insertWaypoint(waypoint1);
|
||||
contentProviderUtils.insertWaypoint(waypoint2);
|
||||
contentProviderUtils.insertWaypoint(waypoint3);
|
||||
contentProviderUtils.insertWaypoint(waypoint4);
|
||||
|
||||
Assert.assertEquals(2, providerUtils.getNextWaypointNumber(trackId, WaypointType.STATISTICS));
|
||||
Assert.assertEquals(3, providerUtils.getNextWaypointNumber(trackId, WaypointType.WAYPOINT));
|
||||
Assert.assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}.
|
||||
*/
|
||||
@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)}.
|
||||
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
|
||||
* {@link ContentProviderUtils#getWaypoint(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertAndGetWaypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
waypoint.setTrackId(trackId);
|
||||
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
|
||||
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||
|
||||
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(waypointId).getDescription());
|
||||
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}.
|
||||
* Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateWaypoint() {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
// Insert at first.
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
waypoint.setTrackId(trackId);
|
||||
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
|
||||
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||
|
||||
// Update
|
||||
waypoint = providerUtils.getWaypoint(waypointId);
|
||||
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||
waypoint.setDescription(TEST_DESC_NEW);
|
||||
providerUtils.updateWaypoint(waypoint);
|
||||
contentProviderUtils.updateWaypoint(waypoint);
|
||||
|
||||
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(waypointId).getDescription());
|
||||
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], int, long)}.
|
||||
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(Location[], int, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testBulkInsertTrackPoint() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
|
||||
Assert.assertEquals(20, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
|
||||
Assert.assertEquals(28, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
|
||||
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
|
||||
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}.
|
||||
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTrackPoint() {
|
||||
@@ -704,206 +613,146 @@ public class CustomContentProviderUtilsImplTest {
|
||||
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);
|
||||
Location location = contentProviderUtils.createTrackPoint(cursorMock);
|
||||
Assert.assertEquals(longitude, location.getLongitude(), 0.01);
|
||||
Assert.assertEquals(latitude, location.getLatitude(), 0.01);
|
||||
Assert.assertEquals(time, location.getTime(), 0.01);
|
||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}.
|
||||
* {@link ContentProviderUtils#insertTrackPoint(Location, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertTrackPoint() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
providerUtils.insertTrackPoint(createLocation(22), trackId);
|
||||
Assert.assertEquals(11, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
contentProviderUtils.insertTrackPoint(TestDataUtil.createLocation(22), trackId);
|
||||
Assert.assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetFirstTrackPointId() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
Assert.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)}.
|
||||
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetLastValidTrackPoint() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
Location lastLocation = providerUtils.getLastValidTrackPoint(trackId);
|
||||
Location lastLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
checkLocation(9, lastLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_desc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getLocations().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
||||
Assert.assertEquals(2, cursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_asc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getLocations().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
||||
Assert.assertEquals(2, cursor.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, TrackPointFactory)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_desc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getLocations().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[9];
|
||||
|
||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, TrackPointFactory.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());
|
||||
Assert.assertEquals(startTrackPointId - i, locationIterator.getTrackPointId());
|
||||
checkLocation((trackpointIds.length - 1) - i, location);
|
||||
}
|
||||
Assert.assertFalse(locationIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, TrackPointFactory)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_asc() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
providerUtils.insertTrack(track);
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getLocations().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[0];
|
||||
|
||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, TrackPointFactory.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());
|
||||
Assert.assertEquals(startTrackPointId + i, locationIterator.getTrackPointId());
|
||||
|
||||
checkLocation(i, location);
|
||||
}
|
||||
Assert.assertFalse(locationIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a track which is used for testing.
|
||||
*
|
||||
* @param id the id of the track
|
||||
* @param numPoints the location number in the track
|
||||
* @return the simulated track
|
||||
*/
|
||||
private Track getTrack(long id, int numPoints) {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id);
|
||||
track.setNumberOfPoints(numPoints);
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
track.addLocation(createLocation(i));
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*
|
||||
* @param i the index to set the value of location.
|
||||
* @return created location
|
||||
*/
|
||||
private Location createLocation(int i) {
|
||||
Location loc = new Location("test");
|
||||
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
loc.setAccuracy((float) i / 100.0f);
|
||||
loc.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
return loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the value of a location.
|
||||
*
|
||||
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
||||
* @param i the index of this location which created in the method {@link TestDataUtil#getTrack(long, int)}
|
||||
* @param location the location to be checked
|
||||
*/
|
||||
private void checkLocation(int i, Location location) {
|
||||
Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
|
||||
Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
|
||||
Assert.assertEquals(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
|
||||
Assert.assertEquals(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
|
||||
Assert.assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
|
||||
Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
|
||||
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -912,7 +761,15 @@ public class CustomContentProviderUtilsImplTest {
|
||||
* @param track track to be inserted
|
||||
*/
|
||||
private void insertTrackWithLocations(Track track) {
|
||||
providerUtils.insertTrack(track);
|
||||
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
|
||||
contentProviderUtils.insertTrack(track);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
|
||||
}
|
||||
|
||||
@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 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);
|
||||
}
|
||||
}
|
||||
@@ -17,18 +17,21 @@
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.ChartView;
|
||||
import de.dennisguse.opentracks.TrackStubUtils;
|
||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -44,10 +47,22 @@ public class ChartFragmentTest {
|
||||
|
||||
private ChartFragment chartFragment;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
chartFragment = new ChartFragment();
|
||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext()));
|
||||
boolean chartByDistance = false;
|
||||
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
|
||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||
}
|
||||
|
||||
@@ -56,7 +71,7 @@ public class ChartFragmentTest {
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_sensorIncorrect() {
|
||||
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
@@ -80,7 +95,7 @@ public class ChartFragmentTest {
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_sensorCorrect() {
|
||||
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
|
||||
@@ -107,17 +122,17 @@ public class ChartFragmentTest {
|
||||
// By distance.
|
||||
chartFragment.setChartByDistance(true);
|
||||
// Resets last location and writes first location.
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
|
||||
// The second is a same location, just different time.
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
|
||||
// The third location is a new location, and use metric.
|
||||
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation3.setLatitude(23);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation3);
|
||||
|
||||
@@ -129,7 +144,7 @@ public class ChartFragmentTest {
|
||||
Assert.assertEquals(distance1, point[0], 0.01);
|
||||
|
||||
// The fourth location is a new location, and use metric.
|
||||
SensorDataSetLocation sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation4.setLatitude(24);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation4);
|
||||
|
||||
@@ -151,12 +166,12 @@ public class ChartFragmentTest {
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// The first is a same location, just different time.
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
|
||||
// The second location is a new location, and use imperial.
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation2.setLatitude(23);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
|
||||
@@ -170,7 +185,7 @@ public class ChartFragmentTest {
|
||||
Assert.assertEquals(distance1, point[0], 0.01);
|
||||
|
||||
// The third location is a new location, and use imperial.
|
||||
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation3.setLatitude(24);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation3);
|
||||
|
||||
@@ -190,11 +205,11 @@ public class ChartFragmentTest {
|
||||
public void testFillDataPoint_time() {
|
||||
// By time
|
||||
chartFragment.setChartByDistance(false);
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
long timeSpan = 222;
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals((double) timeSpan, point[0], 0.01);
|
||||
@@ -205,7 +220,7 @@ public class ChartFragmentTest {
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_elevation() {
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* At first, clear old points of elevation, so give true to the second parameter.
|
||||
@@ -217,7 +232,7 @@ public class ChartFragmentTest {
|
||||
/*
|
||||
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
|
||||
*/
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
|
||||
@@ -235,7 +250,7 @@ public class ChartFragmentTest {
|
||||
* At first, clear old points of speed, so give true to the second parameter.
|
||||
* It will not be filled in to the speed buffer.
|
||||
*/
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation1.setSpeed(128.5f);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
@@ -244,7 +259,7 @@ public class ChartFragmentTest {
|
||||
* Tests the logic when both metricUnits and reportSpeed are true.
|
||||
* This location will be filled into speed buffer.
|
||||
*/
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
|
||||
@@ -264,12 +279,12 @@ public class ChartFragmentTest {
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation1.setSpeed(100.0f);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
@@ -290,12 +305,12 @@ public class ChartFragmentTest {
|
||||
chartFragment.setReportSpeed(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation1.setSpeed(100.0f);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
@@ -314,7 +329,7 @@ public class ChartFragmentTest {
|
||||
public void testFillDataPoint_pace_zeroSpeed() {
|
||||
// Setups reportSpeed to false.
|
||||
chartFragment.setReportSpeed(false);
|
||||
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation.setSpeed(0);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1], 0.01);
|
||||
|
||||
@@ -32,11 +32,11 @@ 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.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 +57,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);
|
||||
@@ -101,8 +101,6 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
*/
|
||||
protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint(location != null ? (Location[]) any() : (Location[]) any(), eq(1), eq(trackId))).thenReturn(1);
|
||||
when(contentProviderUtils.getFirstTrackPointId(trackId)).thenReturn(trackPointId);
|
||||
when(contentProviderUtils.getLastTrackPointId(trackId)).thenReturn(trackPointId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +110,7 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
* @param lastTrack true if it is the last track in the gpx
|
||||
* @param trackId the track id
|
||||
*/
|
||||
protected void expectUpdateTrack(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
|
||||
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
|
||||
contentProviderUtils.updateTrack(trackCaptor.capture());
|
||||
|
||||
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
|
||||
@@ -128,7 +126,5 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
if (time != -1L) {
|
||||
Assert.assertEquals(time, track.getTripStatistics().getStartTime());
|
||||
}
|
||||
Assert.assertNotSame(-1, track.getStartId());
|
||||
Assert.assertNotSame(-1, track.getStopId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
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.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
* <p>
|
||||
* TODO: test ignores {@link de.dennisguse.opentracks.stats.TripStatistics} for now.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class ExportImportTest {
|
||||
|
||||
private static final String TAG = ExportImportTest.class.getSimpleName();
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
private static final String TRACK_ICON = "the track icon";
|
||||
private static final String TRACK_CATEGORY = "the category";
|
||||
private static final String TRACK_DESCRIPTION = "the description";
|
||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||
private long importTrackId;
|
||||
private long trackId = System.currentTimeMillis();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
track.setIcon(TRACK_ICON);
|
||||
track.setCategory(TRACK_CATEGORY);
|
||||
track.setDescription(TRACK_DESCRIPTION);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setName("the waypoint " + i);
|
||||
waypoint.setDescription("the waypoint description " + i);
|
||||
waypoint.setCategory("the waypoint category" + i);
|
||||
waypoint.setIcon("the waypoing icon" + i);
|
||||
waypoint.setPhotoUrl("the photo url" + i);
|
||||
waypoint.setTrackId(trackId);
|
||||
waypoint.setLocation(track.getLocations().get(i));
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
waypoints.add(waypoint);
|
||||
}
|
||||
|
||||
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(trackId));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
contentProviderUtils.deleteTrack(context, importTrackId);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_only_track() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_trackdetail() {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(context, outputStream);
|
||||
|
||||
System.out.println(outputStream.toString());
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getLocations(), importedTrack.getLocations());
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. waypoints
|
||||
assertWaypoints();
|
||||
|
||||
//TODO Check absolute time of trackpoints
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_trackdetail_and_sensordata() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_only_track() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata_and_pictures() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx() {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(context, outputStream);
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track trackImported = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(trackImported);
|
||||
assertEquals(track.getLocations(), trackImported.getLocations());
|
||||
assertEquals(track.getCategory(), trackImported.getCategory());
|
||||
assertEquals(track.getDescription(), trackImported.getDescription());
|
||||
assertEquals(track.getName(), trackImported.getName());
|
||||
|
||||
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
|
||||
//assertEquals(track.getIcon(), trackImported.getIcon());
|
||||
|
||||
// 2. waypoints
|
||||
assertWaypoints();
|
||||
|
||||
//TODO Check absolute time of trackpoints
|
||||
}
|
||||
|
||||
private void assertWaypoints() {
|
||||
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(importTrackId));
|
||||
|
||||
List<Waypoint> importedWaypoints = contentProviderUtils.getWaypoints(importTrackId);
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
Waypoint waypoint = waypoints.get(i);
|
||||
Waypoint importedWaypoint = importedWaypoints.get(i);
|
||||
assertEquals(waypoint.getCategory(), importedWaypoint.getCategory());
|
||||
assertEquals(waypoint.getDescription(), importedWaypoint.getDescription());
|
||||
// assertEquals(waypoint.getIcon(), importedWaypoint.getIcon()); // TODO for KML
|
||||
assertEquals(waypoint.getName(), importedWaypoint.getName());
|
||||
assertEquals("", importedWaypoint.getPhotoUrl());
|
||||
|
||||
assertEquals(waypoint.getLocation().getLatitude(), importedWaypoint.getLocation().getLatitude(), 0.001);
|
||||
assertEquals(waypoint.getLocation().getLongitude(), importedWaypoint.getLocation().getLongitude(), 0.001);
|
||||
assertEquals(waypoint.getLocation().getAltitude(), importedWaypoint.getLocation().getAltitude(), 0.001);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,13 +27,13 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -84,8 +84,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
@@ -95,25 +94,28 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
@@ -121,12 +123,18 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
@@ -135,30 +143,33 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegmentsNoTime() {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
|
||||
}
|
||||
|
||||
@@ -197,9 +208,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
|
||||
// For the following, use StubReturn since we don't care whether they are invoked or not.
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
||||
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
||||
|
||||
@@ -27,11 +27,11 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -68,8 +68,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
@@ -77,27 +76,31 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint(new Location[]{location1}, eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
contentProviderUtils.clearTrack(context, TRACK_ID_0);
|
||||
@@ -106,12 +109,18 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
@@ -120,7 +129,6 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,19 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Tests the {@link AdaptiveLocationListenerPolicy}.
|
||||
*
|
||||
* @author youtaol
|
||||
*/
|
||||
public class AdaptiveLocationListenerPolicyTest extends TestCase {
|
||||
@RunWith(JUnit4.class)
|
||||
public class AdaptiveLocationListenerPolicyTest {
|
||||
|
||||
private static final long MIN = 1000;
|
||||
private static final long MAX = 3000;
|
||||
@@ -31,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TrackRecordingServiceNotificationManagerTest {
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private Location locationMock;
|
||||
|
||||
@Mock
|
||||
private NotificationCompat.Builder notificationCompatBuilder;
|
||||
|
||||
@Mock
|
||||
private NotificationManager notificationManager;
|
||||
|
||||
@Test
|
||||
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||
when(locationMock.hasAccuracy()).thenReturn(true);
|
||||
when(locationMock.getAccuracy()).thenReturn(999f);
|
||||
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
|
||||
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
|
||||
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
|
||||
// when
|
||||
subject.updateLocation(context, locationMock, 100);
|
||||
subject.updateLocation(context, locationMock, 100);
|
||||
subject.updateLocation(context, locationMock, 1000);
|
||||
subject.updateLocation(context, locationMock, 100);
|
||||
|
||||
// then
|
||||
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||
verify(notificationCompatBuilder, times(2)).setOnlyAlertOnce(false);
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,12 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -38,19 +36,16 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
@@ -59,7 +54,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
* <p>
|
||||
* ATTENTION: This tests deletes all stored tracks in the database.
|
||||
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
|
||||
* So, if it is executed on a real device, data might be lost.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@@ -72,19 +67,12 @@ public class TrackRecordingServiceTest {
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils providerUtils;
|
||||
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) {
|
||||
Intent startIntent = new Intent();
|
||||
startIntent.setClass(context, TrackRecordingService.class);
|
||||
return startIntent;
|
||||
}
|
||||
|
||||
static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) {
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins);
|
||||
return new Intent(context, TrackRecordingService.class);
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -94,29 +82,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);
|
||||
|
||||
// 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
|
||||
@@ -133,121 +118,16 @@ 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, PreferencesUtils.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);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
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, PreferencesUtils.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.getLong(context, R.string.recording_track_id_key));
|
||||
|
||||
// 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)));
|
||||
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, PreferencesUtils.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);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it was stopped.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
||||
// Insert a dummy track last updated 20 min ago.
|
||||
createDummyTrack(trackId, System.currentTimeMillis() - 20 * 60 * 1000, true);
|
||||
|
||||
// Clear the number of attempts and set the timeout to 10 min.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it has expired.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(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, PreferencesUtils.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);
|
||||
|
||||
//Explicit start service, so `startCommand()` is executed - which would be called by BootReceiver.
|
||||
context.startService(startIntent);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because there were already too many attempts.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_noTracks() throws Exception {
|
||||
List<Track> tracks = providerUtils.getAllTracks();
|
||||
List<Track> tracks = contentProviderUtils.getAllTracks();
|
||||
Assert.assertTrue(tracks.isEmpty());
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Intent startIntent = createStartIntent(context);
|
||||
mServiceRule.startService(startIntent);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
|
||||
|
||||
// Test if we start in no-recording mode by default.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
@@ -258,7 +138,7 @@ public class TrackRecordingServiceTest {
|
||||
public void testRecording_oldTracks() throws Exception {
|
||||
createDummyTrack(trackId, -1L, false);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
@@ -266,11 +146,14 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||
Intent startIntent = createStartIntent(context);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
|
||||
|
||||
// Just set recording track to a bogus value.
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
@@ -278,69 +161,34 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
long trackId = service.getRecordingTrackId();
|
||||
|
||||
long newTrackId = service.startNewTrack();
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||
|
||||
Assert.assertEquals(trackId, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
// Ending the current track when there is no recording should not result in any error.
|
||||
service.endCurrentTrack();
|
||||
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertFalse(PreferencesUtils.isRecording(context));
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertEquals(-1L, waypointId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertFalse(service.isPaused());
|
||||
insertLocation(service);
|
||||
|
||||
long waypointId1 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertNotEquals(-1L, waypointId1);
|
||||
long waypointId2 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertNotEquals(-1L, waypointId2);
|
||||
|
||||
Waypoint wpt = providerUtils.getWaypoint(waypointId1);
|
||||
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
|
||||
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 0), wpt.getName());
|
||||
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
|
||||
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNotNull(wpt.getTripStatistics());
|
||||
// TODO check the rest of the params.
|
||||
|
||||
// TODO: Check waypoint 2.
|
||||
}
|
||||
|
||||
// NOTE: Do not use to create a track that is currently recording.
|
||||
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
||||
Track dummyTrack = new Track();
|
||||
dummyTrack.setId(id);
|
||||
@@ -353,8 +201,8 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
private void addTrack(Track track, boolean isRecording) {
|
||||
Assert.assertTrue(track.getId() >= 0);
|
||||
providerUtils.insertTrack(track);
|
||||
Assert.assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
|
||||
contentProviderUtils.insertTrack(track);
|
||||
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
||||
}
|
||||
@@ -362,7 +210,7 @@ public class TrackRecordingServiceTest {
|
||||
/**
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
|
||||
private void insertLocation(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||
Location location = new Location("gps");
|
||||
location.setLongitude(35.0f);
|
||||
location.setLatitude(45.0f);
|
||||
@@ -378,72 +226,31 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
long waypointId = service.insertWaypoint(null, null, null, null);
|
||||
Assert.assertEquals(-1L, waypointId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
insertLocation(service);
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
long trackId = service.getRecordingTrackId();
|
||||
long waypointId = service.insertWaypoint(null, null, null, null);
|
||||
Assert.assertNotEquals(-1L, waypointId);
|
||||
Waypoint wpt = providerUtils.getWaypoint(waypointId);
|
||||
Waypoint wpt = contentProviderUtils.getWaypoint(waypointId);
|
||||
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
|
||||
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
||||
Assert.assertEquals(WaypointType.WAYPOINT, wpt.getType());
|
||||
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNull(wpt.getTripStatistics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous/waitable broadcast receiver to be used in testing.
|
||||
*/
|
||||
private class BlockingBroadcastReceiver extends BroadcastReceiver {
|
||||
private static final long MAX_WAIT_TIME_MS = 3000;
|
||||
private final List<Intent> receivedIntents = new ArrayList<>();
|
||||
|
||||
public List<Intent> getReceivedIntents() {
|
||||
return receivedIntents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
Log.d("Test", "Got broadcast: " + intent);
|
||||
synchronized (receivedIntents) {
|
||||
receivedIntents.add(intent);
|
||||
receivedIntents.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean waitUntilReceived(int receiveCount) {
|
||||
long deadline = System.currentTimeMillis() + MAX_WAIT_TIME_MS;
|
||||
synchronized (receivedIntents) {
|
||||
while (receivedIntents.size() < receiveCount) {
|
||||
try {
|
||||
// Wait releases synchronized lock until it returns
|
||||
receivedIntents.wait(500);
|
||||
} catch (InterruptedException e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() > deadline) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,9 @@ 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.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
@@ -48,7 +47,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
|
||||
private 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
|
||||
@@ -103,7 +99,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT);
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -125,7 +121,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -139,7 +135,8 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
||||
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -150,31 +147,10 @@ public class TrackRecordingServiceTestLooper {
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, PreferencesUtils.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 {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -188,7 +164,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -202,30 +178,31 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
||||
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testIntegration_completeRecordingSession() throws TimeoutException {
|
||||
List<Track> tracks = providerUtils.getAllTracks();
|
||||
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.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
|
||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
@@ -240,10 +217,8 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setBearing(3.0f);
|
||||
service.insertTrackPoint(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +226,7 @@ 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();
|
||||
|
||||
@@ -1,557 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks.services.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.same;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@link AnnouncementPeriodicTask}.
|
||||
*
|
||||
* WARNING: I'm not responsible if your eyes start bleeding while reading this code.
|
||||
* You have been warned.
|
||||
* It's still better than no test, though.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AnnouncementPeriodicTaskTest {
|
||||
|
||||
// Use something other than our hardcoded value
|
||||
private static final Locale DEFAULT_LOCALE = Locale.KOREAN;
|
||||
private static final String ANNOUNCEMENT = "I can haz cheeseburger?";
|
||||
private Locale oldDefaultLocale;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private AnnouncementPeriodicTask task;
|
||||
@Mock
|
||||
private AnnouncementPeriodicTask mockTask;
|
||||
private ArgumentCaptor<OnInitListener> initListenerCapture = ArgumentCaptor.forClass(OnInitListener.class);
|
||||
private ArgumentCaptor<PhoneStateListener> phoneListenerCapture = ArgumentCaptor.forClass(PhoneStateListener.class);
|
||||
private TextToSpeechDelegate ttsDelegate;
|
||||
@Mock
|
||||
private TextToSpeechInterface tts;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
oldDefaultLocale = Locale.getDefault();
|
||||
Locale.setDefault(DEFAULT_LOCALE);
|
||||
|
||||
// Eww, the effort required just to mock TextToSpeech is insane
|
||||
final AtomicBoolean listenerCalled = new AtomicBoolean();
|
||||
OnInitListener blockingListener = new OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
synchronized (this) {
|
||||
listenerCalled.set(true);
|
||||
notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ttsDelegate = new TextToSpeechDelegate(context, blockingListener);
|
||||
|
||||
// Wait for all async operations done in the constructor to finish.
|
||||
synchronized (blockingListener) {
|
||||
while (!listenerCalled.get()) {
|
||||
// Releases the synchronized lock until we're woken up.
|
||||
blockingListener.wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a partial forwarding mock
|
||||
task = new AnnouncementPeriodicTask(context) {
|
||||
@Override
|
||||
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
|
||||
return mockTask.newTextToSpeech(ctx, onInitListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAnnouncement(TripStatistics stats) {
|
||||
return mockTask.getAnnouncement(stats);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void listenToPhoneState(PhoneStateListener listener, int events) {
|
||||
mockTask.listenToPhoneState(listener, events);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Locale.setDefault(oldDefaultLocale);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart() {
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
Assert.assertNotNull(ttsInitListener);
|
||||
|
||||
ttsInitListener.onInit(TextToSpeech.SUCCESS);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart_notReady() {
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
Assert.assertNotNull(ttsInitListener);
|
||||
|
||||
ttsInitListener.onInit(TextToSpeech.ERROR);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdown() {
|
||||
// First, start
|
||||
doStart();
|
||||
verify(mockTask);
|
||||
reset(mockTask);
|
||||
|
||||
// Then, shut down
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
mockTask.listenToPhoneState(
|
||||
same(phoneListener), eq(PhoneStateListener.LISTEN_NONE));
|
||||
tts.shutdown();
|
||||
task.shutdown();
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun() {
|
||||
// when service data calls
|
||||
TripStatistics stats = new TripStatistics();
|
||||
|
||||
// when announcement building call
|
||||
when(mockTask.getAnnouncement(same(stats))).thenReturn(ANNOUNCEMENT);
|
||||
|
||||
// Put task in "ready" state
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isLanguageAvailable(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
||||
when(tts.setLanguage(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
||||
when(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).thenReturn(TextToSpeech.SUCCESS);
|
||||
when(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) any())).thenReturn(0);
|
||||
|
||||
// Run the announcement
|
||||
task.announce(stats);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_notReady() {
|
||||
// Put task in "not ready" state
|
||||
startTask(TextToSpeech.ERROR);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_duringCall() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(false);
|
||||
|
||||
// Run the announcement
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
|
||||
task.run(null);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_ringWhileSpeaking() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(true);
|
||||
when(tts.stop()).thenReturn(TextToSpeech.SUCCESS);
|
||||
|
||||
// Update the state to ringing - this should stop the current announcement.
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
||||
|
||||
// Run the announcement - this should do nothing.
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_whileRinging() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(false);
|
||||
|
||||
// Run the announcement
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_noService() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_noStats() {
|
||||
// when service data calls
|
||||
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_time_zero() {
|
||||
long time = 0; // 0 seconds
|
||||
Assert.assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time one.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_time_one() {
|
||||
long time = 1000; // 1 second
|
||||
Assert.assertEquals("0 minutes 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
||||
* numbers with the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_singular_has_hour() {
|
||||
long time = (60 * 60 * 1000) + (60 * 1000) + (1000); // 1 hour 1 minute 1 second
|
||||
Assert.assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
||||
* with the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_plural_has_hour() {
|
||||
long time = (2 * 60 * 60 * 1000) + (2 * 60 * 1000) + (2 * 1000); // 2 hours 2 minutes 2 seconds
|
||||
Assert.assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
||||
* numbers without the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_singular_no_hour() {
|
||||
long time = (1 * 60 * 1000) + (1 * 1000); // 1 minute 1 second
|
||||
Assert.assertEquals("1 minute 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
||||
* without the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_plural_no_hour() {
|
||||
long time = (2 * 60 * 1000) + (2 * 1000); // 2 minutes 2 seconds
|
||||
Assert.assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
private void startTask(int state) {
|
||||
reset(tts);
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
ttsInitListener.onInit(state);
|
||||
//TODO
|
||||
reset(tts);
|
||||
//AndroidMock.resetToDefault(tts);
|
||||
}
|
||||
|
||||
private void doStart() {
|
||||
mockTask.listenToPhoneState(phoneListenerCapture.capture(), eq(PhoneStateListener.LISTEN_CALL_STATE));
|
||||
when(mockTask.newTextToSpeech(same(context), initListenerCapture.capture())).thenReturn(ttsDelegate);
|
||||
|
||||
task.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mockable interface that we delegate TTS calls to.
|
||||
*/
|
||||
interface TextToSpeechInterface {
|
||||
int addEarcon(String earcon, String packagename, int resourceId);
|
||||
|
||||
int addEarcon(String earcon, String filename);
|
||||
|
||||
int addSpeech(String text, String packagename, int resourceId);
|
||||
|
||||
int addSpeech(String text, String filename);
|
||||
|
||||
boolean areDefaultsEnforced();
|
||||
|
||||
String getDefaultEngine();
|
||||
|
||||
Locale getLanguage();
|
||||
|
||||
int isLanguageAvailable(Locale loc);
|
||||
|
||||
boolean isSpeaking();
|
||||
|
||||
int playEarcon(String earcon, int queueMode,
|
||||
HashMap<String, String> params);
|
||||
|
||||
int playSilence(long durationInMs, int queueMode, HashMap<String, String> params);
|
||||
|
||||
int setLanguage(Locale loc);
|
||||
|
||||
int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener);
|
||||
|
||||
int setPitch(float pitch);
|
||||
|
||||
int setSpeechRate(float speechRate);
|
||||
|
||||
void shutdown();
|
||||
|
||||
int speak(String text, int queueMode, HashMap<String, String> params);
|
||||
|
||||
int stop();
|
||||
|
||||
int synthesizeToFile(String text, HashMap<String, String> params, String filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass of {@link TextToSpeech} which delegates calls to the interface
|
||||
* above.
|
||||
* The logic here is stupid and the author is ashamed of having to write it
|
||||
* like this, but basically the issue is that TextToSpeech cannot be mocked
|
||||
* without running its constructor, its constructor runs async operations
|
||||
* which call other methods (and then if the methods are part of a mock we'd
|
||||
* have to set a behavior, but we can't 'cause the object hasn't been fully
|
||||
* built yet).
|
||||
* The logic is that calls made during the constructor (when tts is not yet
|
||||
* set) will go up to the original class, but after tts is set we'll forward
|
||||
* them all to the mock.
|
||||
*/
|
||||
private class TextToSpeechDelegate extends TextToSpeech implements TextToSpeechInterface {
|
||||
public TextToSpeechDelegate(Context context, OnInitListener listener) {
|
||||
super(context, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addEarcon(String earcon, String packagename, int resourceId) {
|
||||
if (tts == null) {
|
||||
return super.addEarcon(earcon, packagename, resourceId);
|
||||
}
|
||||
return tts.addEarcon(earcon, packagename, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addEarcon(String earcon, String filename) {
|
||||
if (tts == null) {
|
||||
return super.addEarcon(earcon, filename);
|
||||
}
|
||||
return tts.addEarcon(earcon, filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addSpeech(String text, String packagename, int resourceId) {
|
||||
if (tts == null) {
|
||||
return super.addSpeech(text, packagename, resourceId);
|
||||
}
|
||||
return tts.addSpeech(text, packagename, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addSpeech(String text, String filename) {
|
||||
if (tts == null) {
|
||||
return super.addSpeech(text, filename);
|
||||
}
|
||||
return tts.addSpeech(text, filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLanguage() {
|
||||
if (tts == null) {
|
||||
return super.getLanguage();
|
||||
}
|
||||
return tts.getLanguage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int isLanguageAvailable(Locale loc) {
|
||||
if (tts == null) {
|
||||
return super.isLanguageAvailable(loc);
|
||||
}
|
||||
return tts.isLanguageAvailable(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpeaking() {
|
||||
if (tts == null) {
|
||||
return super.isSpeaking();
|
||||
}
|
||||
return tts.isSpeaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int playEarcon(String earcon, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.playEarcon(earcon, queueMode, params);
|
||||
}
|
||||
return tts.playEarcon(earcon, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int playSilence(long durationInMs, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.playSilence(durationInMs, queueMode, params);
|
||||
}
|
||||
return tts.playSilence(durationInMs, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setLanguage(Locale loc) {
|
||||
if (tts == null) {
|
||||
return super.setLanguage(loc);
|
||||
}
|
||||
return tts.setLanguage(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener) {
|
||||
if (tts == null) {
|
||||
return super.setOnUtteranceCompletedListener(listener);
|
||||
}
|
||||
return tts.setOnUtteranceCompletedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setPitch(float pitch) {
|
||||
if (tts == null) {
|
||||
return super.setPitch(pitch);
|
||||
}
|
||||
return tts.setPitch(pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setSpeechRate(float speechRate) {
|
||||
if (tts == null) {
|
||||
return super.setSpeechRate(speechRate);
|
||||
}
|
||||
return tts.setSpeechRate(speechRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (tts == null) {
|
||||
super.shutdown();
|
||||
return;
|
||||
}
|
||||
tts.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int speak(
|
||||
String text, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.speak(text, queueMode, params);
|
||||
}
|
||||
return tts.speak(text, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stop() {
|
||||
if (tts == null) {
|
||||
return super.stop();
|
||||
}
|
||||
return tts.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int synthesizeToFile(String text, HashMap<String, String> params, String filename) {
|
||||
if (tts == null) {
|
||||
return super.synthesizeToFile(text, params, filename);
|
||||
}
|
||||
return tts.synthesizeToFile(text, params, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Tests for {@link TripStatistics}.
|
||||
@@ -23,26 +27,17 @@ import junit.framework.TestCase;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TripStatisticsTest extends TestCase {
|
||||
@RunWith(JUnit4.class)
|
||||
public class TripStatisticsTest {
|
||||
|
||||
private TripStatistics statistics;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
statistics = new TripStatistics();
|
||||
}
|
||||
|
||||
public void testSetBounds() {
|
||||
// This is not a trivial setter, conversion happens in it
|
||||
statistics.setBounds(12345, -34567, 56789, -98765);
|
||||
assertEquals(12345, statistics.getLeft());
|
||||
assertEquals(-34567, statistics.getTop());
|
||||
assertEquals(56789, statistics.getRight());
|
||||
assertEquals(-98765, statistics.getBottom());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMerge() {
|
||||
TripStatistics statistics2 = new TripStatistics();
|
||||
statistics.setStartTime(1000L); // Resulting start time
|
||||
@@ -68,38 +63,32 @@ public class TripStatisticsTest extends TestCase {
|
||||
statistics2.setMaxGrade(35.0); // Resulting max grade
|
||||
statistics2.setMinGrade(0.0);
|
||||
|
||||
// Resulting bounds: -10000, 35000, 30000, -40000
|
||||
statistics.setBounds(-10000, 20000, 30000, -40000);
|
||||
statistics2.setBounds(-5000, 35000, 0, 20000);
|
||||
|
||||
statistics.merge(statistics2);
|
||||
|
||||
assertEquals(1000L, statistics.getStartTime());
|
||||
assertEquals(4000L, statistics.getStopTime());
|
||||
assertEquals(2500L, statistics.getTotalTime());
|
||||
assertEquals(1300L, statistics.getMovingTime());
|
||||
assertEquals(1100.0, statistics.getTotalDistance());
|
||||
assertEquals(900.0, statistics.getTotalElevationGain());
|
||||
assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed());
|
||||
assertEquals(-10000, statistics.getLeft());
|
||||
assertEquals(30000, statistics.getRight());
|
||||
assertEquals(35000, statistics.getTop());
|
||||
assertEquals(-40000, statistics.getBottom());
|
||||
assertEquals(1200.0, statistics.getMinElevation());
|
||||
assertEquals(3575.0, statistics.getMaxElevation());
|
||||
assertEquals(-25.0, statistics.getMinGrade());
|
||||
assertEquals(35.0, statistics.getMaxGrade());
|
||||
Assert.assertEquals(1000L, statistics.getStartTime());
|
||||
Assert.assertEquals(4000L, statistics.getStopTime());
|
||||
Assert.assertEquals(2500L, statistics.getTotalTime());
|
||||
Assert.assertEquals(1300L, statistics.getMovingTime());
|
||||
Assert.assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
|
||||
Assert.assertEquals(900.0, statistics.getTotalElevationGain(), 0.001);
|
||||
Assert.assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed(), 0.001);
|
||||
Assert.assertEquals(1200.0, statistics.getMinElevation(), 0.001);
|
||||
Assert.assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
|
||||
Assert.assertEquals(-25.0, statistics.getMinGrade(), 0.001);
|
||||
Assert.assertEquals(35.0, statistics.getMaxGrade(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAverageSpeed() {
|
||||
statistics.setTotalDistance(1000.0);
|
||||
statistics.setTotalTime(50000); // in milliseconds
|
||||
assertEquals(20.0, statistics.getAverageSpeed());
|
||||
Assert.assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAverageMovingSpeed() {
|
||||
statistics.setTotalDistance(1000.0);
|
||||
statistics.setMovingTime(20000); // in milliseconds
|
||||
assertEquals(50.0, statistics.getAverageMovingSpeed());
|
||||
Assert.assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TripStatistics;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AnnouncementUtilsTest {
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement() {
|
||||
TripStatistics stats = new TripStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats);
|
||||
|
||||
// then
|
||||
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -26,78 +27,83 @@ import java.io.File;
|
||||
*
|
||||
* @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";
|
||||
@@ -109,8 +115,8 @@ public class FileUtilsTest extends TestCase {
|
||||
String truncated = FileUtils.truncateFileName(directory, nameString, suffix);
|
||||
|
||||
for (int i = 0; i < truncated.length(); i++) {
|
||||
assertEquals('a', truncated.charAt(i));
|
||||
Assert.assertEquals('a', truncated.charAt(i));
|
||||
}
|
||||
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
|
||||
Assert.assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.3525Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,4 +191,24 @@ public class StringUtilsTest {
|
||||
Assert.assertEquals(-1, parts[1]);
|
||||
Assert.assertEquals(0, parts[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDecimal() {
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 0));
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.1, 0));
|
||||
Assert.assertEquals("1", StringUtils.formatDecimal(1.1, 0));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10, 0));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10.1, 0));
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(-0.1, 0));
|
||||
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 2));
|
||||
Assert.assertEquals("0.1", StringUtils.formatDecimal(0.1, 2));
|
||||
Assert.assertEquals("1.1", StringUtils.formatDecimal(1.1, 2));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10, 2));
|
||||
Assert.assertEquals("10.1", StringUtils.formatDecimal(10.1, 2));
|
||||
Assert.assertEquals("10.11", StringUtils.formatDecimal(10.111, 2));
|
||||
Assert.assertEquals("-0.1", StringUtils.formatDecimal(-0.1, 2));
|
||||
|
||||
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,28 @@ limitations under the License.
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="3079"
|
||||
android:versionName="v3.2.2"> <!-- For FDroid. -->
|
||||
android:versionCode="3305"
|
||||
android:versionName="v3.5.0"> <!-- For FDroid. -->
|
||||
<!-- Permission to initialize services -->
|
||||
<application
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
android:hasFragileUserData="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/app_name"
|
||||
android:icon="${applicationLogo}"
|
||||
android:label="${applicationName}"
|
||||
android:theme="@style/ThemeCustom">
|
||||
|
||||
<!-- Activities -->
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
<activity android:name=".DeleteActivity" />
|
||||
<activity
|
||||
android:name=".TrackDeleteActivity"
|
||||
android:theme="@style/ThemeCustomTransparent" />
|
||||
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:label="@string/menu_help" />
|
||||
|
||||
<activity
|
||||
android:name=".io.file.importer.ImportActivity"
|
||||
@@ -50,10 +57,10 @@ limitations under the License.
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MarkerEditActivity" />
|
||||
|
||||
<activity android:name=".MarkerDetailActivity" />
|
||||
|
||||
<activity android:name=".MarkerEditActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".MarkerListActivity"
|
||||
android:label="@string/menu_markers">
|
||||
@@ -95,9 +102,8 @@ limitations under the License.
|
||||
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
<provider
|
||||
android:name=".content.ShareContentProvider"
|
||||
android:name=".content.provider.ShareContentProvider"
|
||||
android:authorities="${applicationId}.content"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
@@ -112,18 +118,11 @@ 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"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="location|connectedDevice"
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
</application>
|
||||
@@ -138,44 +137,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>
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import de.dennisguse.opentracks.util.TTSUtils;
|
||||
|
||||
/**
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@@ -32,7 +33,7 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set volume control stream for text to speech
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
setVolumeControlStream(TTSUtils.getTTSStream());
|
||||
|
||||
setContentView(getLayoutResId());
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||
stopRecording = true;
|
||||
} else {
|
||||
long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);
|
||||
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||
for (long trackId : trackIds) {
|
||||
if (trackId == recordingTrackId) {
|
||||
stopRecording = true;
|
||||
@@ -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,7 +19,7 @@ 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.
|
||||
@@ -30,7 +30,7 @@ public 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);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
public class HelpActivity extends AbstractActivity {
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.help;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -64,17 +90,27 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
|
||||
// Setup UI elements
|
||||
statisticsSection = findViewById(R.id.marker_edit_statistics_section);
|
||||
statisticsName = findViewById(R.id.marker_edit_statistics_name);
|
||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||
|
||||
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
|
||||
// Setup UI elements
|
||||
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 ? false : true);
|
||||
insertGalleryImgMenuItem.setVisible(isPhotoSet ? false : true);
|
||||
}
|
||||
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;
|
||||
@@ -69,21 +69,17 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
/*
|
||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||
* class. Anonymous inner class will get garbage collected.
|
||||
*/
|
||||
private final OnSharedPreferenceChangeListener
|
||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
// Note that the key can be null
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key);
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -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,15 +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));
|
||||
@@ -150,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);
|
||||
@@ -159,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);
|
||||
|
||||
@@ -181,11 +175,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||
if (track != null) {
|
||||
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
|
||||
WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + "!=?",
|
||||
new String[]{String.valueOf(track.getId()), String.valueOf(trackFirstWaypointId)}, null);
|
||||
WaypointsColumns.TRACKID + "=?",
|
||||
new String[]{String.valueOf(track.getId())}, null);
|
||||
} else {
|
||||
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
|
||||
WaypointsColumns.STARTTIME + " IS NULL", null, null);
|
||||
null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +238,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (track != null && item.getItemId() == R.id.marker_list_insert_marker) {
|
||||
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId());
|
||||
|
||||
@@ -40,13 +40,12 @@ 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;
|
||||
@@ -55,9 +54,9 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
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.ToolbarUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
/**
|
||||
@@ -102,10 +101,10 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
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);
|
||||
@@ -123,10 +122,6 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
MenuItem shareMenuItem = menu.findItem(R.id.list_context_menu_share);
|
||||
if (isSingleSelectionTrack) {
|
||||
shareMenuItem.setVisible(!isRecording);
|
||||
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
|
||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||
|
||||
ToolbarUtils.setupShareActionProvider(SearchListActivity.this, shareMenuItem, new long[]{trackId});
|
||||
}
|
||||
|
||||
// One item, item is a marker
|
||||
@@ -148,14 +143,14 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) {
|
||||
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key);
|
||||
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(SearchListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -173,7 +168,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
super.onCreate(savedInstanceState);
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
searchEngine = new SearchEngine(contentProviderUtils);
|
||||
@@ -248,7 +245,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
trackRecordingServiceConnection.unbind();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -290,6 +287,11 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentUtils.showCoordinateOnMap(this, (double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + "");
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
if (markerId != null) {
|
||||
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||
@@ -391,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
|
||||
@@ -403,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());
|
||||
|
||||
@@ -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,151 +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.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
|
||||
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,46 +16,36 @@
|
||||
|
||||
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.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.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.FrequencyDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
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.ToolbarUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
@@ -73,21 +63,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
|
||||
@@ -95,8 +77,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
|
||||
// Preferences
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
private boolean showOnLockScreen = PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN;
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
@@ -106,38 +87,32 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
/*
|
||||
* 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) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key);
|
||||
}
|
||||
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
}
|
||||
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key))) {
|
||||
showOnLockScreen = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key, PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN);
|
||||
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;
|
||||
@@ -154,11 +129,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
};
|
||||
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
private MenuItem shareMenuItem;
|
||||
private MenuItem voiceFrequencyMenuItem;
|
||||
private MenuItem splitFrequencyMenuItem;
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -188,37 +160,54 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
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);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
handleIntent(getIntent());
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||
trackDataHub = TrackDataHub.newInstance(this);
|
||||
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 chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab));
|
||||
tabsAdapter.addTab(chartTabSpec, ChartFragment.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,16 +216,31 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
} else if (showOnLockScreen) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
} else {
|
||||
if (showOnLockScreen)
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,30 +275,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(Bundle outState) {
|
||||
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
|
||||
@@ -314,14 +302,7 @@ 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);
|
||||
ToolbarUtils.setupShareActionProviderCompat(this, shareMenuItem, new long[]{trackId});
|
||||
|
||||
voiceFrequencyMenuItem = menu.findItem(R.id.track_detail_voice_frequency);
|
||||
splitFrequencyMenuItem = menu.findItem(R.id.track_detail_split_frequency);
|
||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
@@ -343,12 +324,10 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
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();
|
||||
case R.id.track_detail_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
||||
@@ -358,12 +337,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_voice_frequency:
|
||||
FrequencyDialogFragment.showDialog(getSupportFragmentManager(), R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.string.menu_voice_frequency);
|
||||
return true;
|
||||
case R.id.track_detail_split_frequency:
|
||||
FrequencyDialogFragment.showDialog(getSupportFragmentManager(), R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.string.menu_split_frequency);
|
||||
return true;
|
||||
case R.id.track_detail_edit:
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
@@ -381,20 +354,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;
|
||||
@@ -417,16 +376,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
return trackDataHub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track id.
|
||||
*/
|
||||
public long getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -462,11 +411,8 @@ 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);
|
||||
voiceFrequencyMenuItem.setVisible(isRecording());
|
||||
splitFrequencyMenuItem.setVisible(isRecording());
|
||||
String title;
|
||||
if (isRecording()) {
|
||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
@@ -477,12 +423,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -50,18 +50,18 @@ 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;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ServiceUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.ToolbarUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
@@ -115,9 +115,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
|
||||
boolean isSingleSelection = trackIds.length == 1;
|
||||
|
||||
MenuItem shareMenuItem = menu.findItem(R.id.list_context_menu_share);
|
||||
ToolbarUtils.setupShareActionProvider(TrackListActivity.this, shareMenuItem, trackIds);
|
||||
|
||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
||||
}
|
||||
@@ -128,32 +125,23 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
}
|
||||
};
|
||||
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_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() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.stats_units_key))) {
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
||||
}
|
||||
if (key == null || key.equals(
|
||||
PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(
|
||||
TrackListActivity.this, R.string.recording_track_id_key);
|
||||
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 (key == null || key.equals(PreferencesUtils.getKey(
|
||||
TrackListActivity.this, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this,
|
||||
R.string.recording_track_paused_key,
|
||||
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -172,7 +160,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Menu items
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem startGpsMenuItem;
|
||||
private MenuItem deleteAllMenuItem;
|
||||
// private MenuItem deleteAllMenuItem;
|
||||
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -204,7 +192,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
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;
|
||||
@@ -229,12 +217,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
startRecording();
|
||||
} else {
|
||||
if (recordingTrackPaused) {
|
||||
} else if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
@@ -245,7 +232,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -253,7 +239,9 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||
@@ -352,7 +340,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
super.onStop();
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
trackRecordingServiceConnection.unbind();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -379,14 +367,13 @@ 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
boolean isGpsStarted = TrackRecordingServiceConnection.isRecordingServiceRunning(this);
|
||||
boolean isGpsStarted = ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
updateMenuItems(isGpsStarted, isRecording);
|
||||
|
||||
@@ -406,7 +393,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
startGps = !TrackRecordingServiceConnection.isRecordingServiceRunning(this);
|
||||
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||
|
||||
// Show toast
|
||||
Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT);
|
||||
@@ -415,14 +402,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
// Invoke trackRecordingService
|
||||
if (startGps) {
|
||||
trackRecordingServiceConnection.startAndBind();
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
bindChangedCallback.run();
|
||||
} else {
|
||||
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
trackRecordingService.stopGps();
|
||||
}
|
||||
trackRecordingServiceConnection.unbindAndStop();
|
||||
trackRecordingServiceConnection.unbindAndStop(this);
|
||||
}
|
||||
|
||||
// Update menu after starting or stopping gps
|
||||
@@ -433,9 +420,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);
|
||||
@@ -503,9 +487,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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -513,7 +494,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
*/
|
||||
private void startRecording() {
|
||||
startNewRecording = true;
|
||||
trackRecordingServiceConnection.startAndBind();
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
|
||||
/*
|
||||
* If the binding has happened, then invoke the callback to start a new recording.
|
||||
@@ -531,12 +512,18 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
* @return true if handled.
|
||||
*/
|
||||
private boolean handleContextItem(int itemId, long[] trackIds) {
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, trackIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
@@ -23,8 +23,6 @@ import android.graphics.Paint.Align;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
@@ -203,7 +201,6 @@ class ChartValueSeries {
|
||||
/**
|
||||
* Gets the interval value.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
@@ -218,7 +215,6 @@ class ChartValueSeries {
|
||||
/**
|
||||
* Gets the max marker value.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getMaxMarkerValue() {
|
||||
return maxMarkerValue;
|
||||
}
|
||||