Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 9bd4ba489c | |||
| dd55b50dad | |||
| 1719d21a5b | |||
| 75ef8d99d4 | |||
| ae06fd1680 | |||
| 2b03f7dea8 | |||
| 7591b012b4 | |||
| 0e089f49f3 | |||
| 5ad1856b13 | |||
| 965dbbe829 | |||
| 262c9886df | |||
| ac5a9ea65d | |||
| ed9b4c8f3d | |||
| 209aa6a8a5 | |||
| 81ee6ac1fa | |||
| 024dd603cb | |||
| 50654c6baf | |||
| 8c9bf60fc6 | |||
| a83eb0b983 | |||
| ab3cbfea0f | |||
| c93e5ca1a6 | |||
| c2841092da | |||
| 8b11e1d611 | |||
| 6375a851b2 | |||
| f21200a7ec | |||
| 5752cebdec | |||
| fa6329c157 | |||
| 98a9884798 | |||
| 1f7f615411 | |||
| a79345ccf9 | |||
| 732e0e76f9 | |||
| 63d29f2097 | |||
| 9dd2f7d1c1 | |||
| afd25363b3 | |||
| 2eb5a42099 | |||
| 9c753034b4 | |||
| 95b7c701a2 | |||
| 56685a9dec | |||
| 45b13ea813 | |||
| b0feed84bb | |||
| 0e2da83e1f | |||
| fa364a150d | |||
| 6ec78d77a9 | |||
| 8853bb5408 | |||
| e4d6355875 | |||
| 1e08d72cc9 | |||
| ea82d2df94 | |||
| 71c0114f8f | |||
| a3955c7c37 | |||
| e610b70590 | |||
| 7f4d47d7b5 | |||
| fd27ec11a2 | |||
| 2aa036f3aa | |||
| 2c49a4f3f1 | |||
| 10e4a52947 | |||
| 9f3936e180 | |||
| a3a76ce0c3 | |||
| 545db52f94 | |||
| bfa02a97ee | |||
| 3a2eeaa204 | |||
| 42b3a5dfc9 | |||
| b5cb47a0c7 | |||
| 443e069a13 | |||
| 9c432c3110 | |||
| 79934d51ac | |||
| 496cf9145b | |||
| 264bb3746f | |||
| 7b1ccf4507 | |||
| a326265dba | |||
| 0864bc8104 | |||
| f5656470a9 | |||
| 5bc773f1aa | |||
| 6bfd8c8db8 | |||
| df2865456e | |||
| fc6d5f1f23 | |||
| 96dab8c81b | |||
| 1d13d51f0c | |||
| 87d92d25f9 | |||
| 025dee6821 | |||
| 796d086615 | |||
| bb1ef08474 | |||
| cc770d8bb4 | |||
| 8a56c262cb | |||
| c0a9958b5b | |||
| 7351cd3377 | |||
| 7b3d5848b9 | |||
| 0f311e062e | |||
| 57930a8d86 | |||
| 7c0069394f | |||
| 379913fb3e | |||
| 542d9ccb77 | |||
| c8ae3b83b5 | |||
| 0a046fc100 | |||
| c551a38a8b | |||
| 72b1ca82ed | |||
| 1ef81c45db | |||
| 08e6ae76f2 | |||
| 6e410e049d | |||
| 22b7962940 | |||
| 5788f99b20 | |||
| 8685e43dc2 | |||
| b8bea27155 | |||
| c9226d3b79 | |||
| 9701feff04 | |||
| b8b40a88ef | |||
| adcf7b388b | |||
| 3a7562d96c | |||
| 2753cf7d0b | |||
| 5841415a0b | |||
| d79fa07834 | |||
| ff6caf706e | |||
| c953fbc67a | |||
| 55870cca85 | |||
| 0123242be6 | |||
| 81a6d7b53a | |||
| f423b07cee | |||
| 33231bdbb1 | |||
| 677b691ef1 | |||
| 5e74e80a9a | |||
| 70b5d799a2 | |||
| 16f18fe16f | |||
| 8538c79dd8 | |||
| f8e3b28e51 | |||
| 9f665e07fd | |||
| b7c010c086 | |||
| 8296c0194d | |||
| f537cc7321 | |||
| 4fa2401b27 | |||
| f30e3cd5cd | |||
| b73353120f | |||
| 7fa9df1f99 | |||
| ea3de8a987 |
@@ -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,116 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -1,12 +1,45 @@
|
|||||||
|
#See https://docs.travis-ci.com/user/languages/android/
|
||||||
|
#See https://github.com/ankidroid/Anki-Android/blob/master/.travis.yml
|
||||||
language: android
|
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:
|
android:
|
||||||
components:
|
components:
|
||||||
|
# installing tools to start, then use `sdkmanager` below to get the rest
|
||||||
|
- tools
|
||||||
- build-tools-28.0.3
|
- build-tools-28.0.3
|
||||||
- android-28
|
- android-28
|
||||||
|
|
||||||
|
# Emulator Management: Create, Start and Wait
|
||||||
|
install:
|
||||||
|
- echo y | sdkmanager "platform-tools"
|
||||||
|
- echo y | sdkmanager --channel=4 "emulator"
|
||||||
|
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||||
|
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||||
|
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||||
|
- android-wait-for-emulator
|
||||||
|
- adb shell input keyevent 82 &
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew build
|
- ./gradlew clean 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
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# OpenTrack: Developer information
|
||||||
|
|
||||||
|
## Create releases for FDroid
|
||||||
|
|
||||||
|
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
|
||||||
|
2. Decide on _version name_ (semantic versioning)
|
||||||
|
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
|
||||||
|
4. Create changelog (`_version code_.txt`)
|
||||||
|
5. Create commit with all changes
|
||||||
|
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||||
|
7. Push commits and tags to public repository
|
||||||
|
8. Add changelog to tag (Github releases)
|
||||||
|
|
||||||
|
## Translations (localization)
|
||||||
|
|
||||||
|
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
# OpenTracks: a sport tracker
|
# <img src="drawable-svg/LOGO.svg" alt="OpenTracks logo" height="40"></img> OpenTracks: a sport tracker
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
||||||
|
|
||||||
<a href="https://liberapay.com/dennis.guse/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
<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>
|
||||||
|
|
||||||
## Features:
|
## Features:
|
||||||
* __Tracking:__ track your sport and outdoor activities
|
* __Tracking:__ track your sport and outdoor activities
|
||||||
* __Photos and Markers:__ mark interesting locations while tracking
|
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
||||||
* __Heart rate:__ supports bluetooth LE sensors
|
* __Heart rate:__ supports bluetooth LE sensors
|
||||||
* __Export data:__ Export tracks either as KML or GPX
|
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
||||||
* __Voice annoucements__
|
|
||||||
* __No Internet access:__ Internet is not used
|
* __No Internet access:__ Internet is not used
|
||||||
|
* __Voice annoucements__
|
||||||
* __No advertising__
|
* __No advertising__
|
||||||
* __Privacy:__
|
* __Privacy:__
|
||||||
* _contains in-app analytics_
|
* _does not contain any in-app analytics_
|
||||||
* _does not have Google Play Services_
|
* _does not have Google Play Services_
|
||||||
* _has no cloud integration_
|
* _has no cloud integration_
|
||||||
|
|
||||||
@@ -26,6 +26,18 @@ _Please note:_
|
|||||||
_Tracks_ does not contain a _map_ (as this would required Internet access).
|
_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.
|
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>
|
||||||
|
|
||||||
|
## 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
|
## Project history
|
||||||
|
|
||||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.4.2'
|
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ def getVersionCode = { ->
|
|||||||
return Integer.parseInt(stdout.toString().trim())
|
return Integer.parseInt(stdout.toString().trim())
|
||||||
}
|
}
|
||||||
catch (ignored) {
|
catch (ignored) {
|
||||||
return -1;
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,27 +35,32 @@ def getVersionName = { ->
|
|||||||
try {
|
try {
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
commandLine 'git', 'describe', '--tags', '--dirty'
|
commandLine 'git', 'describe', '--tags'
|
||||||
standardOutput = stdout
|
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) {
|
catch (ignored) {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 29
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
//Useful for debugging.
|
//Useful for debugging.
|
||||||
//versionCode getVersionCode()
|
//versionCode getVersionCode()
|
||||||
//versionName getVersionName()
|
|
||||||
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 29
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -67,22 +72,18 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
variant.resValue "string", "applicationId", variant.applicationId
|
variant.resValue "string", "applicationId", variant.applicationId
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Should be removed (when LINT does not throw any errors).
|
|
||||||
lintOptions {
|
|
||||||
abortOnError false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
@@ -94,6 +95,4 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="188"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 78 188"
|
||||||
|
width="78"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs6" />
|
||||||
|
<g id="layer1" />
|
||||||
|
<g
|
||||||
|
style="display:inline"
|
||||||
|
id="layer2" />
|
||||||
|
<metadata id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<path
|
||||||
|
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
|
||||||
|
d="m 1,150 38,37 39,-37 H 50 V 97 L 29,81 v 69 z"
|
||||||
|
id="path31161" />
|
||||||
|
<path
|
||||||
|
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
|
||||||
|
d="M 29,51 V 70 L 50,86 V 67 Z"
|
||||||
|
id="path31169" />
|
||||||
|
<path
|
||||||
|
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
|
||||||
|
d="M 29,23 V 40 L 50,56 V 39 Z"
|
||||||
|
id="path31192" />
|
||||||
|
<path
|
||||||
|
style="fill:#cccccc;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:0.7019608"
|
||||||
|
d="M 29,0 V 14 L 50,29 V 16 Z"
|
||||||
|
id="path31194" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
height="125"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="ic_button_pause.svg">
|
||||||
|
<defs id="defs6" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="Layer 1">
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#a31a1f;stroke-width:5.03185225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path12"
|
||||||
|
r="59.984074" />
|
||||||
|
<rect
|
||||||
|
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273912;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
height="17.940611"
|
||||||
|
id="rect837"
|
||||||
|
width="5"
|
||||||
|
x="54"
|
||||||
|
y="54.059391" />
|
||||||
|
<rect
|
||||||
|
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273865;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
height="17.940611"
|
||||||
|
id="rect837-3"
|
||||||
|
width="5"
|
||||||
|
x="66"
|
||||||
|
y="54" />
|
||||||
|
</g>
|
||||||
|
<metadata id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
id="namedview4"
|
||||||
|
objecttolerance="10"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:cx="75.030293"
|
||||||
|
inkscape:cy="72.881196"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-height="1152"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:window-width="2048"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:zoom="5.3400704" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="125"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs6" />
|
||||||
|
<g id="layer1">
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#a31a1f;stroke-width:5.03185225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path12"
|
||||||
|
r="59.984074" />
|
||||||
|
<circle
|
||||||
|
style="fill:#414141;fill-opacity:0.4;stroke:none;stroke-width:6.9172821;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path835"
|
||||||
|
r="50" />
|
||||||
|
<rect
|
||||||
|
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273912;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
height="17.940611"
|
||||||
|
id="rect837"
|
||||||
|
width="5"
|
||||||
|
x="54"
|
||||||
|
y="54.059391" />
|
||||||
|
<rect
|
||||||
|
style="fill:#efefef;fill-opacity:0.87843137;stroke:none;stroke-width:4.82273865;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
height="17.940611"
|
||||||
|
id="rect837-3"
|
||||||
|
width="5"
|
||||||
|
x="66"
|
||||||
|
y="54" />
|
||||||
|
</g>
|
||||||
|
<metadata id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="125"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs6" />
|
||||||
|
<g id="layer1">
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#a31a1f;stroke-width:5.03185225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path12"
|
||||||
|
r="59.984074" />
|
||||||
|
<circle
|
||||||
|
style="fill:#9a191d;fill-opacity:1;stroke:none;stroke-width:6.9172821;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path835"
|
||||||
|
r="50" />
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#7e3b3d;stroke-width:1.62162137;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="61.500004"
|
||||||
|
id="path831"
|
||||||
|
r="9.189189" />
|
||||||
|
</g>
|
||||||
|
<metadata id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="125"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
fill="none"
|
||||||
|
r="59.984"
|
||||||
|
stroke="#a31a1f"
|
||||||
|
stroke-width="5.0319" />
|
||||||
|
<circle
|
||||||
|
cx="62.5"
|
||||||
|
cy="-62.5"
|
||||||
|
fill="url(#linearGradient934)"
|
||||||
|
r="50"
|
||||||
|
transform="rotate(90)" />
|
||||||
|
<circle
|
||||||
|
cx="62.5"
|
||||||
|
cy="61.5"
|
||||||
|
fill="#fff"
|
||||||
|
r="9.1892"
|
||||||
|
stroke="#7e3b3d"
|
||||||
|
stroke-width="1.6216" />
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="linearGradient934"
|
||||||
|
x1="12.5"
|
||||||
|
x2="112.5"
|
||||||
|
y1="-62.5"
|
||||||
|
y2="-62.5">
|
||||||
|
<stop
|
||||||
|
offset="0"
|
||||||
|
stop-color="#721212" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
stop-color="#a21a1f" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<metadata>
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title />
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="125"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs6">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient884"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#969696;stop-opacity:1;"
|
||||||
|
id="stop882"
|
||||||
|
offset="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="layer1">
|
||||||
|
<circle
|
||||||
|
style="fill:#484848;fill-opacity:1;stroke:none;stroke-width:6.9172821;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path835"
|
||||||
|
r="50" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#555555;stroke-width:5.03194237;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.502243"
|
||||||
|
cy="63.029659"
|
||||||
|
id="path12"
|
||||||
|
rx="59.986229"
|
||||||
|
ry="59.984074"
|
||||||
|
transform="matrix(0.99996409,-0.00847427,0,1,0,0)" />
|
||||||
|
</g>
|
||||||
|
<metadata id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<rect
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#414141;stroke-width:1.04023623;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
height="17.459764"
|
||||||
|
id="rect844"
|
||||||
|
width="17.459761"
|
||||||
|
x="53.020119"
|
||||||
|
y="54.020119" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="125"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
fill="#2a2a2a"
|
||||||
|
r="50" />
|
||||||
|
<ellipse
|
||||||
|
cx="62.502"
|
||||||
|
cy="63.03"
|
||||||
|
fill="none"
|
||||||
|
rx="59.986"
|
||||||
|
ry="59.984"
|
||||||
|
stroke="#2a2a2a"
|
||||||
|
stroke-width="5.0319"
|
||||||
|
transform="matrix(.99996 -.0084743 0 1 0 0)" />
|
||||||
|
<metadata>
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title />
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<rect
|
||||||
|
fill="#555"
|
||||||
|
height="15"
|
||||||
|
width="15"
|
||||||
|
x="54"
|
||||||
|
y="56" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1022 B |
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="125"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle
|
||||||
|
cx="62.5"
|
||||||
|
cy="-62.5"
|
||||||
|
fill="url(#linearGradient1053)"
|
||||||
|
r="50"
|
||||||
|
transform="rotate(90)" />
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
gradientTransform="translate(0,-125)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="linearGradient1053"
|
||||||
|
x1="12.5"
|
||||||
|
x2="112.5"
|
||||||
|
y1="62.5"
|
||||||
|
y2="62.5">
|
||||||
|
<stop
|
||||||
|
offset="0"
|
||||||
|
stop-color="#404040" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
stop-color="#555" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<ellipse
|
||||||
|
cx="62.502"
|
||||||
|
cy="63.03"
|
||||||
|
fill="none"
|
||||||
|
rx="59.986"
|
||||||
|
ry="59.984"
|
||||||
|
stroke="#555"
|
||||||
|
stroke-width="5.0319"
|
||||||
|
transform="matrix(.99996 -.0084743 0 1 0 0)" />
|
||||||
|
<metadata>
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title />
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<rect
|
||||||
|
fill="#dbdbdb"
|
||||||
|
height="17.46"
|
||||||
|
stroke="#414141"
|
||||||
|
stroke-width="1.0402"
|
||||||
|
width="17.46"
|
||||||
|
x="53.02"
|
||||||
|
y="54.02" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
height="48"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
width="48"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="ic_track_paused.svg">
|
||||||
|
<defs id="defs6" />
|
||||||
|
<metadata id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<rect
|
||||||
|
style="fill:#a31a1f;fill-opacity:1;stroke-width:1.01154423"
|
||||||
|
height="20"
|
||||||
|
id="rect12"
|
||||||
|
width="5"
|
||||||
|
x="15"
|
||||||
|
y="14" />
|
||||||
|
<rect
|
||||||
|
style="fill:#a31a1f;fill-opacity:1;stroke-width:1.01154423"
|
||||||
|
height="20"
|
||||||
|
id="rect12-3"
|
||||||
|
width="5"
|
||||||
|
x="27"
|
||||||
|
y="14" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
id="namedview4"
|
||||||
|
objecttolerance="10"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:current-layer="svg2"
|
||||||
|
inkscape:cx="21.262539"
|
||||||
|
inkscape:cy="30.645051"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-height="1152"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:window-width="2048"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:zoom="9.8333333">
|
||||||
|
<inkscape:grid
|
||||||
|
id="grid16"
|
||||||
|
type="xygrid" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
height="48"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
width="48"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs6" />
|
||||||
|
<g
|
||||||
|
style="display:inline"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:#a31a1f;fill-opacity:1;stroke-width:0.03607465"
|
||||||
|
d="m 22.92317,31.9906 c -0.06948,-0.0081 -0.277422,-0.03235 -0.46209,-0.05384 -2.147889,-0.249888 -4.249982,-1.419727 -5.59965,-3.116269 -0.961167,-1.208194 -1.554689,-2.572853 -1.795388,-4.128058 -0.08907,-0.575498 -0.08782,-1.835731 0.0024,-2.415668 0.223761,-1.438345 0.765805,-2.731082 1.632716,-3.893914 0.353204,-0.473771 1.18254,-1.30549 1.649602,-1.654344 0.947391,-0.707615 1.928391,-1.18098 3.033216,-1.463629 0.649731,-0.166221 1.126538,-0.233843 1.827948,-0.259242 1.94015,-0.07026 3.814111,0.517516 5.397189,1.692835 0.484944,0.360035 1.31879,1.186156 1.663737,1.648325 0.890877,1.193617 1.438199,2.499896 1.661736,3.966023 0.07887,0.517263 0.08854,1.773309 0.01737,2.253423 -0.23912,1.612873 -0.846098,3.039393 -1.810255,4.254461 -0.293648,0.370069 -0.946591,1.022292 -1.317069,1.315618 -1.186276,0.939234 -2.594384,1.549011 -4.122093,1.785059 -0.343493,0.05308 -1.524586,0.09902 -1.779366,0.06922 z"
|
||||||
|
id="path85" />
|
||||||
|
</g>
|
||||||
|
<metadata id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||||
|
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
||||||
|
|
||||||
|
Funktionen & Features:
|
||||||
|
* zeichnet Sport und andere Outdooraktivitäten auf
|
||||||
|
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
|
||||||
|
* zeichnet deine Herzfrequenz auf (unterstützt Bluetooth-LE-Sensoren)
|
||||||
|
* exportiert deine Daten als KML oder GPX
|
||||||
|
* funktioniert ganz ohne Internet
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
v3.0.0: OpenTracks
|
||||||
|
|
||||||
|
First release of OpenTracks (i.e., hard fork from MyTracks)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Rebranded from MyTracks to OpenTracks
|
||||||
|
- Added Bluetooth LE heart rate sensor
|
||||||
|
- Removed all features requiring Internet access
|
||||||
|
- Removed Google Maps integration
|
||||||
|
- Removed use of Google Play services
|
||||||
|
- Removed ANT+ sensors
|
||||||
|
- Removed Bluetooth classic sensors
|
||||||
|
- Removed Protobuf
|
||||||
|
- Removed calorie estimation and activity estimation
|
||||||
|
- Removed MyTracks Service API (i.e., no integration with other apps)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
v3.0.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Share markers
|
||||||
|
- Restructured settings
|
||||||
|
- Removed actionbar back button
|
||||||
|
- Export: removed photo size settings option
|
||||||
|
- Export: KML as format is now selectable
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- export directory is now /OpenTracks/
|
||||||
|
- landscape mode is not crashing
|
||||||
|
- fixed KML/KMZ export
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Test are executable (but many failing)
|
||||||
|
- Enabled continuous integration with TravisCI (no tests)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.1.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Added show track in map apps (tested with OsmAnd and Maps.ME)
|
||||||
|
- Added while tracking, show track statistics without unlocking the device
|
||||||
|
- Removed landscape mode for track detail view (was buggy)
|
||||||
|
- Removed search suggestions history
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Request storage permission for import all/export all
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Upgrade to Gradle 5.6
|
||||||
|
- Do not minify release builds
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
v3.2.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Removed Widget
|
||||||
|
- Introduced material design icons
|
||||||
|
- Removed snowboarding and skiing (need new icons)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fixed search functionality
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Fixed (some) test cases
|
||||||
|
- Removed unused parcelable functionality
|
||||||
|
- Major cleanup and reformatting
|
||||||
|
- Do not use broadcast for app to widget communication
|
||||||
|
- stats_content.xml does not include other files
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.2.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Settings are now on a single page
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Settings moved to Androidx compat (uses fragments)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
v3.2.2: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Removed aggregated stats
|
||||||
|
- Migrated to Material Design
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fixed export of sensor data
|
||||||
|
- Fixed import of incomplete sensor data
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Replaced actionbar with toolbar
|
||||||
|
- Migrated to androidx appcompat (incl. Material Design)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.2.3: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Sharing: use Android Sharesheet
|
||||||
|
- Sharing: share multiple tracks as individual files
|
||||||
|
- Use Android ShareSheet
|
||||||
|
- Heart rate sensor (Bluetooth LE): automatic reconnect
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Heart rate sensor (Bluetooth LE): disconnect properly
|
||||||
|
- Heart rate wasn't always shown while recording
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Enabled TravisCI instrumentation tests
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
v3.2.4: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Settings activity crashes
|
||||||
|
Removed possibility to change voice/split frequency from the context menu
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
v3.2.5: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Disable activity change
|
||||||
|
- Add permission for Android 10: recording location data
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fix reset settings
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Preferences
|
||||||
|
- if Settings cannot be loaded reset all preferences
|
||||||
|
- moved access to preferences to PreferencesUtil
|
||||||
|
- moved default of preferences from code to xml
|
||||||
|
- use SummaryProvider
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v3.2.6: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Heart rate monitor: scan for available devices
|
||||||
|
- Remove shortcut to Bluetooth settings
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Preferences: moved default of preferences from code to xml
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(tbd): OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
-
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
-
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
-
|
||||||
@@ -3,6 +3,6 @@ OpenTracks is a sport tracking application that completely respects your privacy
|
|||||||
Features:
|
Features:
|
||||||
* Sport tracking: track your sport and outdoor activities
|
* Sport tracking: track your sport and outdoor activities
|
||||||
* Photos and markers: mark interesting locations while tracking
|
* Photos and markers: mark interesting locations while tracking
|
||||||
* Heart rate: supports bluetooth LE sensors
|
* Heart rate: supports Bluetooth LE sensors
|
||||||
* Export data: Export tracks either as KML or GPX
|
* Export data: export tracks either as KML or GPX
|
||||||
* No Internet access: Internet is not used
|
* No Internet access: Internet is not used
|
||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
@@ -0,0 +1 @@
|
|||||||
|
A sport tracker that completely respects your privacy.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#Wed Jun 19 22:47:37 CEST 2019
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
|
||||||
|
|||||||
@@ -1,4 +1,20 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
##
|
||||||
@@ -6,42 +22,6 @@
|
|||||||
##
|
##
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS=""
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=`basename "$0"`
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD="maximum"
|
|
||||||
|
|
||||||
warn ( ) {
|
|
||||||
echo "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
die ( ) {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
case "`uname`" in
|
|
||||||
CYGWIN* )
|
|
||||||
cygwin=true
|
|
||||||
;;
|
|
||||||
Darwin* )
|
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
PRG="$0"
|
||||||
@@ -60,6 +40,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
|
|||||||
APP_HOME="`pwd -P`"
|
APP_HOME="`pwd -P`"
|
||||||
cd "$SAVED" >/dev/null
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -85,7 +105,7 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
if [ $? -eq 0 ] ; then
|
if [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
@@ -105,8 +125,8 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
@@ -150,11 +170,19 @@ if $cygwin ; then
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
# Escape application args
|
||||||
function splitJvmOpts() {
|
save () {
|
||||||
JVM_OPTS=("$@")
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
}
|
}
|
||||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
APP_ARGS=$(save "$@")
|
||||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
|
||||||
|
|
||||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -8,14 +24,14 @@
|
|||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables with windows NT shell
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS=
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
@@ -46,10 +62,9 @@ echo location of your Java installation.
|
|||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
:init
|
||||||
@rem Get command-line arguments, handling Windowz variants
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
|
||||||
|
|
||||||
:win9xME_args
|
:win9xME_args
|
||||||
@rem Slurp the command line arguments.
|
@rem Slurp the command line arguments.
|
||||||
@@ -60,11 +75,6 @@ set _SKIP=2
|
|||||||
if "x%~1" == "x" goto execute
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
set CMD_LINE_ARGS=%*
|
||||||
goto execute
|
|
||||||
|
|
||||||
:4NT_args
|
|
||||||
@rem Get arguments from the 4NT Shell from JP Software
|
|
||||||
set CMD_LINE_ARGS=%$
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
A sport tracker that completely respects your privacy
|
|
||||||
@@ -87,6 +87,8 @@ public class BootReceiverTest {
|
|||||||
*/
|
*/
|
||||||
private boolean isServiceExisted(Context context) {
|
private boolean isServiceExisted(Context context) {
|
||||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
Assert.assertNotNull(activityManager);
|
||||||
|
|
||||||
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
|
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||||
|
|
||||||
for (int i = 0; i < serviceList.size(); i++) {
|
for (int i = 0; i < serviceList.size(); i++) {
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ import de.dennisguse.opentracks.content.Track;
|
|||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commons utilities for creating stubs of track, location. The class will be
|
* Commons utilities for creating stubs of track, location.
|
||||||
* enriched if needs more similar stubs for test.
|
|
||||||
*
|
*
|
||||||
* @author Youtao Liu
|
* @author Youtao Liu
|
||||||
*/
|
*/
|
||||||
@@ -50,8 +49,7 @@ public class TrackStubUtils {
|
|||||||
public static Track createTrack(int numberOfLocations) {
|
public static Track createTrack(int numberOfLocations) {
|
||||||
Track track = new Track();
|
Track track = new Track();
|
||||||
for (int i = 0; i < numberOfLocations; i++) {
|
for (int i = 0; i < numberOfLocations; i++) {
|
||||||
track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE
|
track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
|
||||||
+ i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return track;
|
return track;
|
||||||
@@ -71,7 +69,7 @@ public class TrackStubUtils {
|
|||||||
*
|
*
|
||||||
* @return a SensorDataSetLocation stub.
|
* @return a SensorDataSetLocation stub.
|
||||||
*/
|
*/
|
||||||
public static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, double altitude) {
|
private static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, double altitude) {
|
||||||
// Initial Location
|
// Initial Location
|
||||||
Location loc = new Location(LOCATION_PROVIDER);
|
Location loc = new Location(LOCATION_PROVIDER);
|
||||||
loc.setLatitude(latitude);
|
loc.setLatitude(latitude);
|
||||||
@@ -85,5 +83,4 @@ public class TrackStubUtils {
|
|||||||
|
|
||||||
return new SensorDataSetLocation(loc, sd);
|
return new SensorDataSetLocation(loc, sd);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ public class CustomContentProviderTest {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
context.deleteDatabase(DATABASE_NAME);
|
context.deleteDatabase(DATABASE_NAME);
|
||||||
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
|
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
|
||||||
customContentProvider = new CustomContentProvider();
|
customContentProvider = new CustomContentProvider() {
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
package de.dennisguse.opentracks.content;
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
@@ -32,7 +33,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
|
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
|
||||||
@@ -40,7 +40,6 @@ import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,11 +54,12 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||||
private static final String TEST_DESC = "Test Desc!";
|
private static final String TEST_DESC = "Test Desc!";
|
||||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
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 Context context = ApplicationProvider.getApplicationContext();
|
||||||
private ContentProviderUtils providerUtils;
|
private ContentProviderUtils providerUtils;
|
||||||
private double INITIAL_LATITUDE = 37.0;
|
|
||||||
private double INITIAL_LONGITUDE = -57.0;
|
|
||||||
private double ALTITUDE_INTERVAL = 2.5;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Cursor cursorMock;
|
private Cursor cursorMock;
|
||||||
@@ -136,8 +136,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
long lastPointId = initializeTrack(trackId, numPoints);
|
long lastPointId = initializeTrack(trackId, numPoints);
|
||||||
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
|
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
|
||||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||||
LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory);
|
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
||||||
try {
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
Location loc = it.next();
|
Location loc = it.next();
|
||||||
Assert.assertNotNull(loc);
|
Assert.assertNotNull(loc);
|
||||||
@@ -147,8 +146,6 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
: lastPointId - numPoints + locations.size(), it.getLocationId());
|
: lastPointId - numPoints + locations.size(), it.getLocationId());
|
||||||
}
|
}
|
||||||
Assert.assertEquals(numPoints, locations.size());
|
Assert.assertEquals(numPoints, locations.size());
|
||||||
} finally {
|
|
||||||
it.close();
|
|
||||||
}
|
}
|
||||||
return locations;
|
return locations;
|
||||||
}
|
}
|
||||||
@@ -176,15 +173,12 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
// Load all inserted locations.
|
// Load all inserted locations.
|
||||||
long lastPointId = -1;
|
long lastPointId = -1;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
||||||
try {
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
it.next();
|
||||||
lastPointId = it.getLocationId();
|
lastPointId = it.getLocationId();
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
it.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
||||||
@@ -219,7 +213,6 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
Track track = providerUtils.createTrack(cursorMock);
|
Track track = providerUtils.createTrack(cursorMock);
|
||||||
Assert.assertEquals(trackId, track.getId());
|
Assert.assertEquals(trackId, track.getId());
|
||||||
Assert.assertEquals(name, track.getName());
|
Assert.assertEquals(name, track.getName());
|
||||||
verify(cursorMock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -269,26 +262,20 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
providerUtils.insertWaypoint(waypoint);
|
providerUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null,
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
TracksColumns._ID);
|
|
||||||
Assert.assertEquals(3, tracksCursor.getCount());
|
Assert.assertEquals(3, tracksCursor.getCount());
|
||||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null,
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||||
null, TrackPointsColumns._ID);
|
|
||||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null,
|
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
null, WaypointsColumns._ID);
|
|
||||||
Assert.assertEquals(1, waypointCursor.getCount());
|
Assert.assertEquals(1, waypointCursor.getCount());
|
||||||
// Delete one track.
|
// Delete one track.
|
||||||
providerUtils.deleteTrack(context, trackId);
|
providerUtils.deleteTrack(context, trackId);
|
||||||
// Check whether all data of a track has been deleted.
|
// Check whether all data of a track has been deleted.
|
||||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null,
|
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
TracksColumns._ID);
|
|
||||||
Assert.assertEquals(2, tracksCursor.getCount());
|
Assert.assertEquals(2, tracksCursor.getCount());
|
||||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null,
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||||
null, TrackPointsColumns._ID);
|
|
||||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null,
|
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
null, WaypointsColumns._ID);
|
|
||||||
Assert.assertEquals(0, waypointCursor.getCount());
|
Assert.assertEquals(0, waypointCursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,12 +360,12 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
waypoint.setType(WaypointType.STATISTICS);
|
waypoint.setType(WaypointType.STATISTICS);
|
||||||
waypoint.setTripStatistics(statistics);
|
waypoint.setTripStatistics(statistics);
|
||||||
|
|
||||||
Location loc = new Location("test");
|
Location location = new Location("test");
|
||||||
loc.setLatitude(22);
|
location.setLatitude(22);
|
||||||
loc.setLongitude(22);
|
location.setLongitude(22);
|
||||||
loc.setAccuracy((float) 1 / 100.0f);
|
location.setAccuracy((float) 1 / 100.0f);
|
||||||
loc.setAltitude(2.5);
|
location.setAltitude(2.5);
|
||||||
waypoint.setLocation(loc);
|
waypoint.setLocation(location);
|
||||||
providerUtils.insertWaypoint(waypoint);
|
providerUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
|
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
|
||||||
@@ -425,7 +412,6 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
Assert.assertEquals(id, waypoint.getId());
|
Assert.assertEquals(id, waypoint.getId());
|
||||||
Assert.assertEquals(name, waypoint.getName());
|
Assert.assertEquals(name, waypoint.getName());
|
||||||
Assert.assertEquals(trackId, waypoint.getTrackId());
|
Assert.assertEquals(trackId, waypoint.getTrackId());
|
||||||
verify(cursorMock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -455,8 +441,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateTrackDescription(Track aTrack, Vector<Double> distances,
|
public String generateTrackDescription(Track aTrack, boolean html) {
|
||||||
Vector<Double> elevations, boolean html) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -489,19 +474,22 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
statistics.setMaxGrade(15.0);
|
statistics.setMaxGrade(15.0);
|
||||||
statistics.setMinGrade(-25.0);
|
statistics.setMinGrade(-25.0);
|
||||||
statistics.setBounds(-10000, 20000, 30000, -40000);
|
statistics.setBounds(-10000, 20000, 30000, -40000);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
Waypoint waypoint1 = new Waypoint();
|
Waypoint waypoint1 = new Waypoint();
|
||||||
waypoint1.setDescription(Long.toString(trackId));
|
waypoint1.setDescription(Long.toString(trackId));
|
||||||
waypoint1.setTrackId(trackId);
|
waypoint1.setTrackId(trackId);
|
||||||
waypoint1.setType(WaypointType.STATISTICS);
|
waypoint1.setType(WaypointType.STATISTICS);
|
||||||
waypoint1.setTripStatistics(statistics);
|
waypoint1.setTripStatistics(statistics);
|
||||||
providerUtils.insertWaypoint(waypoint1);
|
long waypoint1Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
|
||||||
|
|
||||||
Waypoint waypoint2 = new Waypoint();
|
Waypoint waypoint2 = new Waypoint();
|
||||||
waypoint2.setDescription(Long.toString(trackId));
|
waypoint2.setDescription(Long.toString(trackId));
|
||||||
waypoint2.setTrackId(trackId);
|
waypoint2.setTrackId(trackId);
|
||||||
waypoint2.setType(WaypointType.STATISTICS);
|
waypoint2.setType(WaypointType.STATISTICS);
|
||||||
waypoint2.setTripStatistics(statistics);
|
waypoint2.setTripStatistics(statistics);
|
||||||
providerUtils.insertWaypoint(waypoint2);
|
long waypoint2Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
|
||||||
|
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||||
@@ -511,15 +499,16 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateTrackDescription(Track aTrack, Vector<Double> distances,
|
public String generateTrackDescription(Track aTrack, boolean html) {
|
||||||
Vector<Double> elevations, boolean html) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
|
|
||||||
|
|
||||||
Assert.assertNull(providerUtils.getWaypoint(1));
|
Assert.assertNotNull(providerUtils.getWaypoint(waypoint1Id));
|
||||||
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(2)
|
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
|
||||||
|
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
|
||||||
|
|
||||||
|
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
||||||
.getDescription());
|
.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,13 +523,15 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
Waypoint waypoint1 = new Waypoint();
|
Waypoint waypoint1 = new Waypoint();
|
||||||
waypoint1.setTrackId(trackId);
|
waypoint1.setTrackId(trackId);
|
||||||
|
long waypoint1id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
|
||||||
|
|
||||||
Waypoint waypoint2 = new Waypoint();
|
Waypoint waypoint2 = new Waypoint();
|
||||||
waypoint2.setTrackId(trackId);
|
waypoint2.setTrackId(trackId);
|
||||||
providerUtils.insertWaypoint(waypoint1);
|
long waypoint2id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
|
||||||
providerUtils.insertWaypoint(waypoint2);
|
|
||||||
|
|
||||||
Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1));
|
Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1));
|
||||||
Assert.assertEquals(1L, providerUtils.getFirstWaypointId(trackId));
|
Assert.assertEquals(waypoint1id, providerUtils.getFirstWaypointId(trackId));
|
||||||
|
Assert.assertEquals(waypoint2id, providerUtils.getLastWaypoint(trackId, WaypointType.WAYPOINT).getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -615,9 +606,9 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
Waypoint waypoint = new Waypoint();
|
Waypoint waypoint = new Waypoint();
|
||||||
waypoint.setDescription(TEST_DESC);
|
waypoint.setDescription(TEST_DESC);
|
||||||
waypoint.setTrackId(trackId);
|
waypoint.setTrackId(trackId);
|
||||||
providerUtils.insertWaypoint(waypoint);
|
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
|
||||||
|
|
||||||
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(1).getDescription());
|
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(waypointId).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -632,13 +623,14 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
Waypoint waypoint = new Waypoint();
|
Waypoint waypoint = new Waypoint();
|
||||||
waypoint.setDescription(TEST_DESC);
|
waypoint.setDescription(TEST_DESC);
|
||||||
waypoint.setTrackId(trackId);
|
waypoint.setTrackId(trackId);
|
||||||
providerUtils.insertWaypoint(waypoint);
|
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
waypoint = providerUtils.getWaypoint(1);
|
waypoint = providerUtils.getWaypoint(waypointId);
|
||||||
waypoint.setDescription(TEST_DESC_NEW);
|
waypoint.setDescription(TEST_DESC_NEW);
|
||||||
providerUtils.updateWaypoint(waypoint);
|
providerUtils.updateWaypoint(waypoint);
|
||||||
|
|
||||||
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(1).getDescription());
|
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(waypointId).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -714,7 +706,6 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
|
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
|
||||||
Assert.assertEquals(time, location.getTime(), 0.01);
|
Assert.assertEquals(time, location.getTime(), 0.01);
|
||||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
||||||
verify(cursorMock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -742,7 +733,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
Track track = getTrack(trackId, 10);
|
Track track = getTrack(trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
insertTrackWithLocations(track);
|
||||||
|
|
||||||
Assert.assertEquals(1, providerUtils.getFirstTrackPointId(trackId));
|
Assert.assertNotEquals(-1L, providerUtils.getFirstTrackPointId(trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -755,7 +746,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
Track track = getTrack(trackId, 10);
|
Track track = getTrack(trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
insertTrackWithLocations(track);
|
||||||
|
|
||||||
Assert.assertEquals(10, providerUtils.getLastTrackPointId(trackId));
|
Assert.assertTrue(providerUtils.getFirstTrackPointId(trackId) < providerUtils.getLastTrackPointId(trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -773,35 +764,41 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||||
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
|
|
||||||
* in descending.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_desc() {
|
public void testGetTrackPointCursor_desc() {
|
||||||
// Insert track, points at first.
|
// Insert track, points at first.
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = getTrack(trackId, 10);
|
Track track = getTrack(trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
providerUtils.insertTrack(track);
|
||||||
|
|
||||||
Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, true);
|
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||||
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
|
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
||||||
Assert.assertEquals(2, cursor.getCount());
|
Assert.assertEquals(2, cursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||||
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
|
|
||||||
* in ascending.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_asc() {
|
public void testGetTrackPointCursor_asc() {
|
||||||
// Insert track, points at first.
|
// Insert track, points at first.
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = getTrack(trackId, 10);
|
Track track = getTrack(trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
providerUtils.insertTrack(track);
|
||||||
|
|
||||||
Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, false);
|
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||||
Assert.assertEquals(5, cursor.getCount());
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
|
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
||||||
|
Assert.assertEquals(2, cursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -812,40 +809,48 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
// Insert track, points at first.
|
// Insert track, points at first.
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = getTrack(trackId, 10);
|
Track track = getTrack(trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
providerUtils.insertTrack(track);
|
||||||
|
|
||||||
long startTrackPointId = 2L;
|
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||||
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
|
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
long startTrackPointId = trackpointIds[9];
|
||||||
|
|
||||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||||
for (int i = 1; i >= 0; i--) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
Assert.assertTrue(locationIterator.hasNext());
|
Assert.assertTrue(locationIterator.hasNext());
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId());
|
Assert.assertEquals(startTrackPointId - i, locationIterator.getLocationId());
|
||||||
checkLocation(i, location);
|
checkLocation((trackpointIds.length - 1) - i, location);
|
||||||
}
|
}
|
||||||
Assert.assertFalse(locationIterator.hasNext());
|
Assert.assertFalse(locationIterator.hasNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
||||||
* {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)}
|
|
||||||
* in ascending.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_asc() {
|
public void testGetTrackPointLocationIterator_asc() {
|
||||||
// Insert track, point at first.
|
// Insert track, point at first.
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = getTrack(trackId, 10);
|
Track track = getTrack(trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
providerUtils.insertTrack(track);
|
||||||
|
|
||||||
long startTrackPointId = 2L;
|
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||||
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
|
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
long startTrackPointId = trackpointIds[0];
|
||||||
|
|
||||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||||
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
for (int i = 1; i < 10; i++) {
|
|
||||||
Assert.assertTrue(locationIterator.hasNext());
|
Assert.assertTrue(locationIterator.hasNext());
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId());
|
Assert.assertEquals(startTrackPointId + i, locationIterator.getLocationId());
|
||||||
|
|
||||||
checkLocation(i, location);
|
checkLocation(i, location);
|
||||||
}
|
}
|
||||||
Assert.assertFalse(locationIterator.hasNext());
|
Assert.assertFalse(locationIterator.hasNext());
|
||||||
@@ -887,8 +892,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
/**
|
/**
|
||||||
* Checks the value of a location.
|
* Checks the value of a location.
|
||||||
*
|
*
|
||||||
* @param i the index of this location which created in the method
|
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
||||||
* {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
|
||||||
* @param location the location to be checked
|
* @param location the location to be checked
|
||||||
*/
|
*/
|
||||||
private void checkLocation(int i, Location location) {
|
private void checkLocation(int i, Location location) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, java.util.Vector, java.util.Vector, boolean)}.
|
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, boolean)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateTrackDescription() {
|
public void testGenerateTrackDescription() {
|
||||||
@@ -88,7 +88,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
+ "Min grade: 11 %<br>"
|
+ "Min grade: 11 %<br>"
|
||||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||||
|
|
||||||
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, null, null, true));
|
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,8 +128,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder,
|
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, int, String)}.
|
||||||
* int, String)}.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWriteDistance() {
|
public void testWriteDistance() {
|
||||||
@@ -139,8 +138,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int,
|
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, String)}.
|
||||||
* String)}.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWriteTime() {
|
public void testWriteTime() {
|
||||||
@@ -150,8 +148,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder,
|
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, int, String)}.
|
||||||
* int, String)}.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWriteSpeed() {
|
public void testWriteSpeed() {
|
||||||
@@ -161,8 +158,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder,
|
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, int, String)}.
|
||||||
* int, String)}.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWriteElevation() {
|
public void testWriteElevation() {
|
||||||
@@ -172,8 +168,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int,
|
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, String)}.
|
||||||
* String)}.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWritePace() {
|
public void testWritePace() {
|
||||||
@@ -183,8 +178,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
|
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)}.
|
||||||
* int, String)}.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWriteGrade() {
|
public void testWriteGrade() {
|
||||||
@@ -194,8 +188,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
|
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with a NaN.
|
||||||
* int, String)} with a NaN.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWriteGrade_nan() {
|
public void testWriteGrade_nan() {
|
||||||
@@ -205,8 +198,7 @@ public class DescriptionGeneratorImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
|
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with an infinite number.
|
||||||
* int, String)} with an infinite number.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testWriteGrade_infinite() {
|
public void testWriteGrade_infinite() {
|
||||||
|
|||||||
@@ -40,9 +40,8 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SearchEngine}.
|
* Tests for {@link SearchEngine}.
|
||||||
* These are not meant to be quality tests, but instead feature-by-feature tests
|
* These are not meant to be quality tests, but instead feature-by-feature tests.
|
||||||
* (in other words, they don't test the mixing of different score boostings, just
|
* In other words, they don't test the mixing of different score boostings, just each boosting separately.
|
||||||
* each boosting separately)
|
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
@@ -50,7 +49,7 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
|||||||
public class SearchEngineTest {
|
public class SearchEngineTest {
|
||||||
|
|
||||||
private static final Location HERE = new Location("gps");
|
private static final Location HERE = new Location("gps");
|
||||||
private static final long NOW = 1234567890000L; // After OLDEST_ALLOWED_TIMESTAMP
|
private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000; // After OLDEST_ALLOWED_TIMESTAMP
|
||||||
private ContentProviderUtils providerUtils;
|
private ContentProviderUtils providerUtils;
|
||||||
private SearchEngine engine;
|
private SearchEngine engine;
|
||||||
|
|
||||||
|
|||||||
@@ -1,761 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2011 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks.content;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.location.Location;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.isA;
|
|
||||||
import static org.mockito.Mockito.reset;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link TrackDataHub}.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class TrackDataHubTest {
|
|
||||||
private static final long TRACK_ID = 42L;
|
|
||||||
private static final int TARGET_POINTS = 50;
|
|
||||||
|
|
||||||
private Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private DataSource dataSource;
|
|
||||||
|
|
||||||
private TrackDataHub trackDataHub;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private TrackDataListener trackDataListener1;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private TrackDataListener trackDataListener2;
|
|
||||||
private ArgumentCaptor<OnSharedPreferenceChangeListener> preferenceChangeListenerCapture = ArgumentCaptor.forClass(OnSharedPreferenceChangeListener.class);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
|
||||||
trackDataHub = new TrackDataHub(context, new TrackDataManager(), contentProviderUtils, TARGET_POINTS) {
|
|
||||||
@Override
|
|
||||||
protected DataSource newDataSource() {
|
|
||||||
return dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void runInHandlerThread(Runnable runnable) {
|
|
||||||
// Run everything in the same thread
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, TRACK_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
reset(dataSource);
|
|
||||||
|
|
||||||
// Expect everything to be unregistered.
|
|
||||||
dataSource.unregisterContentObserver(isA(ContentObserver.class));
|
|
||||||
//TODO
|
|
||||||
// AndroidMock.expectLastCall().times(3);
|
|
||||||
dataSource.unregisterOnSharedPreferenceChangeListener(isA(OnSharedPreferenceChangeListener.class));
|
|
||||||
verify(dataSource);
|
|
||||||
trackDataHub.stop();
|
|
||||||
trackDataHub = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests registering for tracks table update.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTracksTableUpdate() {
|
|
||||||
// Register two listeners
|
|
||||||
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
Track track = new Track();
|
|
||||||
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
|
|
||||||
dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), contentObserverCapture.capture());
|
|
||||||
trackDataListener1.onTrackUpdated(track);
|
|
||||||
trackDataListener2.onTrackUpdated(track);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.start();
|
|
||||||
trackDataHub.loadTrack(TRACK_ID);
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE));
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.TRACKS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Causes tracks table update
|
|
||||||
ContentObserver contentObserver = contentObserverCapture.getValue();
|
|
||||||
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
|
|
||||||
trackDataListener1.onTrackUpdated(track);
|
|
||||||
trackDataListener2.onTrackUpdated(track);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
contentObserver.onChange(false);
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Unregister one listener
|
|
||||||
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
|
|
||||||
trackDataListener2.onTrackUpdated(track);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.unregisterTrackDataListener(trackDataListener1);
|
|
||||||
contentObserver.onChange(false);
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Unregister the second listener
|
|
||||||
dataSource.unregisterContentObserver(contentObserver);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.unregisterTrackDataListener(trackDataListener2);
|
|
||||||
contentObserver.onChange(false);
|
|
||||||
verifyAndReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO
|
|
||||||
/**
|
|
||||||
* Tests registering for waypoints table update.
|
|
||||||
*/
|
|
||||||
// @Test
|
|
||||||
// public void testWaypointsTableUpdate() {
|
|
||||||
// Waypoint waypoint1 = new Waypoint();
|
|
||||||
// Waypoint waypoint2 = new Waypoint();
|
|
||||||
// Waypoint waypoint3 = new Waypoint();
|
|
||||||
// Waypoint waypoint4 = new Waypoint();
|
|
||||||
// Location location = new Location("gps");
|
|
||||||
// location.setLatitude(10.0);
|
|
||||||
// location.setLongitude(8.0);
|
|
||||||
// waypoint1.setLocation(location);
|
|
||||||
// waypoint2.setLocation(location);
|
|
||||||
// waypoint3.setLocation(location);
|
|
||||||
// waypoint4.setLocation(location);
|
|
||||||
//
|
|
||||||
// // Register two listeners
|
|
||||||
// ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
// when(contentProviderUtils.getWaypointCursor(
|
|
||||||
// eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
|
|
||||||
// .andStubAnswer(new FixedSizeCursorAnswer(2));
|
|
||||||
// when(contentProviderUtils.createWaypoint(isA(Cursor.class)))
|
|
||||||
// .thenReturn(waypoint1)
|
|
||||||
// .thenReturn(waypoint2)
|
|
||||||
// .thenReturn(waypoint1)
|
|
||||||
// .thenReturn(waypoint2);
|
|
||||||
// dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), contentObserverCapture.capture());
|
|
||||||
// trackDataListener1.clearWaypoints();
|
|
||||||
// trackDataListener2.clearWaypoints();
|
|
||||||
// trackDataListener1.onNewWaypoint(waypoint1);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint1);
|
|
||||||
// trackDataListener1.onNewWaypoint(waypoint2);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint2);
|
|
||||||
// trackDataListener1.onNewWaypointsDone();
|
|
||||||
// trackDataListener2.onNewWaypointsDone();
|
|
||||||
// replay();
|
|
||||||
//
|
|
||||||
// trackDataHub.start();
|
|
||||||
// trackDataHub.loadTrack(TRACK_ID);
|
|
||||||
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
|
|
||||||
// trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
|
|
||||||
// verifyAndReset();
|
|
||||||
//
|
|
||||||
// // Cause waypoints table update
|
|
||||||
// ContentObserver contentObserver = contentObserverCapture.getValue();
|
|
||||||
// when(contentProviderUtils.getWaypointCursor(
|
|
||||||
// eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
|
|
||||||
// .andStubAnswer(new FixedSizeCursorAnswer(3));
|
|
||||||
// when(contentProviderUtils.createWaypoint(isA(Cursor.class)))
|
|
||||||
// .thenReturn(waypoint1)
|
|
||||||
// .thenReturn(waypoint2)
|
|
||||||
// .thenReturn(waypoint3);
|
|
||||||
// trackDataListener1.clearWaypoints();
|
|
||||||
// trackDataListener2.clearWaypoints();
|
|
||||||
// trackDataListener1.onNewWaypoint(waypoint1);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint1);
|
|
||||||
// trackDataListener1.onNewWaypoint(waypoint2);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint2);
|
|
||||||
// trackDataListener1.onNewWaypoint(waypoint3);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint3);
|
|
||||||
// trackDataListener1.onNewWaypointsDone();
|
|
||||||
// trackDataListener2.onNewWaypointsDone();
|
|
||||||
// replay();
|
|
||||||
//
|
|
||||||
// contentObserver.onChange(false);
|
|
||||||
// verifyAndReset();
|
|
||||||
//
|
|
||||||
// // Unregister one listener
|
|
||||||
// when(contentProviderUtils.getWaypointCursor(
|
|
||||||
// eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
|
|
||||||
// .andStubAnswer(new FixedSizeCursorAnswer(4));
|
|
||||||
// when(contentProviderUtils.createWaypoint(isA(Cursor.class)))
|
|
||||||
// .thenReturn(waypoint1)
|
|
||||||
// .thenReturn(waypoint2)
|
|
||||||
// .thenReturn(waypoint3)
|
|
||||||
// .thenReturn(waypoint4);
|
|
||||||
// trackDataListener2.clearWaypoints();
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint1);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint2);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint3);
|
|
||||||
// trackDataListener2.onNewWaypoint(waypoint4);
|
|
||||||
// trackDataListener2.onNewWaypointsDone();
|
|
||||||
// replay();
|
|
||||||
//
|
|
||||||
// trackDataHub.unregisterTrackDataListener(trackDataListener1);
|
|
||||||
// contentObserver.onChange(false);
|
|
||||||
// verifyAndReset();
|
|
||||||
//
|
|
||||||
// // Unregister the second listener
|
|
||||||
// dataSource.unregisterContentObserver(contentObserver);
|
|
||||||
// replay();
|
|
||||||
//
|
|
||||||
// trackDataHub.unregisterTrackDataListener(trackDataListener2);
|
|
||||||
// contentObserver.onChange(false);
|
|
||||||
// verifyAndReset();
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests track points table update.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTrackPointsTableUpdate() {
|
|
||||||
// Register one listener
|
|
||||||
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
|
|
||||||
|
|
||||||
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
|
|
||||||
trackDataListener1.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener1);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.start();
|
|
||||||
trackDataHub.loadTrack(TRACK_ID);
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Register a second listener
|
|
||||||
locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
|
|
||||||
trackDataListener2.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener2);
|
|
||||||
trackDataListener2.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Deliver more points - should go to both listeners without clearing
|
|
||||||
ContentObserver contentObserver = contentObserverCapture.getValue();
|
|
||||||
locationIterator = new FixedSizeLocationIterator(11, 10, 1);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(11L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(20L);
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener1);
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener2);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
trackDataListener2.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
contentObserver.onChange(false);
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Unregister one listener and change track
|
|
||||||
locationIterator = new FixedSizeLocationIterator(101, 10);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
|
|
||||||
.thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).thenReturn(110L);
|
|
||||||
trackDataListener2.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener2);
|
|
||||||
trackDataListener2.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.unregisterTrackDataListener(trackDataListener1);
|
|
||||||
trackDataHub.loadTrack(TRACK_ID + 1);
|
|
||||||
verifyAndReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests track points table update with registering the same listener.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTrackPointsTableUpdate_reRegister() {
|
|
||||||
// Register one listener
|
|
||||||
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
|
|
||||||
|
|
||||||
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(
|
|
||||||
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
|
|
||||||
|
|
||||||
trackDataListener1.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener1);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.start();
|
|
||||||
trackDataHub.loadTrack(TRACK_ID);
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Unregister the listener
|
|
||||||
ContentObserver observer = contentObserverCapture.getValue();
|
|
||||||
dataSource.unregisterContentObserver(observer);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.unregisterTrackDataListener(trackDataListener1);
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Register again
|
|
||||||
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
|
|
||||||
locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
|
|
||||||
trackDataListener1.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener1);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests tracks point able change. Register a listener after a track change.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTrackPointsTableUpdate_reRegisterAfterTrackChange() {
|
|
||||||
// Register one listener
|
|
||||||
ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
|
|
||||||
|
|
||||||
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(
|
|
||||||
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(10L);
|
|
||||||
trackDataListener1.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener1);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.start();
|
|
||||||
trackDataHub.loadTrack(TRACK_ID);
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Unregister the listener
|
|
||||||
ContentObserver observer = observerCapture.getValue();
|
|
||||||
dataSource.unregisterContentObserver(observer);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.unregisterTrackDataListener(trackDataListener1);
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Register the listener after a new track
|
|
||||||
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
|
|
||||||
locationIterator = new FixedSizeLocationIterator(1, 10);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
|
|
||||||
.thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).thenReturn(10L);
|
|
||||||
trackDataListener1.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener1);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.loadTrack(TRACK_ID + 1);
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO
|
|
||||||
/**
|
|
||||||
* Tests track points table update with large track sampling.
|
|
||||||
*/
|
|
||||||
// @Test
|
|
||||||
// public void testTrackPointsTableUpdate_largeTrackSampling() {
|
|
||||||
// ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
// dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
|
|
||||||
//
|
|
||||||
// when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(200L);
|
|
||||||
// AndroidMock.expectLastCall().anyTimes();
|
|
||||||
// FixedSizeLocationIterator locationIterator1 = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120);
|
|
||||||
// when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator1);
|
|
||||||
// FixedSizeLocationIterator locationIterator2 = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120);
|
|
||||||
// when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator2);
|
|
||||||
//
|
|
||||||
// trackDataListener1.clearTrackPoints();
|
|
||||||
// locationIterator1.expectSampledLocationsDelivered(trackDataListener1, 4, false);
|
|
||||||
// trackDataListener1.onNewTrackPointsDone();
|
|
||||||
// trackDataListener2.clearTrackPoints();
|
|
||||||
// locationIterator2.expectSampledLocationsDelivered(trackDataListener2, 4, true);
|
|
||||||
// trackDataListener2.onNewTrackPointsDone();
|
|
||||||
// replay();
|
|
||||||
//
|
|
||||||
// trackDataHub.start();
|
|
||||||
// trackDataHub.loadTrack(TRACK_ID);
|
|
||||||
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
// trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
|
||||||
// verifyAndReset();
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests track points table update with resampling.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTrackPointsTableUpdate_resampling() {
|
|
||||||
ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
|
|
||||||
|
|
||||||
// Deliver 30 points (no sampling happens)
|
|
||||||
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(
|
|
||||||
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(30L);
|
|
||||||
|
|
||||||
trackDataListener1.clearTrackPoints();
|
|
||||||
locationIterator.expectLocationsDelivered(trackDataListener1);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.start();
|
|
||||||
trackDataHub.loadTrack(TRACK_ID);
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Now deliver 30 more (incrementally sampled)
|
|
||||||
ContentObserver observer = observerCapture.getValue();
|
|
||||||
locationIterator = new FixedSizeLocationIterator(31, 30);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(eq(TRACK_ID), eq(31L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(60L);
|
|
||||||
locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
observer.onChange(false);
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Now another 30 (triggers resampling)
|
|
||||||
locationIterator = new FixedSizeLocationIterator(1, 90);
|
|
||||||
when(contentProviderUtils.getTrackPointLocationIterator(
|
|
||||||
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).thenReturn(locationIterator);
|
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID)).thenReturn(90L);
|
|
||||||
trackDataListener1.clearTrackPoints();
|
|
||||||
locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false);
|
|
||||||
trackDataListener1.onNewTrackPointsDone();
|
|
||||||
replay();
|
|
||||||
|
|
||||||
observer.onChange(false);
|
|
||||||
verifyAndReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests preferences change.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testPreferencesChange() throws Exception {
|
|
||||||
// Register two listeners
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT);
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
|
||||||
|
|
||||||
dataSource.registerOnSharedPreferenceChangeListener(preferenceChangeListenerCapture.capture());
|
|
||||||
when(trackDataListener1.onMetricUnitsChanged(true)).thenReturn(false);
|
|
||||||
when(trackDataListener1.onReportSpeedChanged(true)).thenReturn(false);
|
|
||||||
when(trackDataListener1.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)).thenReturn(false);
|
|
||||||
when(trackDataListener1.onRecordingDistanceIntervalChanged(PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).thenReturn(false);
|
|
||||||
|
|
||||||
when(trackDataListener2.onMetricUnitsChanged(true)).thenReturn(false);
|
|
||||||
when(trackDataListener2.onReportSpeedChanged(true)).thenReturn(false);
|
|
||||||
when(trackDataListener2.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)).thenReturn(false);
|
|
||||||
when(trackDataListener2.onRecordingDistanceIntervalChanged(PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).thenReturn(false);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
trackDataHub.start();
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.PREFERENCE));
|
|
||||||
trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.PREFERENCE));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Change report speed to false
|
|
||||||
when(trackDataListener1.onReportSpeedChanged(false)).thenReturn(false);
|
|
||||||
when(trackDataListener2.onReportSpeedChanged(false)).thenReturn(false);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_rate_key, context.getString(R.string.stats_rate_pace));
|
|
||||||
OnSharedPreferenceChangeListener listener = preferenceChangeListenerCapture.getValue();
|
|
||||||
listener.onSharedPreferenceChanged(sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_rate_key));
|
|
||||||
verifyAndReset();
|
|
||||||
|
|
||||||
// Change metric units to false
|
|
||||||
when(trackDataListener1.onMetricUnitsChanged(false)).thenReturn(false);
|
|
||||||
when(trackDataListener2.onMetricUnitsChanged(false)).thenReturn(false);
|
|
||||||
replay();
|
|
||||||
|
|
||||||
String imperialUnits = context.getString(R.string.stats_units_imperial);
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits);
|
|
||||||
listener.onSharedPreferenceChanged(sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_units_key));
|
|
||||||
verifyAndReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replays mocks.
|
|
||||||
*/
|
|
||||||
private void replay() {
|
|
||||||
// AndroidMock.replay(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies and resets mocks.
|
|
||||||
*/
|
|
||||||
private void verifyAndReset() {
|
|
||||||
verify(contentProviderUtils);
|
|
||||||
verify(dataSource);
|
|
||||||
verify(trackDataListener1);
|
|
||||||
verify(trackDataListener2);
|
|
||||||
reset(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO
|
|
||||||
/**
|
|
||||||
* Tests the method {@link TrackDataHub#start()}.
|
|
||||||
*/
|
|
||||||
// @Test
|
|
||||||
// public void testRegisterTracksTableListener() {
|
|
||||||
// ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
// dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), observerCapture.capture());
|
|
||||||
// Track track = TrackStubUtils.createTrack(1);
|
|
||||||
//
|
|
||||||
// when(contentProviderUtils.getTrack(capture(new Capture<Long>())).thenReturn(track);
|
|
||||||
// // Make the track id is unique.
|
|
||||||
// PreferencesUtils.setLong(context, R.string.recording_track_id_key, System.currentTimeMillis());
|
|
||||||
// trackDataListener1.onTrackUpdated(track);
|
|
||||||
// replay();
|
|
||||||
// trackDataHub.start();
|
|
||||||
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE));
|
|
||||||
// verifyAndReset();
|
|
||||||
// }
|
|
||||||
|
|
||||||
//TODO
|
|
||||||
/**
|
|
||||||
* Tests the method {@link TrackDataHub#start()}.
|
|
||||||
*/
|
|
||||||
// @Test
|
|
||||||
// public void testRegisterWaypointsTableListener() {
|
|
||||||
// ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
|
|
||||||
// dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), observerCapture.capture());
|
|
||||||
// when(contentProviderUtils.getWaypointCursor(
|
|
||||||
// capture(new Capture<Long>()), capture(new Capture<Long>()),
|
|
||||||
// capture(new Capture<Integer>()))).thenReturn(null);
|
|
||||||
// trackDataListener1.clearWaypoints();
|
|
||||||
// trackDataListener1.onNewWaypointsDone();
|
|
||||||
// replay();
|
|
||||||
// trackDataHub.start();
|
|
||||||
// trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
|
|
||||||
// verifyAndReset();
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when
|
|
||||||
* the key is R.string.min_required_accuracy_key.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testNotifyPreferenceChanged_minRequiredAccuracy() {
|
|
||||||
int value = 1;
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value);
|
|
||||||
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key));
|
|
||||||
Assert.assertEquals(value, trackDataHub.getRecordingGpsAccuracy());
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value + 1);
|
|
||||||
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key));
|
|
||||||
Assert.assertEquals(value + 1, trackDataHub.getRecordingGpsAccuracy());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when
|
|
||||||
* the key is R.string.metric_units_key.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testNotifyPreferenceChanged_metricUnitsNoNotify() {
|
|
||||||
String imperialUnits = context.getString(R.string.stats_units_imperial);
|
|
||||||
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits);
|
|
||||||
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_units_key));
|
|
||||||
Assert.assertFalse(trackDataHub.isMetricUnits());
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
|
||||||
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_units_key));
|
|
||||||
Assert.assertTrue(trackDataHub.isMetricUnits());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when
|
|
||||||
* the key is R.string.metric_units_key.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testNotifyPreferenceChanged_reportSpeedNoNotify() {
|
|
||||||
String value = context.getString(R.string.stats_rate_pace);
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_rate_key, value);
|
|
||||||
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_rate_key));
|
|
||||||
Assert.assertFalse(trackDataHub.isReportSpeed());
|
|
||||||
|
|
||||||
value = context.getString(R.string.stats_rate_speed);
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_rate_key, value);
|
|
||||||
trackDataHub.notifyPreferenceChanged(PreferencesUtils.getKey(context, R.string.stats_rate_key));
|
|
||||||
Assert.assertTrue(trackDataHub.isReportSpeed());
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO
|
|
||||||
/**
|
|
||||||
* Fixed size cursor answer.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
private static class FixedSizeCursorAnswer {
|
|
||||||
private final int size;
|
|
||||||
|
|
||||||
public FixedSizeCursorAnswer(int size) {
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public Cursor answer() throws Throwable {
|
|
||||||
// MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID});
|
|
||||||
// for (long i = 0; i < size; i++) {
|
|
||||||
// cursor.addRow(new Object[]{i});
|
|
||||||
// }
|
|
||||||
// return cursor;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fixed size location iterator.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
private static class FixedSizeLocationIterator implements LocationIterator {
|
|
||||||
private final long startId;
|
|
||||||
private final Location[] locations;
|
|
||||||
private final Set<Integer> splitIndexSet = new HashSet<Integer>();
|
|
||||||
private int currentIndex = -1;
|
|
||||||
|
|
||||||
public FixedSizeLocationIterator(long startId, int size) {
|
|
||||||
this(startId, size, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FixedSizeLocationIterator(long startId, int size, int... splitIndexes) {
|
|
||||||
this.startId = startId;
|
|
||||||
this.locations = new Location[size];
|
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
Location location = new Location("gps");
|
|
||||||
location.setLatitude(-15.0 + i / 1000.0);
|
|
||||||
location.setLongitude(37 + i / 1000.0);
|
|
||||||
location.setAltitude(i);
|
|
||||||
locations[i] = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (splitIndexes != null) {
|
|
||||||
for (int splitIndex : splitIndexes) {
|
|
||||||
splitIndexSet.add(splitIndex);
|
|
||||||
|
|
||||||
Location splitLocation = locations[splitIndex];
|
|
||||||
splitLocation.setLatitude(100.0);
|
|
||||||
splitLocation.setLongitude(200.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void expectLocationsDelivered(TrackDataListener listener) {
|
|
||||||
for (int i = 0; i < locations.length; i++) {
|
|
||||||
if (splitIndexSet.contains(i)) {
|
|
||||||
listener.onSegmentSplit(locations[i]);
|
|
||||||
} else {
|
|
||||||
listener.onSampledInTrackPoint(locations[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void expectSampledLocationsDelivered(
|
|
||||||
TrackDataListener listener, int sampleFrequency, boolean includeSampledOut) {
|
|
||||||
boolean includeNext = false;
|
|
||||||
for (int i = 0; i < locations.length; i++) {
|
|
||||||
if (splitIndexSet.contains(i)) {
|
|
||||||
listener.onSegmentSplit(locations[i]);
|
|
||||||
includeNext = true;
|
|
||||||
} else if (includeNext || (i % sampleFrequency == 0)) {
|
|
||||||
listener.onSampledInTrackPoint(locations[i]);
|
|
||||||
includeNext = false;
|
|
||||||
} else if (includeSampledOut) {
|
|
||||||
listener.onSampledOutTrackPoint(locations[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return currentIndex < locations.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location next() {
|
|
||||||
currentIndex++;
|
|
||||||
return locations[currentIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getLocationId() {
|
|
||||||
return startId + currentIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks.content;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the WaypointCreationRequest class.
|
|
||||||
* {@link WaypointCreationRequest}
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class WaypointCreationRequestTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTypeParceling() {
|
|
||||||
WaypointCreationRequest original = WaypointCreationRequest.DEFAULT_WAYPOINT;
|
|
||||||
Parcel p = Parcel.obtain();
|
|
||||||
original.writeToParcel(p, 0);
|
|
||||||
p.setDataPosition(0);
|
|
||||||
WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p);
|
|
||||||
Assert.assertEquals(original.getType(), copy.getType());
|
|
||||||
Assert.assertFalse(copy.isTrackStatistics());
|
|
||||||
Assert.assertNull(copy.getName());
|
|
||||||
Assert.assertNull(copy.getDescription());
|
|
||||||
Assert.assertNull(copy.getIconUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAllAttributesParceling() {
|
|
||||||
WaypointCreationRequest original = new WaypointCreationRequest(WaypointType.WAYPOINT, false, "name", "category", "description", "img.png", null);
|
|
||||||
Parcel p = Parcel.obtain();
|
|
||||||
original.writeToParcel(p, 0);
|
|
||||||
p.setDataPosition(0);
|
|
||||||
WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p);
|
|
||||||
Assert.assertEquals(original.getType(), copy.getType());
|
|
||||||
Assert.assertFalse(copy.isTrackStatistics());
|
|
||||||
Assert.assertEquals("name", copy.getName());
|
|
||||||
Assert.assertEquals("category", copy.getCategory());
|
|
||||||
Assert.assertEquals("description", copy.getDescription());
|
|
||||||
Assert.assertEquals("img.png", copy.getIconUrl());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,7 +52,7 @@ public class ChartFragmentTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}
|
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testFillDataPoint_sensorIncorrect() {
|
public void testFillDataPoint_sensorIncorrect() {
|
||||||
@@ -161,12 +161,11 @@ public class ChartFragmentTest {
|
|||||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Computes the distance between Latitude 22 and 23. And for we set using
|
* Computes the distance between Latitude 22 and 23.
|
||||||
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||||
*/
|
*/
|
||||||
float[] results = new float[4];
|
float[] results = new float[4];
|
||||||
Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(),
|
Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(), sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
|
||||||
sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
|
|
||||||
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||||
Assert.assertEquals(distance1, point[0], 0.01);
|
Assert.assertEquals(distance1, point[0], 0.01);
|
||||||
|
|
||||||
@@ -176,11 +175,10 @@ public class ChartFragmentTest {
|
|||||||
point = fillDataPointTestHelper(sensorDataSetLocation3);
|
point = fillDataPointTestHelper(sensorDataSetLocation3);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Computes the distance between Latitude 23 and 24. And for we set using
|
* Computes the distance between Latitude 23 and 24.
|
||||||
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||||
*/
|
*/
|
||||||
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(),
|
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
|
||||||
sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
|
|
||||||
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||||
Assert.assertEquals(distance1 + distance2, point[0], 0.01);
|
Assert.assertEquals(distance1 + distance2, point[0], 0.01);
|
||||||
}
|
}
|
||||||
@@ -210,15 +208,14 @@ public class ChartFragmentTest {
|
|||||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* At first, clear old points of elevation, so give true to the second
|
* At first, clear old points of elevation, so give true to the second parameter.
|
||||||
* parameter. Then only one value INITIALLONGTITUDE in buffer.
|
* Then only one value INITIAL_ALTITUDE in buffer.
|
||||||
*/
|
*/
|
||||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||||
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01);
|
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send another value to buffer, now there are two values, INITIALALTITUDE
|
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
|
||||||
* and INITIALALTITUDE * 2.
|
|
||||||
*/
|
*/
|
||||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||||
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||||
@@ -235,8 +232,8 @@ public class ChartFragmentTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testFillDataPoint_speed() {
|
public void testFillDataPoint_speed() {
|
||||||
/*
|
/*
|
||||||
* At first, clear old points of speed, so give true to the second
|
* At first, clear old points of speed, so give true to the second parameter.
|
||||||
* parameter. It will not be filled in to the speed buffer.
|
* It will not be filled in to the speed buffer.
|
||||||
*/
|
*/
|
||||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||||
sensorDataSetLocation1.setSpeed(128.5f);
|
sensorDataSetLocation1.setSpeed(128.5f);
|
||||||
@@ -244,14 +241,13 @@ public class ChartFragmentTest {
|
|||||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tests the logic when both metricUnits and reportSpeed are true.This
|
* Tests the logic when both metricUnits and reportSpeed are true.
|
||||||
* location will be filled into speed buffer.
|
* This location will be filled into speed buffer.
|
||||||
*/
|
*/
|
||||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a time span here to make sure the second point is valid, the value
|
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
|
||||||
* 222 here is doesn't matter.
|
|
||||||
*/
|
*/
|
||||||
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222);
|
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222);
|
||||||
sensorDataSetLocation2.setSpeed(130);
|
sensorDataSetLocation2.setSpeed(130);
|
||||||
@@ -276,9 +272,8 @@ public class ChartFragmentTest {
|
|||||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a time span here to make sure the second point is valid and the speed
|
* Add a time span here to make sure the second point and the speed is valid.
|
||||||
* is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION
|
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||||
* * timeDifference speedDifference = 102 -100 timeDifference = 222
|
|
||||||
*/
|
*/
|
||||||
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
||||||
sensorDataSetLocation2.setSpeed(102);
|
sensorDataSetLocation2.setSpeed(102);
|
||||||
@@ -303,9 +298,8 @@ public class ChartFragmentTest {
|
|||||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a time span here to make sure the second point is valid and the speed
|
* Add a time span here to make sure the second point and the speed is valid.
|
||||||
* is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION
|
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||||
* * timeDifference speedDifference = 102 -100 timeDifference = 222
|
|
||||||
*/
|
*/
|
||||||
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
||||||
sensorDataSetLocation2.setSpeed(102);
|
sensorDataSetLocation2.setSpeed(102);
|
||||||
@@ -314,8 +308,7 @@ public class ChartFragmentTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the logic to get pace value when reportSpeed is false and average
|
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
|
||||||
* speed is zero.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testFillDataPoint_pace_zeroSpeed() {
|
public void testFillDataPoint_pace_zeroSpeed() {
|
||||||
|
|||||||
@@ -49,40 +49,32 @@ import static org.mockito.Mockito.when;
|
|||||||
*/
|
*/
|
||||||
public abstract class AbstractTestFileTrackImporter {
|
public abstract class AbstractTestFileTrackImporter {
|
||||||
|
|
||||||
protected static final String TRACK_NAME_0 = "blablub";
|
static final String TRACK_NAME_0 = "blablub";
|
||||||
protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
||||||
|
|
||||||
protected static final String TRACK_NAME_1 = "another track";
|
static final double TRACK_LATITUDE = 48.768364;
|
||||||
protected static final String TRACK_DESCRIPTION_1 = "another description";
|
static final double TRACK_LONGITUDE = 9.177886;
|
||||||
|
static final double TRACK_ELEVATION = 324.0;
|
||||||
|
|
||||||
protected static final double TRACK_LATITUDE = 48.768364;
|
static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
||||||
protected static final double TRACK_LONGITUDE = 9.177886;
|
static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123";
|
||||||
protected static final double TRACK_ELEVATION = 324.0;
|
static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
|
||||||
|
static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
|
||||||
|
|
||||||
protected static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
||||||
protected static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123";
|
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
||||||
protected static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
|
static final long TRACK_ID_0 = 1;
|
||||||
protected static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
|
static final long TRACK_POINT_ID_0 = 1;
|
||||||
|
static final long TRACK_POINT_ID_1 = 2;
|
||||||
protected static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
static final long TRACK_POINT_ID_3 = 4;
|
||||||
protected static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
|
||||||
protected static final long TRACK_ID_0 = 1;
|
private static final long WAYPOINT_ID_0 = 1;
|
||||||
protected static final long TRACK_ID_1 = 2;
|
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
||||||
protected static final long TRACK_POINT_ID_0 = 1;
|
|
||||||
protected static final long TRACK_POINT_ID_1 = 2;
|
|
||||||
protected static final long TRACK_POINT_ID_3 = 4;
|
|
||||||
protected static final long WAYPOINT_ID_0 = 1;
|
|
||||||
protected static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
|
|
||||||
protected static final Uri TRACK_ID_1_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build();
|
|
||||||
protected static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
|
||||||
|
|
||||||
protected final Context context = ApplicationProvider.getApplicationContext();
|
protected final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
/*
|
// We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is.
|
||||||
* We can't omit the timezones in the test, otherwise it'll use the local
|
|
||||||
* timezone and fail depending on where the test runner is.
|
|
||||||
*/
|
|
||||||
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC");
|
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC");
|
||||||
DATE_FORMAT_0.setTimeZone(utc);
|
DATE_FORMAT_0.setTimeZone(utc);
|
||||||
DATE_FORMAT_1.setTimeZone(utc);
|
DATE_FORMAT_1.setTimeZone(utc);
|
||||||
@@ -91,7 +83,7 @@ public abstract class AbstractTestFileTrackImporter {
|
|||||||
@Mock
|
@Mock
|
||||||
public ContentProviderUtils contentProviderUtils;
|
public ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
protected Location createLocation(int index, long time) {
|
Location createLocation(int index, long time) {
|
||||||
Location location = new Location(LocationManager.GPS_PROVIDER);
|
Location location = new Location(LocationManager.GPS_PROVIDER);
|
||||||
location.setLatitude(TRACK_LATITUDE + index);
|
location.setLatitude(TRACK_LATITUDE + index);
|
||||||
location.setLongitude(TRACK_LONGITUDE + index);
|
location.setLongitude(TRACK_LONGITUDE + index);
|
||||||
@@ -120,7 +112,7 @@ public abstract class AbstractTestFileTrackImporter {
|
|||||||
* @param lastTrack true if it is the last track in the gpx
|
* @param lastTrack true if it is the last track in the gpx
|
||||||
* @param trackId the track id
|
* @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());
|
contentProviderUtils.updateTrack(trackCaptor.capture());
|
||||||
|
|
||||||
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
|
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
|
||||||
|
|||||||
@@ -27,18 +27,17 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.anyInt;
|
import static org.mockito.Mockito.anyInt;
|
||||||
import static org.mockito.Mockito.anyLong;
|
import static org.mockito.Mockito.anyLong;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link GpxFileTrackImporter}.
|
* Tests for {@link GpxFileTrackImporter}.
|
||||||
*
|
*
|
||||||
@@ -58,6 +57,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
|
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
|
||||||
+ getTrackPoint(0, null) + getTrackPoint(1, null) + "</trkseg><trkseg>"
|
+ getTrackPoint(0, null) + getTrackPoint(1, null) + "</trkseg><trkseg>"
|
||||||
+ getTrackPoint(2, null) + getTrackPoint(3, null) + "</trkseg></trk></gpx>";
|
+ getTrackPoint(2, null) + getTrackPoint(3, null) + "</trkseg></trk></gpx>";
|
||||||
|
|
||||||
private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring(0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50);
|
private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring(0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50);
|
||||||
private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "1000.0");
|
private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "1000.0");
|
||||||
private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(TRACK_TIME_0, "invalid");
|
private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(TRACK_TIME_0, "invalid");
|
||||||
@@ -82,13 +82,9 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests one track with one segment.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testOneTrackOneSegment() throws Exception {
|
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 location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||||
|
|
||||||
@@ -98,28 +94,28 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), 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.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
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());
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||||
|
|
||||||
|
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||||
verify(contentProviderUtils);
|
|
||||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests one track with two segments.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testOneTrackTwoSegments() throws Exception {
|
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());
|
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
|
|
||||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||||
@@ -127,12 +123,18 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
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.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
|
||||||
|
|
||||||
|
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());
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||||
|
|
||||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||||
@@ -141,79 +143,61 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||||
|
|
||||||
verify(contentProviderUtils);
|
|
||||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests one track with two segments, but no time in the track points.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testOneTrackTwoSegmentsNoTime() {
|
public void testOneTrackTwoSegmentsNoTime() {
|
||||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
// given
|
||||||
|
|
||||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||||
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
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.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
|
||||||
|
|
||||||
|
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());
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
|
||||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
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);
|
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test an invalid xml input.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidXml() {
|
public void testInvalidXml() {
|
||||||
testInvalidGpx(INVALID_XML_GPX);
|
testInvalidGpx(INVALID_XML_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test an invalid location.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidLocation() {
|
public void testInvalidLocation() {
|
||||||
testInvalidGpx(INVALID_LOCATION_GPX);
|
testInvalidGpx(INVALID_LOCATION_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test an invalid time.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidTime() {
|
public void testInvalidTime() {
|
||||||
testInvalidGpx(INVALID_TIME_GPX);
|
testInvalidGpx(INVALID_TIME_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test an invalid altitude.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidAltitude() {
|
public void testInvalidAltitude() {
|
||||||
testInvalidGpx(INVALID_ALTITUDE_GPX);
|
testInvalidGpx(INVALID_ALTITUDE_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test an invalid latitude.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidLatitude() {
|
public void testInvalidLatitude() {
|
||||||
testInvalidGpx(INVALID_LATITUDE_GPX);
|
testInvalidGpx(INVALID_LATITUDE_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test an invalid longitude.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidLongitude() {
|
public void testInvalidLongitude() {
|
||||||
testInvalidGpx(INVALID_LONGITUDE_GPX);
|
testInvalidGpx(INVALID_LONGITUDE_GPX);
|
||||||
@@ -222,18 +206,16 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
private void testInvalidGpx(String xml) {
|
private void testInvalidGpx(String xml) {
|
||||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||||
|
|
||||||
// For the following, use StubReturn since we don't care whether they are
|
// For the following, use StubReturn since we don't care whether they are invoked or not.
|
||||||
// invoked or not.
|
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
||||||
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||||
when(contentProviderUtils.getLastTrackPointId(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);
|
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
||||||
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
||||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||||
Assert.assertEquals(-1L, trackId);
|
Assert.assertEquals(-1L, trackId);
|
||||||
verify(contentProviderUtils);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,16 +27,15 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link KmlFileTrackImporter}.
|
* Tests for {@link KmlFileTrackImporter}.
|
||||||
*
|
*
|
||||||
@@ -45,13 +44,11 @@ import static org.mockito.Mockito.when;
|
|||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||||
|
|
||||||
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX =
|
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||||
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
|
||||||
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
||||||
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1)
|
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1)
|
||||||
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
|
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
|
||||||
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX =
|
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||||
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
|
||||||
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
||||||
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</gx:Track><gx:Track>"
|
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</gx:Track><gx:Track>"
|
||||||
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3)
|
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3)
|
||||||
@@ -66,18 +63,12 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
String latitude = Double.toString(TRACK_LATITUDE + index);
|
String latitude = Double.toString(TRACK_LATITUDE + index);
|
||||||
String longitude = Double.toString(TRACK_LONGITUDE + index);
|
String longitude = Double.toString(TRACK_LONGITUDE + index);
|
||||||
String altitude = Double.toString(TRACK_ELEVATION + index);
|
String altitude = Double.toString(TRACK_ELEVATION + index);
|
||||||
StringBuilder buffer = new StringBuilder();
|
return "<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>";
|
||||||
buffer.append("<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>");
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests one track with one segment.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testOneTrackOneSegment() throws Exception {
|
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 location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||||
|
|
||||||
@@ -85,30 +76,31 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
// A flush happens at the end
|
// 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.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
|
||||||
|
|
||||||
|
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());
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||||
|
|
||||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||||
verify(contentProviderUtils);
|
|
||||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests one track with two segments.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testOneTrackTwoSegments() throws Exception {
|
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());
|
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
|
|
||||||
contentProviderUtils.clearTrack(context, TRACK_ID_0);
|
contentProviderUtils.clearTrack(context, TRACK_ID_0);
|
||||||
@@ -117,12 +109,18 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
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.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
|
||||||
|
|
||||||
|
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());
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||||
|
|
||||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||||
@@ -131,7 +129,6 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||||
|
|
||||||
verify(contentProviderUtils);
|
|
||||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the
|
* Tests the {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four situations.
|
||||||
* {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four
|
|
||||||
* situations.
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The newIdleTime is bigger than max interval.</li>
|
* <li>The newIdleTime is bigger than max interval.</li>
|
||||||
* <li>The newIdleTime is between min and max interval.</li>
|
* <li>The newIdleTime is between min and max interval.</li>
|
||||||
@@ -68,9 +66,9 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance()}.
|
* Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance_m()}.
|
||||||
*/
|
*/
|
||||||
public void testGetMinDistance() {
|
public void testGetMinDistance() {
|
||||||
assertEquals(MIN_DISTANCE, adocationListenerPolicy.getMinDistance());
|
assertEquals(MIN_DISTANCE, adocationListenerPolicy.getMinDistance_m());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks.services;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.rule.GrantPermissionRule;
|
|
||||||
import androidx.test.rule.ServiceTestRule;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link ControlRecordingService}.
|
|
||||||
*
|
|
||||||
* @author Youtao Liu
|
|
||||||
*/
|
|
||||||
public class ControlRecordingServiceTest {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final ServiceTestRule mServiceRule = new ServiceTestRule();
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
|
||||||
|
|
||||||
private Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ITrackRecordingService iTrackRecordingServiceMock;
|
|
||||||
|
|
||||||
private ControlRecordingService subject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the start of {@link ControlRecordingService} and tests the method
|
|
||||||
* {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)}
|
|
||||||
* to start a track recording.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testStartRecording() throws TimeoutException {
|
|
||||||
Assert.assertNull(subject);
|
|
||||||
Intent intent = startControlRecordingService(context.getString(R.string.track_action_start));
|
|
||||||
Assert.assertNotNull(subject);
|
|
||||||
|
|
||||||
when(iTrackRecordingServiceMock.startNewTrack()).thenReturn(1L);
|
|
||||||
subject.onHandleIntent(intent, iTrackRecordingServiceMock);
|
|
||||||
verify(iTrackRecordingServiceMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method
|
|
||||||
* {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)}
|
|
||||||
* to stop a track recording.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testStopRecording() throws TimeoutException {
|
|
||||||
Intent intent = startControlRecordingService(context.getString(R.string.track_action_end));
|
|
||||||
iTrackRecordingServiceMock.endCurrentTrack();
|
|
||||||
|
|
||||||
subject.onHandleIntent(intent, iTrackRecordingServiceMock);
|
|
||||||
verify(iTrackRecordingServiceMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a ControlRecordingService with a specified action.
|
|
||||||
*
|
|
||||||
* @param action the action string in the start intent
|
|
||||||
*/
|
|
||||||
private Intent startControlRecordingService(String action) throws TimeoutException {
|
|
||||||
Intent intent = new Intent(context, ControlRecordingService.class);
|
|
||||||
intent.setAction(action);
|
|
||||||
mServiceRule.startService(intent);
|
|
||||||
subject = ((ControlRecordingService) mServiceRule.bindService(intent));
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,29 +15,30 @@
|
|||||||
*/
|
*/
|
||||||
package de.dennisguse.opentracks.services;
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.ContentProvider;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.MediumTest;
|
import androidx.test.filters.MediumTest;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
import androidx.test.rule.ServiceTestRule;
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -54,45 +55,76 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
|||||||
* Tests for the track recording service.
|
* Tests for the track recording service.
|
||||||
*
|
*
|
||||||
* @author Bartlomiej Niechwiej
|
* @author Bartlomiej Niechwiej
|
||||||
|
* <p>
|
||||||
|
* ATTENTION: This tests deletes all stored tracks in the database.
|
||||||
|
* So, if it is executed on a real device, data might be lost.
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TrackRecordingServiceTest {
|
public class TrackRecordingServiceTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final ServiceTestRule mServiceRule = new ServiceTestRule();
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||||
private ContentProviderUtils providerUtils;
|
|
||||||
|
@Rule
|
||||||
|
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||||
|
|
||||||
private Context context = ApplicationProvider.getApplicationContext();
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
private ContentProviderUtils providerUtils;
|
||||||
|
|
||||||
|
private long trackId = Math.abs(new Random().nextLong());
|
||||||
|
|
||||||
|
static Intent createStartIntent(Context context) {
|
||||||
|
return new Intent(context, TrackRecordingService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) {
|
||||||
|
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
|
||||||
|
PreferencesUtils.setString(context, R.string.auto_resume_track_timeout_key, "" + timeoutMins);
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() {
|
||||||
// Set up the mock content resolver
|
// Set up the mock content resolver
|
||||||
CustomContentProvider customContentProvider = new CustomContentProvider();
|
ContentProvider customContentProvider = new CustomContentProvider() {
|
||||||
|
};
|
||||||
customContentProvider.attachInfo(context, null);
|
customContentProvider.attachInfo(context, null);
|
||||||
|
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = ContentProviderUtils.Factory.get(context);
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
|
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
sharedPreferences.edit().clear().apply();
|
sharedPreferences.edit().clear().apply();
|
||||||
|
|
||||||
// Disable auto resume by default.
|
// Disable auto resume by default.
|
||||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
|
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
|
||||||
// No recording track.
|
|
||||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
// Ensure that the database is empty before every test
|
||||||
|
providerUtils.deleteAllTracks(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws TimeoutException {
|
||||||
|
// Reset service (if some previous test failed)
|
||||||
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
if (service.isRecording() || service.isPaused()) {
|
||||||
|
service.endCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the database is empty after every test
|
||||||
|
providerUtils.deleteAllTracks(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@Test
|
@Test
|
||||||
public void testStartable() throws TimeoutException {
|
public void testStartable() throws TimeoutException {
|
||||||
Intent intent = new Intent(context, TrackRecordingService.class);
|
mServiceRule.startService(createStartIntent(context));
|
||||||
mServiceRule.startService(createStartIntent());
|
Assert.assertNotNull(mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertNotNull(mServiceRule.bindService(createStartIntent()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testBindable() throws TimeoutException {
|
public void testBindable() throws TimeoutException {
|
||||||
IBinder service = mServiceRule.bindService(createStartIntent());
|
IBinder service = mServiceRule.bindService(createStartIntent(context));
|
||||||
Assert.assertNotNull(service);
|
Assert.assertNotNull(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,27 +132,30 @@ public class TrackRecordingServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testResumeAfterReboot_shouldResume() throws Exception {
|
public void testResumeAfterReboot_shouldResume() throws Exception {
|
||||||
// Insert a dummy track and mark it as recording track.
|
// Insert a dummy track and mark it as recording track.
|
||||||
createDummyTrack(123L, System.currentTimeMillis(), true);
|
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
// Clear the number of attempts and set the timeout to 10 min.
|
||||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||||
Intent startIntent = createStartIntent();
|
Intent startIntent = createStartIntent(context);
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||||
|
mServiceRule.startService(startIntent);
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertNotNull(service);
|
Assert.assertNotNull(service);
|
||||||
|
|
||||||
// We expect to resume the previous track.
|
// We expect to resume the previous track.
|
||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
Assert.assertEquals(123L, service.getRecordingTrackId());
|
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testResumeAfterReboot_simulateReboot() throws Exception {
|
public void testResumeAfterReboot_simulateReboot() throws Exception {
|
||||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
// Simulate recording a track.
|
// Simulate recording a track.
|
||||||
@@ -128,15 +163,17 @@ public class TrackRecordingServiceTest {
|
|||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||||
mServiceRule.unbindService();
|
mServiceRule.unbindService();
|
||||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||||
Intent startIntent = createStartIntent();
|
|
||||||
|
Intent startIntent = createStartIntent(context);
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||||
mServiceRule.startService(startIntent);
|
mServiceRule.startService(startIntent);
|
||||||
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertNotNull(service);
|
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertNotNull(service);
|
||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,16 +181,18 @@ public class TrackRecordingServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
|
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
|
||||||
// Insert a dummy track and mark it as recording track.
|
// Insert a dummy track and mark it as recording track.
|
||||||
createDummyTrack(123L, System.currentTimeMillis(), false);
|
createDummyTrack(trackId, System.currentTimeMillis(), false);
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
// Clear the number of attempts and set the timeout to 10 min.
|
||||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT,
|
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||||
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||||
Intent startIntent = createStartIntent();
|
Intent startIntent = createStartIntent(context);
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||||
|
mServiceRule.startService(startIntent);
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertNotNull(service);
|
Assert.assertNotNull(service);
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because it was stopped.
|
// We don't expect to resume the previous track, because it was stopped.
|
||||||
@@ -165,43 +204,46 @@ public class TrackRecordingServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
||||||
// Insert a dummy track last updated 20 min ago.
|
// Insert a dummy track last updated 20 min ago.
|
||||||
createDummyTrack(123L, System.currentTimeMillis() - 20 * 60 * 1000, true);
|
createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
// Clear the number of attempts and set the timeout to 10 min.
|
||||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT,
|
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||||
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||||
Intent startIntent = createStartIntent();
|
Intent startIntent = createStartIntent(context);
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||||
|
mServiceRule.startService(startIntent);
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertNotNull(service);
|
Assert.assertNotNull(service);
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because it has expired.
|
// We don't expect to resume the previous track, because it has expired.
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
|
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
|
||||||
// Insert a dummy track.
|
// Insert a dummy track.
|
||||||
createDummyTrack(123L, System.currentTimeMillis(), true);
|
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
||||||
|
|
||||||
// Set the number of attempts to max.
|
// Set the number of attempts to max.
|
||||||
updateAutoResumePrefs(TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS,
|
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||||
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||||
Intent startIntent = createStartIntent();
|
Intent startIntent = createStartIntent(context);
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||||
|
mServiceRule.startService(startIntent);
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||||
|
|
||||||
|
//then
|
||||||
Assert.assertNotNull(service);
|
Assert.assertNotNull(service);
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because there were already
|
// We don't expect to resume the previous track, because there were already too many attempts.
|
||||||
// too many attempts.
|
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@@ -210,7 +252,10 @@ public class TrackRecordingServiceTest {
|
|||||||
List<Track> tracks = providerUtils.getAllTracks();
|
List<Track> tracks = providerUtils.getAllTracks();
|
||||||
Assert.assertTrue(tracks.isEmpty());
|
Assert.assertTrue(tracks.isEmpty());
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
Intent startIntent = createStartIntent(context);
|
||||||
|
mServiceRule.startService(startIntent);
|
||||||
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||||
|
|
||||||
// Test if we start in no-recording mode by default.
|
// Test if we start in no-recording mode by default.
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||||
@@ -219,137 +264,60 @@ public class TrackRecordingServiceTest {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_oldTracks() throws Exception {
|
public void testRecording_oldTracks() throws Exception {
|
||||||
createDummyTrack(123L, -1L, false);
|
createDummyTrack(trackId, -1L, false);
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||||
|
Intent startIntent = createStartIntent(context);
|
||||||
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||||
|
|
||||||
// Just set recording track to a bogus value.
|
// Just set recording track to a bogus value.
|
||||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 256L);
|
// Make sure that the service will not start recording and will clear the bogus track.
|
||||||
|
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||||
|
|
||||||
// Make sure that the service will not start recording and will clear
|
// then
|
||||||
// the bogus track.
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testStartNewTrack_noRecording() throws Exception {
|
|
||||||
// NOTICE: due to the way Android permissions work, if this fails,
|
|
||||||
// uninstall the test apk then retry - the test must be installed *after* the app (go figure).
|
|
||||||
// Reference: http://code.google.com/p/android/issues/detail?id=5521
|
|
||||||
BlockingBroadcastReceiver startReceiver = new BlockingBroadcastReceiver();
|
|
||||||
String startAction = context.getString(R.string.track_started_broadcast_action);
|
|
||||||
context.registerReceiver(startReceiver, new IntentFilter(startAction));
|
|
||||||
|
|
||||||
List<Track> tracks = providerUtils.getAllTracks();
|
|
||||||
Assert.assertTrue(tracks.isEmpty());
|
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
long id = service.startNewTrack();
|
|
||||||
Assert.assertTrue(id >= 0);
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
Track track = providerUtils.getTrack(id);
|
|
||||||
Assert.assertNotNull(track);
|
|
||||||
Assert.assertEquals(id, track.getId());
|
|
||||||
Assert.assertEquals(PreferencesUtils.getString(context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT), track.getCategory());
|
|
||||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
|
||||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
|
||||||
|
|
||||||
// Verify that the start broadcast was received.
|
|
||||||
Assert.assertTrue(startReceiver.waitUntilReceived(1));
|
|
||||||
List<Intent> receivedIntents = startReceiver.getReceivedIntents();
|
|
||||||
Assert.assertEquals(1, receivedIntents.size());
|
|
||||||
Intent broadcastIntent = receivedIntents.get(0);
|
|
||||||
Assert.assertEquals(startAction, broadcastIntent.getAction());
|
|
||||||
Assert.assertEquals(id, broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
|
|
||||||
|
|
||||||
context.unregisterReceiver(startReceiver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||||
createDummyTrack(123L, -1L, true);
|
createDummyTrack(trackId, -1L, true);
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
|
|
||||||
// Starting a new track when there is a recording should just return -1L.
|
long newTrackId = service.startNewTrack();
|
||||||
long newTrack = service.startNewTrack();
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||||
Assert.assertEquals(-1L, newTrack);
|
|
||||||
|
|
||||||
Assert.assertEquals(123L, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||||
Assert.assertEquals(123L, service.getRecordingTrackId());
|
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testEndCurrentTrack_alreadyRecording() throws Exception {
|
|
||||||
// See comment above if this fails randomly.
|
|
||||||
BlockingBroadcastReceiver stopReceiver = new BlockingBroadcastReceiver();
|
|
||||||
String stopAction = context.getString(R.string.track_stopped_broadcast_action);
|
|
||||||
context.registerReceiver(stopReceiver, new IntentFilter(stopAction));
|
|
||||||
|
|
||||||
createDummyTrack(123L, -1L, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
|
|
||||||
// End the current track.
|
|
||||||
service.endCurrentTrack();
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
|
||||||
|
|
||||||
// Verify that the stop broadcast was received.
|
|
||||||
Assert.assertTrue(stopReceiver.waitUntilReceived(1));
|
|
||||||
List<Intent> receivedIntents = stopReceiver.getReceivedIntents();
|
|
||||||
Assert.assertEquals(1, receivedIntents.size());
|
|
||||||
Intent broadcastIntent = receivedIntents.get(0);
|
|
||||||
Assert.assertEquals(stopAction, broadcastIntent.getAction());
|
|
||||||
Assert.assertEquals(123L, broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
|
|
||||||
|
|
||||||
context.unregisterReceiver(stopReceiver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
public void testEndCurrentTrack_noRecording() throws Exception {
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
/*
|
// Ending the current track when there is no recording should not result in any error.
|
||||||
* Ending the current track when there is no recording should not result in
|
|
||||||
* any error.
|
|
||||||
*/
|
|
||||||
service.endCurrentTrack();
|
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());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testIntegration_completeRecordingSession() throws Exception {
|
|
||||||
List<Track> tracks = providerUtils.getAllTracks();
|
|
||||||
Assert.assertTrue(tracks.isEmpty());
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||||
@@ -359,21 +327,23 @@ public class TrackRecordingServiceTest {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
||||||
createDummyTrack(123L, -1L, true);
|
createDummyTrack(trackId, -1L, true);
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
Assert.assertFalse(service.isPaused());
|
Assert.assertFalse(service.isPaused());
|
||||||
insertLocation(service);
|
insertLocation(service);
|
||||||
|
|
||||||
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
long waypointId1 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||||
Assert.assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
Assert.assertNotEquals(-1L, waypointId1);
|
||||||
|
long waypointId2 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||||
|
Assert.assertNotEquals(-1L, waypointId2);
|
||||||
|
|
||||||
Waypoint wpt = providerUtils.getWaypoint(2);
|
Waypoint wpt = providerUtils.getWaypoint(waypointId1);
|
||||||
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
|
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
|
||||||
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 1), wpt.getName());
|
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 0), wpt.getName());
|
||||||
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
|
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
|
||||||
Assert.assertEquals(123L, wpt.getTrackId());
|
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||||
Assert.assertNotNull(wpt.getLocation());
|
Assert.assertNotNull(wpt.getLocation());
|
||||||
Assert.assertNotNull(wpt.getTripStatistics());
|
Assert.assertNotNull(wpt.getTripStatistics());
|
||||||
@@ -382,149 +352,7 @@ public class TrackRecordingServiceTest {
|
|||||||
// TODO: Check waypoint 2.
|
// TODO: Check waypoint 2.
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
||||||
@Test
|
|
||||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
|
||||||
Assert.assertEquals(-1L, waypointId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
|
||||||
createDummyTrack(123L, -1L, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
insertLocation(service);
|
|
||||||
|
|
||||||
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT));
|
|
||||||
Waypoint wpt = providerUtils.getWaypoint(1);
|
|
||||||
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
|
|
||||||
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
|
||||||
Assert.assertEquals(WaypointType.WAYPOINT, wpt.getType());
|
|
||||||
Assert.assertEquals(123L, wpt.getTrackId());
|
|
||||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
|
||||||
Assert.assertNotNull(wpt.getLocation());
|
|
||||||
Assert.assertNull(wpt.getTripStatistics());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_voiceFrequencyDefault() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_voiceFrequencyByDistance() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_voiceFrequencyByTime() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_maxRecordingDistanceDefault() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_maxRecordingDistance() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_minRecordingDistanceDefault() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_minRecordingDistance() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_splitFrequencyDefault() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_splitFrequencyByDistance() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_splitFrequencyByTime() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_metricUnitsDefault() throws Exception {
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_metricUnitsDisabled() throws Exception {
|
|
||||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_minRecordingIntervalDefault() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_minRecordingInterval() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_minRequiredAccuracyDefault() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_minRequiredAccuracy() throws Exception {
|
|
||||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Track createDummyTrack(long id, long stopTime, boolean isRecording) {
|
|
||||||
Track dummyTrack = new Track();
|
Track dummyTrack = new Track();
|
||||||
dummyTrack.setId(id);
|
dummyTrack.setId(id);
|
||||||
dummyTrack.setName("Dummy Track");
|
dummyTrack.setName("Dummy Track");
|
||||||
@@ -532,18 +360,6 @@ public class TrackRecordingServiceTest {
|
|||||||
tripStatistics.setStopTime(stopTime);
|
tripStatistics.setStopTime(stopTime);
|
||||||
dummyTrack.setTripStatistics(tripStatistics);
|
dummyTrack.setTripStatistics(tripStatistics);
|
||||||
addTrack(dummyTrack, isRecording);
|
addTrack(dummyTrack, isRecording);
|
||||||
return dummyTrack;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateAutoResumePrefs(int attempts, int timeoutMins) {
|
|
||||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
|
|
||||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Intent createStartIntent() {
|
|
||||||
Intent startIntent = new Intent();
|
|
||||||
startIntent.setClass(context, TrackRecordingService.class);
|
|
||||||
return startIntent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTrack(Track track, boolean isRecording) {
|
private void addTrack(Track track, boolean isRecording) {
|
||||||
@@ -554,56 +370,8 @@ public class TrackRecordingServiceTest {
|
|||||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fullRecordingSession() throws Exception {
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
// Start a track.
|
|
||||||
long id = service.startNewTrack();
|
|
||||||
Assert.assertTrue(id >= 0);
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
Track track = providerUtils.getTrack(id);
|
|
||||||
Assert.assertNotNull(track);
|
|
||||||
Assert.assertEquals(id, track.getId());
|
|
||||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
|
||||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
|
||||||
|
|
||||||
// Insert a few points, markers and statistics.
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
Location loc = new Location("gps");
|
|
||||||
loc.setLongitude(35.0f + i / 10.0f);
|
|
||||||
loc.setLatitude(45.0f - i / 5.0f);
|
|
||||||
loc.setAccuracy(5);
|
|
||||||
loc.setSpeed(10);
|
|
||||||
loc.setTime(startTime + i * 10000);
|
|
||||||
loc.setBearing(3.0f);
|
|
||||||
service.insertTrackPoint(loc);
|
|
||||||
|
|
||||||
if (i % 10 == 0) {
|
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
|
||||||
} else if (i % 7 == 0) {
|
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the track. Validate if it has correct data.
|
|
||||||
service.endCurrentTrack();
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
|
||||||
track = providerUtils.getTrack(id);
|
|
||||||
Assert.assertNotNull(track);
|
|
||||||
Assert.assertEquals(id, track.getId());
|
|
||||||
TripStatistics tripStatistics = track.getTripStatistics();
|
|
||||||
Assert.assertNotNull(tripStatistics);
|
|
||||||
Assert.assertTrue(tripStatistics.getStartTime() > 0);
|
|
||||||
Assert.assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a location and waits for 100ms.
|
* Inserts a location and waits for 200ms.
|
||||||
*
|
|
||||||
* @param trackRecordingService the track recording service
|
|
||||||
*/
|
*/
|
||||||
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
|
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
|
||||||
Location location = new Location("gps");
|
Location location = new Location("gps");
|
||||||
@@ -614,47 +382,38 @@ public class TrackRecordingServiceTest {
|
|||||||
location.setTime(System.currentTimeMillis());
|
location.setTime(System.currentTimeMillis());
|
||||||
location.setBearing(3.0f);
|
location.setBearing(3.0f);
|
||||||
trackRecordingService.insertTrackPoint(location);
|
trackRecordingService.insertTrackPoint(location);
|
||||||
|
|
||||||
Thread.sleep(200);
|
Thread.sleep(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@MediumTest
|
||||||
* Synchronous/waitable broadcast receiver to be used in testing.
|
@Test
|
||||||
*/
|
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||||
private class BlockingBroadcastReceiver extends BroadcastReceiver {
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
private static final long MAX_WAIT_TIME_MS = 10000;
|
Assert.assertFalse(service.isRecording());
|
||||||
private final List<Intent> receivedIntents = new ArrayList<>();
|
|
||||||
|
|
||||||
public List<Intent> getReceivedIntents() {
|
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||||
return receivedIntents;
|
Assert.assertEquals(-1L, waypointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@MediumTest
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
@Test
|
||||||
Log.d("Test", "Got broadcast: " + intent);
|
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||||
synchronized (receivedIntents) {
|
createDummyTrack(trackId, -1L, true);
|
||||||
receivedIntents.add(intent);
|
|
||||||
receivedIntents.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean waitUntilReceived(int receiveCount) {
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
long deadline = System.currentTimeMillis() + MAX_WAIT_TIME_MS;
|
Assert.assertTrue(service.isRecording());
|
||||||
synchronized (receivedIntents) {
|
insertLocation(service);
|
||||||
while (receivedIntents.size() < receiveCount) {
|
|
||||||
try {
|
|
||||||
// Wait releases synchronized lock until it returns
|
|
||||||
receivedIntents.wait(500);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
if (System.currentTimeMillis() > deadline) {
|
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||||
return false;
|
Assert.assertNotEquals(-1L, waypointId);
|
||||||
}
|
Waypoint wpt = providerUtils.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());
|
||||||
return true;
|
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||||
}
|
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||||
|
Assert.assertNotNull(wpt.getLocation());
|
||||||
|
Assert.assertNull(wpt.getTripStatistics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.MediumTest;
|
||||||
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.content.CustomContentProvider;
|
||||||
|
import de.dennisguse.opentracks.content.Track;
|
||||||
|
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||||
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the track recording service, which require a {@link Looper}.
|
||||||
|
*
|
||||||
|
* @author Bartlomiej Niechwiej
|
||||||
|
* <p>
|
||||||
|
* ATTENTION: This tests deletes all stored tracks in the database.
|
||||||
|
* So, if it is executed on a real device, data might be lost.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TrackRecordingServiceTestLooper {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
private ContentProviderUtils providerUtils;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void preSetUp() {
|
||||||
|
// Prepare looper for Android's message queue
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void finalTearDown() {
|
||||||
|
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
// Set up the mock content resolver
|
||||||
|
ContentProvider customContentProvider = new CustomContentProvider() {
|
||||||
|
};
|
||||||
|
customContentProvider.attachInfo(context, null);
|
||||||
|
|
||||||
|
providerUtils = ContentProviderUtils.Factory.get(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws TimeoutException {
|
||||||
|
// Reset service (if some previous test failed)
|
||||||
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||||
|
if (service.isRecording() || service.isPaused()) {
|
||||||
|
service.endCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the database is empty after every test
|
||||||
|
providerUtils.deleteAllTracks(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
||||||
|
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_minRecordingDistance() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.split_frequency_key, Integer.parseInt(context.getResources().getString(R.string.split_frequency_default)));
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_splitFrequencyByDistance() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_splitFrequencyByTime() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||||
|
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
|
||||||
|
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_minRecordingInterval() throws TimeoutException {
|
||||||
|
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
||||||
|
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testIntegration_completeRecordingSession() throws TimeoutException {
|
||||||
|
List<Track> tracks = providerUtils.getAllTracks();
|
||||||
|
Assert.assertTrue(tracks.isEmpty());
|
||||||
|
fullRecordingSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fullRecordingSession() throws TimeoutException {
|
||||||
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
// Start a track.
|
||||||
|
long id = service.startNewTrack();
|
||||||
|
Assert.assertTrue(id >= 0);
|
||||||
|
Assert.assertTrue(service.isRecording());
|
||||||
|
Track track = providerUtils.getTrack(id);
|
||||||
|
Assert.assertNotNull(track);
|
||||||
|
Assert.assertEquals(id, track.getId());
|
||||||
|
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
|
||||||
|
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||||
|
|
||||||
|
// Insert a few points, markers and statistics.
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
Location location = new Location("gps");
|
||||||
|
location.setLongitude(35.0f + i / 10.0f);
|
||||||
|
location.setLatitude(45.0f - i / 5.0f);
|
||||||
|
location.setAccuracy(5);
|
||||||
|
location.setSpeed(10);
|
||||||
|
location.setTime(startTime + i * 10000);
|
||||||
|
location.setBearing(3.0f);
|
||||||
|
service.insertTrackPoint(location);
|
||||||
|
|
||||||
|
if (i % 10 == 0) {
|
||||||
|
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||||
|
} else if (i % 7 == 0) {
|
||||||
|
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the track. Validate if it has correct data.
|
||||||
|
service.endCurrentTrack();
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||||
|
track = providerUtils.getTrack(id);
|
||||||
|
Assert.assertNotNull(track);
|
||||||
|
Assert.assertEquals(id, track.getId());
|
||||||
|
TripStatistics tripStatistics = track.getTripStatistics();
|
||||||
|
Assert.assertNotNull(tripStatistics);
|
||||||
|
Assert.assertTrue(tripStatistics.getStartTime() > 0);
|
||||||
|
Assert.assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,17 +17,19 @@
|
|||||||
package de.dennisguse.opentracks.services.tasks;
|
package de.dennisguse.opentracks.services.tasks;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link AnnouncementPeriodicTaskFactory}.
|
* Tests for {@link AnnouncementPeriodicTaskFactory}.
|
||||||
* These tests require Donut+ to run.
|
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
@@ -36,6 +38,17 @@ public class AnnouncementPeriodicTaskFactoryTest {
|
|||||||
|
|
||||||
private Context context = ApplicationProvider.getApplicationContext();
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void preSetUp() {
|
||||||
|
// Prepare looper for Android's message queue
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void finalTearDown() {
|
||||||
|
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreate() {
|
public void testCreate() {
|
||||||
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
|
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
|
||||||
|
|||||||
@@ -1,549 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks.services.tasks;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
|
||||||
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
|
|
||||||
import android.telephony.PhoneStateListener;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.reset;
|
|
||||||
import static org.mockito.Mockito.same;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link AnnouncementPeriodicTask}.
|
|
||||||
* WARNING: I'm not responsible if your eyes start bleeding while reading this
|
|
||||||
* code. You have been warned. It's still better than no test, though.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class AnnouncementPeriodicTaskTest {
|
|
||||||
|
|
||||||
// Use something other than our hardcoded value
|
|
||||||
private static final Locale DEFAULT_LOCALE = Locale.KOREAN;
|
|
||||||
private static final String ANNOUNCEMENT = "I can haz cheeseburger?";
|
|
||||||
private Locale oldDefaultLocale;
|
|
||||||
|
|
||||||
private Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
private AnnouncementPeriodicTask task;
|
|
||||||
@Mock
|
|
||||||
private AnnouncementPeriodicTask mockTask;
|
|
||||||
private ArgumentCaptor<OnInitListener> initListenerCapture = ArgumentCaptor.forClass(OnInitListener.class);
|
|
||||||
private ArgumentCaptor<PhoneStateListener> phoneListenerCapture = ArgumentCaptor.forClass(PhoneStateListener.class);
|
|
||||||
private TextToSpeechDelegate ttsDelegate;
|
|
||||||
@Mock
|
|
||||||
private TextToSpeechInterface tts;
|
|
||||||
|
|
||||||
// @UsesMocks({
|
|
||||||
// StringUtils.class,
|
|
||||||
// })
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
oldDefaultLocale = Locale.getDefault();
|
|
||||||
Locale.setDefault(DEFAULT_LOCALE);
|
|
||||||
|
|
||||||
// Eww, the effort required just to mock TextToSpeech is insane
|
|
||||||
final AtomicBoolean listenerCalled = new AtomicBoolean();
|
|
||||||
OnInitListener blockingListener = new OnInitListener() {
|
|
||||||
@Override
|
|
||||||
public void onInit(int status) {
|
|
||||||
synchronized (this) {
|
|
||||||
listenerCalled.set(true);
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ttsDelegate = new TextToSpeechDelegate(context, blockingListener);
|
|
||||||
|
|
||||||
// Wait for all async operations done in the constructor to finish.
|
|
||||||
synchronized (blockingListener) {
|
|
||||||
while (!listenerCalled.get()) {
|
|
||||||
// Releases the synchronized lock until we're woken up.
|
|
||||||
blockingListener.wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a partial forwarding mock
|
|
||||||
task = new AnnouncementPeriodicTask(context) {
|
|
||||||
@Override
|
|
||||||
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
|
|
||||||
return mockTask.newTextToSpeech(ctx, onInitListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getAnnouncement(TripStatistics stats) {
|
|
||||||
return mockTask.getAnnouncement(stats);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void listenToPhoneState(PhoneStateListener listener, int events) {
|
|
||||||
mockTask.listenToPhoneState(listener, events);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
Locale.setDefault(oldDefaultLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStart() {
|
|
||||||
doStart();
|
|
||||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
|
||||||
Assert.assertNotNull(ttsInitListener);
|
|
||||||
|
|
||||||
ttsInitListener.onInit(TextToSpeech.SUCCESS);
|
|
||||||
//TODO
|
|
||||||
//verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStart_notReady() {
|
|
||||||
doStart();
|
|
||||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
|
||||||
Assert.assertNotNull(ttsInitListener);
|
|
||||||
|
|
||||||
ttsInitListener.onInit(TextToSpeech.ERROR);
|
|
||||||
//TODO
|
|
||||||
//AndroidMock.verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testShutdown() {
|
|
||||||
// First, start
|
|
||||||
doStart();
|
|
||||||
verify(mockTask);
|
|
||||||
reset(mockTask);
|
|
||||||
|
|
||||||
// Then, shut down
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
mockTask.listenToPhoneState(
|
|
||||||
same(phoneListener), eq(PhoneStateListener.LISTEN_NONE));
|
|
||||||
tts.shutdown();
|
|
||||||
task.shutdown();
|
|
||||||
//TODO
|
|
||||||
//AndroidMock.verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRun() {
|
|
||||||
// when service data calls
|
|
||||||
TripStatistics stats = new TripStatistics();
|
|
||||||
|
|
||||||
// when announcement building call
|
|
||||||
when(mockTask.getAnnouncement(same(stats))).thenReturn(ANNOUNCEMENT);
|
|
||||||
|
|
||||||
// Put task in "ready" state
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
when(tts.isLanguageAvailable(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
|
||||||
when(tts.setLanguage(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
|
||||||
when(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).thenReturn(TextToSpeech.SUCCESS);
|
|
||||||
when(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) any())).thenReturn(0);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
task.announce(stats);
|
|
||||||
//TODO
|
|
||||||
//AndroidMock.verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRun_notReady() {
|
|
||||||
// Put task in "not ready" state
|
|
||||||
startTask(TextToSpeech.ERROR);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
task.run(null);
|
|
||||||
//TODO
|
|
||||||
//verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRun_duringCall() {
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
when(tts.isSpeaking()).thenReturn(false);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
|
|
||||||
task.run(null);
|
|
||||||
//TODO
|
|
||||||
//verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRun_ringWhileSpeaking() {
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
when(tts.isSpeaking()).thenReturn(true);
|
|
||||||
when(tts.stop()).thenReturn(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
// Update the state to ringing - this should stop the current announcement.
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
|
||||||
|
|
||||||
// Run the announcement - this should do nothing.
|
|
||||||
task.run(null);
|
|
||||||
//TODO
|
|
||||||
//AndroidMock.verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRun_whileRinging() {
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
when(tts.isSpeaking()).thenReturn(false);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
|
||||||
task.run(null);
|
|
||||||
//TODO
|
|
||||||
//AndroidMock.verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRun_noService() {
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
task.run(null);
|
|
||||||
//TODO
|
|
||||||
//AndroidMock.verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRun_noStats() {
|
|
||||||
// when service data calls
|
|
||||||
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
task.run(null);
|
|
||||||
//TODO
|
|
||||||
//AndroidMock.verify(mockTask, tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAnnounceTime_time_zero() {
|
|
||||||
long time = 0; // 0 seconds
|
|
||||||
Assert.assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time one.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAnnounceTime_time_one() {
|
|
||||||
long time = 1 * 1000; // 1 second
|
|
||||||
Assert.assertEquals("0 minutes 1 second", task.getAnnounceTime(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
|
||||||
* numbers with the hour unit.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAnnounceTime_singular_has_hour() {
|
|
||||||
long time = (1 * 60 * 60 * 1000) + (1 * 60 * 1000) + (1 * 1000); // 1 hour 1 minute 1 second
|
|
||||||
Assert.assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
|
||||||
* with the hour unit.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAnnounceTime_plural_has_hour() {
|
|
||||||
long time = (2 * 60 * 60 * 1000) + (2 * 60 * 1000) + (2 * 1000); // 2 hours 2 minutes 2 seconds
|
|
||||||
Assert.assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
|
||||||
* numbers without the hour unit.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAnnounceTime_singular_no_hour() {
|
|
||||||
long time = (1 * 60 * 1000) + (1 * 1000); // 1 minute 1 second
|
|
||||||
Assert.assertEquals("1 minute 1 second", task.getAnnounceTime(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
|
||||||
* without the hour unit.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAnnounceTime_plural_no_hour() {
|
|
||||||
long time = (2 * 60 * 1000) + (2 * 1000); // 2 minutes 2 seconds
|
|
||||||
Assert.assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startTask(int state) {
|
|
||||||
reset(tts);
|
|
||||||
doStart();
|
|
||||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
|
||||||
ttsInitListener.onInit(state);
|
|
||||||
//TODO
|
|
||||||
reset(tts);
|
|
||||||
//AndroidMock.resetToDefault(tts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doStart() {
|
|
||||||
mockTask.listenToPhoneState(phoneListenerCapture.capture(), eq(PhoneStateListener.LISTEN_CALL_STATE));
|
|
||||||
when(mockTask.newTextToSpeech(same(context), initListenerCapture.capture())).thenReturn(ttsDelegate);
|
|
||||||
|
|
||||||
task.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mockable interface that we delegate TTS calls to.
|
|
||||||
*/
|
|
||||||
interface TextToSpeechInterface {
|
|
||||||
int addEarcon(String earcon, String packagename, int resourceId);
|
|
||||||
|
|
||||||
int addEarcon(String earcon, String filename);
|
|
||||||
|
|
||||||
int addSpeech(String text, String packagename, int resourceId);
|
|
||||||
|
|
||||||
int addSpeech(String text, String filename);
|
|
||||||
|
|
||||||
boolean areDefaultsEnforced();
|
|
||||||
|
|
||||||
String getDefaultEngine();
|
|
||||||
|
|
||||||
Locale getLanguage();
|
|
||||||
|
|
||||||
int isLanguageAvailable(Locale loc);
|
|
||||||
|
|
||||||
boolean isSpeaking();
|
|
||||||
|
|
||||||
int playEarcon(String earcon, int queueMode,
|
|
||||||
HashMap<String, String> params);
|
|
||||||
|
|
||||||
int playSilence(long durationInMs, int queueMode,
|
|
||||||
HashMap<String, String> params);
|
|
||||||
|
|
||||||
int setEngineByPackageName(String enginePackageName);
|
|
||||||
|
|
||||||
int setLanguage(Locale loc);
|
|
||||||
|
|
||||||
int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener);
|
|
||||||
|
|
||||||
int setPitch(float pitch);
|
|
||||||
|
|
||||||
int setSpeechRate(float speechRate);
|
|
||||||
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
int speak(String text, int queueMode, HashMap<String, String> params);
|
|
||||||
|
|
||||||
int stop();
|
|
||||||
|
|
||||||
int synthesizeToFile(String text, HashMap<String, String> params,
|
|
||||||
String filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclass of {@link TextToSpeech} which delegates calls to the interface
|
|
||||||
* above.
|
|
||||||
* The logic here is stupid and the author is ashamed of having to write it
|
|
||||||
* like this, but basically the issue is that TextToSpeech cannot be mocked
|
|
||||||
* without running its constructor, its constructor runs async operations
|
|
||||||
* which call other methods (and then if the methods are part of a mock we'd
|
|
||||||
* have to set a behavior, but we can't 'cause the object hasn't been fully
|
|
||||||
* built yet).
|
|
||||||
* The logic is that calls made during the constructor (when tts is not yet
|
|
||||||
* set) will go up to the original class, but after tts is set we'll forward
|
|
||||||
* them all to the mock.
|
|
||||||
*/
|
|
||||||
private class TextToSpeechDelegate extends TextToSpeech implements TextToSpeechInterface {
|
|
||||||
public TextToSpeechDelegate(Context context, OnInitListener listener) {
|
|
||||||
super(context, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addEarcon(String earcon, String packagename, int resourceId) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addEarcon(earcon, packagename, resourceId);
|
|
||||||
}
|
|
||||||
return tts.addEarcon(earcon, packagename, resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addEarcon(String earcon, String filename) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addEarcon(earcon, filename);
|
|
||||||
}
|
|
||||||
return tts.addEarcon(earcon, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addSpeech(String text, String packagename, int resourceId) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addSpeech(text, packagename, resourceId);
|
|
||||||
}
|
|
||||||
return tts.addSpeech(text, packagename, resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addSpeech(String text, String filename) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addSpeech(text, filename);
|
|
||||||
}
|
|
||||||
return tts.addSpeech(text, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Locale getLanguage() {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.getLanguage();
|
|
||||||
}
|
|
||||||
return tts.getLanguage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int isLanguageAvailable(Locale loc) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.isLanguageAvailable(loc);
|
|
||||||
}
|
|
||||||
return tts.isLanguageAvailable(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSpeaking() {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.isSpeaking();
|
|
||||||
}
|
|
||||||
return tts.isSpeaking();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int playEarcon(String earcon, int queueMode, HashMap<String, String> params) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.playEarcon(earcon, queueMode, params);
|
|
||||||
}
|
|
||||||
return tts.playEarcon(earcon, queueMode, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int playSilence(long durationInMs, int queueMode, HashMap<String, String> params) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.playSilence(durationInMs, queueMode, params);
|
|
||||||
}
|
|
||||||
return tts.playSilence(durationInMs, queueMode, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setLanguage(Locale loc) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setLanguage(loc);
|
|
||||||
}
|
|
||||||
return tts.setLanguage(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setOnUtteranceCompletedListener(listener);
|
|
||||||
}
|
|
||||||
return tts.setOnUtteranceCompletedListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setPitch(float pitch) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setPitch(pitch);
|
|
||||||
}
|
|
||||||
return tts.setPitch(pitch);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setSpeechRate(float speechRate) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setSpeechRate(speechRate);
|
|
||||||
}
|
|
||||||
return tts.setSpeechRate(speechRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
if (tts == null) {
|
|
||||||
super.shutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tts.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int speak(
|
|
||||||
String text, int queueMode, HashMap<String, String> params) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.speak(text, queueMode, params);
|
|
||||||
}
|
|
||||||
return tts.speak(text, queueMode, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int stop() {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.stop();
|
|
||||||
}
|
|
||||||
return tts.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int synthesizeToFile(String text, HashMap<String, String> params, String filename) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.synthesizeToFile(text, params, filename);
|
|
||||||
}
|
|
||||||
return tts.synthesizeToFile(text, params, filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,16 +32,13 @@ public class FileUtilsTest extends TestCase {
|
|||||||
* Tests {@link FileUtils#getPath(String...)}.
|
* Tests {@link FileUtils#getPath(String...)}.
|
||||||
*/
|
*/
|
||||||
public void testBuildExternalDirectoryPath() {
|
public void testBuildExternalDirectoryPath() {
|
||||||
String expectedName = Environment.getExternalStorageDirectory() + File.separator
|
String expectedName = Environment.getExternalStorageDirectory() + File.separator + FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator + "c";
|
||||||
+ FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator
|
|
||||||
+ "c";
|
|
||||||
String dirName = FileUtils.getPath("a", "b", "c");
|
String dirName = FileUtils.getPath("a", "b", "c");
|
||||||
assertEquals(expectedName, dirName);
|
assertEquals(expectedName, dirName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the
|
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file is new.
|
||||||
* file is new.
|
|
||||||
*/
|
*/
|
||||||
public void testBuildUniqueFileName_new() {
|
public void testBuildUniqueFileName_new() {
|
||||||
String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext");
|
String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext");
|
||||||
@@ -49,8 +46,7 @@ public class FileUtilsTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the
|
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file exists already.
|
||||||
* file exists already.
|
|
||||||
*/
|
*/
|
||||||
public void testBuildUniqueFileName_exist() {
|
public void testBuildUniqueFileName_exist() {
|
||||||
// Expect "/default.prop" to exist on the phone/emulator
|
// Expect "/default.prop" to exist on the phone/emulator
|
||||||
@@ -69,8 +65,8 @@ public class FileUtilsTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in
|
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
|
||||||
* Chinese and Russian). Verifies that they are allowed.
|
* Verifies that they are allowed.
|
||||||
*/
|
*/
|
||||||
public void testSanitizeFileName_i18n() {
|
public void testSanitizeFileName_i18n() {
|
||||||
String name = "您好-привет";
|
String name = "您好-привет";
|
||||||
@@ -79,8 +75,8 @@ public class FileUtilsTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32
|
* Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32 characters.
|
||||||
* characters. Verifies that they are allowed.
|
* Verifies that they are allowed.
|
||||||
*/
|
*/
|
||||||
public void testSanitizeFileName_special_characters() {
|
public void testSanitizeFileName_special_characters() {
|
||||||
String name = "$%'-_@~`!(){}^#&+,;=[] ";
|
String name = "$%'-_@~`!(){}^#&+,;=[] ";
|
||||||
@@ -89,8 +85,8 @@ public class FileUtilsTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped
|
* Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped characters in a row.
|
||||||
* characters in a row. Verifies that they are collapsed into one underscore.
|
* Verifies that they are collapsed into one underscore.
|
||||||
*/
|
*/
|
||||||
public void testSanitizeFileName_collapse() {
|
public void testSanitizeFileName_collapse() {
|
||||||
String name = "hello//there";
|
String name = "hello//there";
|
||||||
@@ -99,8 +95,8 @@ public class FileUtilsTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#truncateFileName(File, String, String)}. Verifies
|
* Tests {@link FileUtils#truncateFileName(File, String, String)}.
|
||||||
* the a long file name is truncated.
|
* Verifies the a long file name is truncated.
|
||||||
*/
|
*/
|
||||||
public void testTruncateFileName() {
|
public void testTruncateFileName() {
|
||||||
File directory = new File("/dir1/dir2/");
|
File directory = new File("/dir1/dir2/");
|
||||||
@@ -115,7 +111,6 @@ public class FileUtilsTest extends TestCase {
|
|||||||
for (int i = 0; i < truncated.length(); i++) {
|
for (int i = 0; i < truncated.length(); i++) {
|
||||||
assertEquals('a', truncated.charAt(i));
|
assertEquals('a', truncated.charAt(i));
|
||||||
}
|
}
|
||||||
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH,
|
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
|
||||||
new File(directory, truncated + suffix).getPath().length());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class StringUtilsTest {
|
|||||||
assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300);
|
assertGetTime("2010-05-04T03:02:01.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.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.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||||
assertGetTime("2010-05-04T03:02:01.3525Z", 2010, 5, 4, 3, 2, 1, 352);
|
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 353);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,8 +149,7 @@ public class StringUtilsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time
|
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time zone.
|
||||||
* zone.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTime_fractionalAndTimezone() {
|
public void testGetTime_fractionalAndTimezone() {
|
||||||
@@ -161,8 +160,7 @@ public class StringUtilsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts the {@link StringUtils#getTime(String)} returns the expected
|
* Asserts the {@link StringUtils#getTime(String)} returns the expected values.
|
||||||
* values.
|
|
||||||
*
|
*
|
||||||
* @param xmlDateTime the xml date time string
|
* @param xmlDateTime the xml date time string
|
||||||
* @param year the expected year
|
* @param year the expected year
|
||||||
@@ -173,8 +171,7 @@ public class StringUtilsTest {
|
|||||||
* @param second the expected second
|
* @param second the expected second
|
||||||
* @param millisecond the expected milliseconds
|
* @param millisecond the expected milliseconds
|
||||||
*/
|
*/
|
||||||
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute,
|
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute, int second, int millisecond) {
|
||||||
int second, int millisecond) {
|
|
||||||
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||||
calendar.set(year, month - 1, day, hour, minute, second);
|
calendar.set(year, month - 1, day, hour, minute, second);
|
||||||
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
|
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
|
||||||
@@ -185,7 +182,7 @@ public class StringUtilsTest {
|
|||||||
* Tests {@link StringUtils#getTimeParts(long)} with a positive number.
|
* Tests {@link StringUtils#getTimeParts(long)} with a positive number.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTimeParts_postive() {
|
public void testGetTimeParts_positive() {
|
||||||
int[] parts = StringUtils.getTimeParts(61000);
|
int[] parts = StringUtils.getTimeParts(61000);
|
||||||
Assert.assertEquals(1, parts[0]);
|
Assert.assertEquals(1, parts[0]);
|
||||||
Assert.assertEquals(1, parts[1]);
|
Assert.assertEquals(1, parts[1]);
|
||||||
@@ -202,4 +199,24 @@ public class StringUtilsTest {
|
|||||||
Assert.assertEquals(-1, parts[1]);
|
Assert.assertEquals(-1, parts[1]);
|
||||||
Assert.assertEquals(0, parts[2]);
|
Assert.assertEquals(0, parts[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFormatDecimal() {
|
||||||
|
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 0));
|
||||||
|
Assert.assertEquals("0", StringUtils.formatDecimal(0.1, 0));
|
||||||
|
Assert.assertEquals("1", StringUtils.formatDecimal(1.1, 0));
|
||||||
|
Assert.assertEquals("10", StringUtils.formatDecimal(10, 0));
|
||||||
|
Assert.assertEquals("10", StringUtils.formatDecimal(10.1, 0));
|
||||||
|
Assert.assertEquals("0", StringUtils.formatDecimal(-0.1, 0));
|
||||||
|
|
||||||
|
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 2));
|
||||||
|
Assert.assertEquals("0.1", StringUtils.formatDecimal(0.1, 2));
|
||||||
|
Assert.assertEquals("1.1", StringUtils.formatDecimal(1.1, 2));
|
||||||
|
Assert.assertEquals("10", StringUtils.formatDecimal(10, 2));
|
||||||
|
Assert.assertEquals("10.1", StringUtils.formatDecimal(10.1, 2));
|
||||||
|
Assert.assertEquals("10.11", StringUtils.formatDecimal(10.111, 2));
|
||||||
|
Assert.assertEquals("-0.1", StringUtils.formatDecimal(-0.1, 2));
|
||||||
|
|
||||||
|
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ public class TrackNameUtilsTest {
|
|||||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests when the track_name_key is
|
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
|
||||||
* settings_recording_track_name_date_local_value.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTrackName_date_local() {
|
public void testTrackName_date_local() {
|
||||||
@@ -55,8 +54,7 @@ public class TrackNameUtilsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests when the track_name_key is
|
* Tests when the track_name_key is settings_recording_track_name_date_iso_8601_value.
|
||||||
* settings_recording_track_name_date_iso_8601_value.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTrackName_date_iso_8601() {
|
public void testTrackName_date_iso_8601() {
|
||||||
@@ -66,8 +64,7 @@ public class TrackNameUtilsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests when the track_name_key is
|
* Tests when the track_name_key is settings_recording_track_name_number_value.
|
||||||
* settings_recording_track_name_number_value.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTrackName_number() {
|
public void testTrackName_number() {
|
||||||
|
|||||||
@@ -14,75 +14,24 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="2947"
|
android:versionCode="3137"
|
||||||
android:versionName="v3.0.1"> <!-- For FDroid. -->
|
android:versionName="v3.2.6"> <!-- For FDroid. -->
|
||||||
<!-- Permission to initialize services -->
|
<!-- Permission to initialize services -->
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
||||||
<!-- Permissions to record locations -->
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
<!-- Permissions to access sensors -->
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
||||||
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
|
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
||||||
<!-- Permission to read/write external storage -->
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<!-- Required features -->
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.location.gps"
|
|
||||||
android:required="true" />
|
|
||||||
<!-- Optional features -->
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.bluetooth"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.camera"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.faketouch"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.sensor.compass"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.telephony"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.telephony.gsm"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.touchscreen"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.touchscreen.multitouch"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<supports-screens
|
|
||||||
android:anyDensity="true"
|
|
||||||
android:largeScreens="true"
|
|
||||||
android:normalScreens="true"
|
|
||||||
android:smallScreens="true"
|
|
||||||
android:xlargeScreens="true" />
|
|
||||||
<application
|
<application
|
||||||
android:name=".InitPreferences"
|
android:name=".Startup"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@drawable/ic_logo"
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/ThemeCustom"
|
android:theme="@style/ThemeCustom">
|
||||||
tools:replace="android:label">
|
|
||||||
<!-- Activities -->
|
<!-- Activities -->
|
||||||
<activity android:name=".AboutActivity" />
|
<activity android:name=".AboutActivity" />
|
||||||
<activity
|
|
||||||
android:name=".AggregatedStatsActivity"
|
|
||||||
android:label="@string/menu_aggregated_statistics" />
|
|
||||||
<activity android:name=".DeleteActivity" />
|
<activity android:name=".DeleteActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".io.file.importer.ImportActivity"
|
android:name=".io.file.importer.ImportActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent">
|
android:theme="@style/ThemeCustomTransparent">
|
||||||
@@ -100,8 +49,11 @@ limitations under the License.
|
|||||||
<data android:scheme="file" />
|
<data android:scheme="file" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".MarkerEditActivity" />
|
<activity android:name=".MarkerEditActivity" />
|
||||||
|
|
||||||
<activity android:name=".MarkerDetailActivity" />
|
<activity android:name=".MarkerDetailActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MarkerListActivity"
|
android:name=".MarkerListActivity"
|
||||||
android:label="@string/menu_markers">
|
android:label="@string/menu_markers">
|
||||||
@@ -109,6 +61,7 @@ limitations under the License.
|
|||||||
android:name="android.app.default_searchable"
|
android:name="android.app.default_searchable"
|
||||||
android:value=".SearchListActivity" />
|
android:value=".SearchListActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SearchListActivity"
|
android:name=".SearchListActivity"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop">
|
||||||
@@ -117,10 +70,15 @@ limitations under the License.
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.searchable"
|
android:name="android.app.searchable"
|
||||||
android:resource="@xml/search" />
|
android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".TrackDetailActivity" />
|
|
||||||
|
<activity
|
||||||
|
android:name=".TrackDetailActivity"
|
||||||
|
android:screenOrientation="userPortrait" />
|
||||||
|
|
||||||
<activity android:name=".TrackEditActivity" />
|
<activity android:name=".TrackEditActivity" />
|
||||||
|
|
||||||
<activity android:name=".TrackListActivity">
|
<activity android:name=".TrackListActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@@ -130,59 +88,19 @@ limitations under the License.
|
|||||||
android:name="android.app.default_searchable"
|
android:name="android.app.default_searchable"
|
||||||
android:value=".SearchListActivity" />
|
android:value=".SearchListActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".io.file.exporter.ExportActivity"
|
android:name=".io.file.exporter.ExportActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent" />
|
android:theme="@style/ThemeCustomTransparent" />
|
||||||
<activity android:name=".settings.RecordingSettingsActivity" />
|
|
||||||
<activity android:name=".settings.SettingsActivity" />
|
|
||||||
<activity android:name=".settings.StatsSettingsActivity" />
|
|
||||||
<activity
|
|
||||||
android:name=".widgets.TrackWidgetConfigActivity"
|
|
||||||
android:label="@string/track_widget_config_title">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<service
|
<activity android:name=".settings.SettingsActivity" />
|
||||||
android:name=".services.ControlRecordingService"
|
|
||||||
android:exported="false" />
|
<!-- Services -->
|
||||||
<service
|
|
||||||
android:name=".services.TrackRecordingService"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="false"
|
|
||||||
android:icon="@drawable/ic_arrow_320"
|
|
||||||
android:label="@string/recording_service" />
|
|
||||||
<!-- Receivers -->
|
|
||||||
<receiver android:name=".BootReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
<receiver android:name=".widgets.TrackWidgetProvider">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
|
||||||
<action android:name="TRACK_PAUSED" />
|
|
||||||
<action android:name="TRACK_RESUMED" />
|
|
||||||
<action android:name="TRACK_STARTED" />
|
|
||||||
<action android:name="TRACK_STOPPED" />
|
|
||||||
<action android:name="TRACK_UPDATE" />
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data
|
|
||||||
android:name="android.appwidget.provider"
|
|
||||||
android:resource="@xml/track_widget_info" />
|
|
||||||
</receiver>
|
|
||||||
<!-- Providers -->
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".content.CustomContentProvider"
|
android:name=".content.ShareContentProvider"
|
||||||
android:authorities="${applicationId}"
|
android:authorities="${applicationId}.content"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true" />
|
android:grantUriPermissions="true" />
|
||||||
<!-- Search suggestion provider -->
|
|
||||||
<provider
|
|
||||||
android:name=".content.SearchEngineProvider"
|
|
||||||
android:authorities="${applicationId}.search"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
@@ -193,5 +111,72 @@ limitations under the License.
|
|||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</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>
|
</application>
|
||||||
|
<supports-screens
|
||||||
|
android:anyDensity="true"
|
||||||
|
android:largeScreens="true"
|
||||||
|
android:normalScreens="true"
|
||||||
|
android:smallScreens="true"
|
||||||
|
android:xlargeScreens="true" />
|
||||||
|
<!-- Permissions to record locations -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.location.gps"
|
||||||
|
android:required="true" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.bluetooth"
|
||||||
|
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>
|
</manifest>
|
||||||
|
|||||||
@@ -18,16 +18,14 @@ package de.dennisguse.opentracks;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.speech.tts.TextToSpeech;
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.view.Menu;
|
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractActivity extends FragmentActivity {
|
public abstract class AbstractActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -37,16 +35,13 @@ public abstract class AbstractActivity extends FragmentActivity {
|
|||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||||
|
|
||||||
setContentView(getLayoutResId());
|
setContentView(getLayoutResId());
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the layout resource id.
|
* Gets the layout resource id.
|
||||||
*/
|
*/
|
||||||
protected abstract int getLayoutResId();
|
protected abstract int getLayoutResId();
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
TrackIconUtils.setMenuIconColor(menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class for the following common tasks across
|
||||||
|
* {@link TrackListActivity}, {@link TrackDetailActivity}, and
|
||||||
|
* {@link SearchListActivity}:
|
||||||
|
* <p>
|
||||||
|
* - share track <br>
|
||||||
|
* - delete tracks <br>
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
|
||||||
|
|
||||||
|
private static final String TAG = AbstractListActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
protected static final int GPS_REQUEST_CODE = 6;
|
||||||
|
private static final int DELETE_REQUEST_CODE = 3;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == DELETE_REQUEST_CODE) {
|
||||||
|
onDeleted();
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete tracks.
|
||||||
|
*
|
||||||
|
* @param trackIds the track ids
|
||||||
|
*/
|
||||||
|
protected void deleteTracks(long[] trackIds) {
|
||||||
|
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfirmDeleteDone(long[] trackIds) {
|
||||||
|
boolean stopRecording = false;
|
||||||
|
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||||
|
stopRecording = true;
|
||||||
|
} else {
|
||||||
|
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||||
|
for (long trackId : trackIds) {
|
||||||
|
if (trackId == recordingTrackId) {
|
||||||
|
stopRecording = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stopRecording) {
|
||||||
|
getTrackRecordingServiceConnection().stopRecording(this, false);
|
||||||
|
}
|
||||||
|
Intent intent = IntentUtils.newIntent(this, DeleteActivity.class);
|
||||||
|
intent.putExtra(DeleteActivity.EXTRA_TRACK_IDS, trackIds);
|
||||||
|
startActivityForResult(intent, DELETE_REQUEST_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the track recording service connection.
|
||||||
|
* For stopping the current recording if need to delete the current recording track.
|
||||||
|
*/
|
||||||
|
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after {@link DeleteActivity} returns its result.
|
||||||
|
*/
|
||||||
|
abstract protected void onDeleted();
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
|
||||||
import de.dennisguse.opentracks.content.Track;
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
|
||||||
import de.dennisguse.opentracks.util.StatsUtils;
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An activity to view aggregated stats from all recorded tracks.
|
|
||||||
*
|
|
||||||
* @author Fergus Nelson
|
|
||||||
*/
|
|
||||||
public class AggregatedStatsActivity extends AbstractActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
StatsUtils.setTripStatisticsValues(this, this, null, getTripStatistics(), null);
|
|
||||||
StatsUtils.setLocationValues(this, this, null, null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayoutResId() {
|
|
||||||
return R.layout.stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the aggregated trip statistics for all the recorded tracks or null if
|
|
||||||
* there is no track.
|
|
||||||
*/
|
|
||||||
private TripStatistics getTripStatistics() {
|
|
||||||
List<Track> tracks = ContentProviderUtils.Factory.get(this).getAllTracks();
|
|
||||||
TripStatistics tripStatistics = null;
|
|
||||||
if (!tracks.isEmpty()) {
|
|
||||||
tripStatistics = new TripStatistics(tracks.iterator().next().getTripStatistics());
|
|
||||||
for (int i = 1; i < tracks.size(); i++) {
|
|
||||||
tripStatistics.merge(tracks.get(i).getTripStatistics());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tripStatistics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,16 +25,16 @@ import android.graphics.Path;
|
|||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class encapsulates the meta data for one series of the chart values.
|
* This class encapsulates the meta data for one series of the chart values.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class ChartValueSeries {
|
class ChartValueSeries {
|
||||||
|
|
||||||
private static final float STROKE_WIDTH = 2f;
|
private static final float STROKE_WIDTH = 2f;
|
||||||
|
|
||||||
@@ -68,8 +68,7 @@ public class ChartValueSeries {
|
|||||||
* @param fillColor the fill color
|
* @param fillColor the fill color
|
||||||
* @param strokeColor the stroke color
|
* @param strokeColor the stroke color
|
||||||
*/
|
*/
|
||||||
public ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues,
|
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
|
||||||
int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
|
|
||||||
this.absoluteMin = absoluteMin;
|
this.absoluteMin = absoluteMin;
|
||||||
this.absoluteMax = absoluteMax;
|
this.absoluteMax = absoluteMax;
|
||||||
this.intervalValues = intervalValues;
|
this.intervalValues = intervalValues;
|
||||||
@@ -108,7 +107,7 @@ public class ChartValueSeries {
|
|||||||
/**
|
/**
|
||||||
* Returns true if the series is enabled.
|
* Returns true if the series is enabled.
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled() {
|
boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,14 +116,14 @@ public class ChartValueSeries {
|
|||||||
*
|
*
|
||||||
* @param enabled true to enable
|
* @param enabled true to enable
|
||||||
*/
|
*/
|
||||||
public void setEnabled(boolean enabled) {
|
void setEnabled(boolean enabled) {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the series has data.
|
* Returns true if the series has data.
|
||||||
*/
|
*/
|
||||||
public boolean hasData() {
|
boolean hasData() {
|
||||||
return extremityMonitor.hasData();
|
return extremityMonitor.hasData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,14 +132,14 @@ public class ChartValueSeries {
|
|||||||
*
|
*
|
||||||
* @param value the new value
|
* @param value the new value
|
||||||
*/
|
*/
|
||||||
public void update(double value) {
|
void update(double value) {
|
||||||
extremityMonitor.update(value);
|
extremityMonitor.update(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the path.
|
* Gets the path.
|
||||||
*/
|
*/
|
||||||
public Path getPath() {
|
Path getPath() {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +148,7 @@ public class ChartValueSeries {
|
|||||||
*
|
*
|
||||||
* @param canvas the canvas
|
* @param canvas the canvas
|
||||||
*/
|
*/
|
||||||
public void drawPath(Canvas canvas) {
|
void drawPath(Canvas canvas) {
|
||||||
canvas.drawPath(path, fillPaint);
|
canvas.drawPath(path, fillPaint);
|
||||||
canvas.drawPath(path, strokePaint);
|
canvas.drawPath(path, strokePaint);
|
||||||
}
|
}
|
||||||
@@ -157,7 +156,7 @@ public class ChartValueSeries {
|
|||||||
/**
|
/**
|
||||||
* Updates the y axis dimension.
|
* Updates the y axis dimension.
|
||||||
*/
|
*/
|
||||||
public void updateDimension() {
|
void updateDimension() {
|
||||||
double min = hasData() ? extremityMonitor.getMin() : 0.0;
|
double min = hasData() ? extremityMonitor.getMin() : 0.0;
|
||||||
double max = hasData() ? extremityMonitor.getMax() : 1.0;
|
double max = hasData() ? extremityMonitor.getMax() : 1.0;
|
||||||
min = Math.max(min, absoluteMin);
|
min = Math.max(min, absoluteMin);
|
||||||
@@ -204,14 +203,15 @@ public class ChartValueSeries {
|
|||||||
/**
|
/**
|
||||||
* Gets the interval value.
|
* Gets the interval value.
|
||||||
*/
|
*/
|
||||||
public int getInterval() {
|
@VisibleForTesting
|
||||||
|
int getInterval() {
|
||||||
return interval;
|
return interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the min marker value.
|
* Gets the min marker value.
|
||||||
*/
|
*/
|
||||||
public int getMinMarkerValue() {
|
int getMinMarkerValue() {
|
||||||
return minMarkerValue;
|
return minMarkerValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,28 +226,28 @@ public class ChartValueSeries {
|
|||||||
/**
|
/**
|
||||||
* Gets the title id.
|
* Gets the title id.
|
||||||
*/
|
*/
|
||||||
public int getTitleId(boolean metricUnits) {
|
int getTitleId(boolean metricUnits) {
|
||||||
return metricUnits ? metricTitleId : imperialTitleId;
|
return metricUnits ? metricTitleId : imperialTitleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the title paint.
|
* Gets the title paint.
|
||||||
*/
|
*/
|
||||||
public Paint getTitlePaint() {
|
Paint getTitlePaint() {
|
||||||
return titlePaint;
|
return titlePaint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the marker paint.
|
* Gets the marker paint.
|
||||||
*/
|
*/
|
||||||
public Paint getMarkerPaint() {
|
Paint getMarkerPaint() {
|
||||||
return markerPaint;
|
return markerPaint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the largest marker.
|
* Gets the largest marker.
|
||||||
*/
|
*/
|
||||||
public String getLargestMarker() {
|
String getLargestMarker() {
|
||||||
String minMarker = numberFormat.format(getMinMarkerValue());
|
String minMarker = numberFormat.format(getMinMarkerValue());
|
||||||
String maxMarker = numberFormat.format(getMaxMarkerValue());
|
String maxMarker = numberFormat.format(getMaxMarkerValue());
|
||||||
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
|
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
|
||||||
@@ -258,7 +258,7 @@ public class ChartValueSeries {
|
|||||||
*
|
*
|
||||||
* @param value the value
|
* @param value the value
|
||||||
*/
|
*/
|
||||||
public String formatMarker(int value) {
|
String formatMarker(int value) {
|
||||||
return numberFormat.format(value);
|
return numberFormat.format(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ public class ChartView extends View {
|
|||||||
markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray));
|
markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray));
|
||||||
markerPaint.setAntiAlias(false);
|
markerPaint.setAntiAlias(false);
|
||||||
|
|
||||||
pointer = context.getResources().getDrawable(R.drawable.ic_arrow_180);
|
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
||||||
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
||||||
|
|
||||||
Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin);
|
Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin);
|
||||||
@@ -223,8 +223,8 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets chart by distance. It is expected that after changing this value, data
|
* Sets chart by distance.
|
||||||
* will be reloaded.
|
* It is expected that after changing this value, data will be reloaded.
|
||||||
*
|
*
|
||||||
* @param value true for by distance, false for by time
|
* @param value true for by distance, false for by time
|
||||||
*/
|
*/
|
||||||
@@ -449,8 +449,7 @@ public class ChartView extends View {
|
|||||||
synchronized (waypoints) {
|
synchronized (waypoints) {
|
||||||
for (int i = 0; i < waypoints.size(); i++) {
|
for (int i = 0; i < waypoints.size(); i++) {
|
||||||
Waypoint waypoint = waypoints.get(i);
|
Waypoint waypoint = waypoints.get(i);
|
||||||
int distance = Math.abs(
|
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
||||||
getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
nearestWaypoint = waypoint;
|
nearestWaypoint = waypoint;
|
||||||
@@ -567,7 +566,7 @@ public class ChartView extends View {
|
|||||||
* @param canvas the canvas
|
* @param canvas the canvas
|
||||||
*/
|
*/
|
||||||
private void drawSeriesTitles(Canvas canvas) {
|
private void drawSeriesTitles(Canvas canvas) {
|
||||||
int[] titleDimensions = getTitleDimenions();
|
int[] titleDimensions = getTitleDimensions();
|
||||||
int lines = titleDimensions[0];
|
int lines = titleDimensions[0];
|
||||||
int lineHeight = titleDimensions[1];
|
int lineHeight = titleDimensions[1];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -585,10 +584,10 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the title dimensions. Returns an array of 2 integers, first element is
|
* Gets the title dimensions.
|
||||||
* the number of lines and the second element is the line height.
|
* Returns an array of 2 integers, first element is the number of lines and the second element is the line height.
|
||||||
*/
|
*/
|
||||||
private int[] getTitleDimenions() {
|
private int[] getTitleDimensions() {
|
||||||
int lines = 0;
|
int lines = 0;
|
||||||
int lineHeight = 0;
|
int lineHeight = 0;
|
||||||
for (int i = 0; i < series.length; i++) {
|
for (int i = 0; i < series.length; i++) {
|
||||||
@@ -768,8 +767,7 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates paths. The path needs to be updated any time after the data or the
|
* Updates paths. The path needs to be updated any time after the data or the dimensions change.
|
||||||
* dimensions change.
|
|
||||||
*/
|
*/
|
||||||
private void updatePaths() {
|
private void updatePaths() {
|
||||||
synchronized (chartData) {
|
synchronized (chartData) {
|
||||||
@@ -867,7 +865,7 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftBorder = (int) (density * BORDER + markerLength);
|
leftBorder = (int) (density * BORDER + markerLength);
|
||||||
int[] titleDimensions = getTitleDimenions();
|
int[] titleDimensions = getTitleDimensions();
|
||||||
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
||||||
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
||||||
// border + x axis marker + spacer + .5 x axis label
|
// border + x axis marker + spacer + .5 x axis label
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2008 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common constants.
|
|
||||||
*
|
|
||||||
* @author Leif Hendrik Wilden
|
|
||||||
*/
|
|
||||||
public class Constants {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum number of waypoints that will be loaded at one time.
|
|
||||||
*/
|
|
||||||
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
|
||||||
|
|
||||||
private Constants() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
|
|
||||||
public class InitPreferences extends Application {
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
|
|
||||||
//Set default values of preferences on first start.
|
|
||||||
PreferencesUtils.resetPreferences(this, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,30 +20,35 @@ import android.database.Cursor;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
|
import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to display marker detail info.
|
* An activity to display marker detail info.
|
||||||
|
* <p>
|
||||||
|
* Allows to swipe to the next and previous marker.
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller {
|
public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller {
|
||||||
|
|
||||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||||
|
|
||||||
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
|
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
|
||||||
|
|
||||||
private ArrayList<Long> markerIds;
|
private List<Long> markerIds;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle) {
|
protected void onCreate(Bundle bundle) {
|
||||||
@@ -64,26 +69,40 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
|
|
||||||
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1)) {
|
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1)) {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
/*
|
for (int i = 0; i < cursor.getCount(); i++) {
|
||||||
* Yes, this will skip the first waypoint and that is intentional as the
|
Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor);
|
||||||
* first waypoint holds the stats for the track.
|
|
||||||
*/
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
Waypoint current = contentProviderUtils.createWaypoint(cursor);
|
|
||||||
|
|
||||||
markerIds.add(current.getId());
|
if (!currentMarker.isTripStatistics()) {
|
||||||
if (current.getId() == markerId) {
|
markerIds.add(currentMarker.getId());
|
||||||
markerIndex = markerIds.size() - 1;
|
if (currentMarker.getId() == markerId) {
|
||||||
|
markerIndex = markerIds.size() - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursor.moveToNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewPager viewPager = findViewById(R.id.maker_detail_activity_view_pager);
|
final ViewPager viewPager = findViewById(R.id.maker_detail_activity_view_pager);
|
||||||
viewPager.setAdapter(new MarkerDetailPagerAdapter(getSupportFragmentManager()));
|
final MarkerDetailPagerAdapter markerAdapter = new MarkerDetailPagerAdapter(getSupportFragmentManager());
|
||||||
if (markerIndex != -1) {
|
viewPager.setAdapter(markerAdapter);
|
||||||
viewPager.setCurrentItem(markerIndex);
|
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
setTitle(markerAdapter.getPageTitle(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageScrollStateChanged(int state) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewPager.setCurrentItem(markerIndex == -1 ? 0 : markerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -108,14 +127,20 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
*/
|
*/
|
||||||
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
|
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
|
||||||
public MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
||||||
super(fragmentManager);
|
super(fragmentManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@NonNull
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
String title = getString(R.string.marker_title, position + 1, getCount());
|
return MarkerDetailFragment.newInstance(markerIds.get(position));
|
||||||
return MarkerDetailFragment.newInstance(markerIds.get(position), title);
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
return getString(R.string.marker_title, position + 1, getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import de.dennisguse.opentracks.content.Waypoint;
|
|||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to add/edit a marker.
|
* An activity to add/edit a marker.
|
||||||
@@ -42,6 +40,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||||
|
|
||||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||||
private long trackId;
|
private long trackId;
|
||||||
private long markerId;
|
private long markerId;
|
||||||
@@ -91,7 +90,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,14 +120,14 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
} else {
|
} else {
|
||||||
saveMarker();
|
saveMarker();
|
||||||
}
|
}
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newMarker) {
|
if (newMarker) {
|
||||||
statisticsSection.setVisibility(View.GONE);
|
statisticsSection.setVisibility(View.GONE);
|
||||||
waypointSection.setVisibility(View.VISIBLE);
|
waypointSection.setVisibility(View.VISIBLE);
|
||||||
int nextWaypointNumber = trackId == -1L ? -1
|
int nextWaypointNumber = trackId == -1L ? -1 : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
||||||
: ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
|
||||||
if (nextWaypointNumber == -1) {
|
if (nextWaypointNumber == -1) {
|
||||||
nextWaypointNumber = 0;
|
nextWaypointNumber = 0;
|
||||||
}
|
}
|
||||||
@@ -168,8 +167,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointDescription.getText().toString(),
|
waypointDescription.getText().toString(),
|
||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
TrackRecordingServiceConnectionUtils.addMarker(
|
trackRecordingServiceConnection.addMarker(this, waypointCreationRequest);
|
||||||
this, trackRecordingServiceConnection, waypointCreationRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,14 +22,11 @@ import android.content.SharedPreferences;
|
|||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
@@ -46,10 +43,10 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|||||||
import de.dennisguse.opentracks.content.WaypointsColumns;
|
import de.dennisguse.opentracks.content.WaypointsColumns;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity to show a list of markers in a track.
|
* Activity to show a list of markers in a track.
|
||||||
@@ -66,10 +63,13 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
|
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
|
||||||
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
|
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
|
||||||
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
|
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
|
||||||
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
/*
|
/*
|
||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||||
* class. Anonymous inner class will get garbage collected.
|
* class. Anonymous inner class will get garbage collected.
|
||||||
@@ -79,11 +79,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
// Note that the key can be null
|
// Note that the key can be null
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) {
|
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) {
|
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -128,6 +128,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
@@ -173,7 +175,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
listView.setAdapter(resourceCursorAdapter);
|
listView.setAdapter(resourceCursorAdapter);
|
||||||
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||||
|
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, new LoaderCallbacks<Cursor>() {
|
LoaderManager.getInstance(this).initLoader(0, null, new LoaderCallbacks<Cursor>() {
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -232,7 +234,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
|
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
|
||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.marker_list_search);
|
searchMenuItem = menu.findItem(R.id.marker_list_search);
|
||||||
AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, null);
|
ActivityUtils.configureSearchWidget(this, searchMenuItem, null);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -254,24 +256,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
|
||||||
getMenuInflater().inflate(R.menu.list_context_menu, menu);
|
|
||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
|
||||||
contextualActionModeCallback.onPrepare(menu, new int[]{info.position}, new long[]{info.id}, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
|
||||||
if (handleContextItem(item.getItemId(), new long[]{info.id})) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onContextItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a context item selection.
|
* Handles a context item selection.
|
||||||
*
|
*
|
||||||
@@ -284,10 +268,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
switch (itemId) {
|
switch (itemId) {
|
||||||
case R.id.list_context_menu_show_on_map:
|
case R.id.list_context_menu_show_on_map:
|
||||||
if (markerIds.length == 1) {
|
if (markerIds.length == 1) {
|
||||||
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(markerIds[0]));
|
||||||
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerIds[0]);
|
|
||||||
//TODO Use IntentUtils.newShowOnMapIntent()
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_edit:
|
case R.id.list_context_menu_edit:
|
||||||
@@ -301,8 +282,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
if (markerIds.length > 1 && markerIds.length == listView.getCount()) {
|
if (markerIds.length > 1 && markerIds.length == listView.getCount()) {
|
||||||
markerIds = new long[]{-1L};
|
markerIds = new long[]{-1L};
|
||||||
}
|
}
|
||||||
DeleteMarkerDialogFragment.newInstance(markerIds)
|
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||||
.show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
|
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_select_all:
|
case R.id.list_context_menu_select_all:
|
||||||
int size = listView.getCount();
|
int size = listView.getCount();
|
||||||
|
|||||||
@@ -17,48 +17,22 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.SearchRecentSuggestions;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
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.SearchEngineProvider;
|
|
||||||
import de.dennisguse.opentracks.content.Track;
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -66,14 +40,33 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.SortedSet;
|
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.fragments.ConfirmDeleteDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
import de.dennisguse.opentracks.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.StringUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to display a list of search results.
|
* An activity to display a list of searchable results.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
* <p>
|
* <p>
|
||||||
* TODO: allow to refine search (present search in context menu)
|
* TODO: allow to refine searchable (present searchable in context menu)
|
||||||
*/
|
*/
|
||||||
public class SearchListActivity extends AbstractTrackActivity implements DeleteMarkerCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
public class SearchListActivity extends AbstractListActivity implements DeleteMarkerCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
||||||
|
|
||||||
private static final String TAG = SearchListActivity.class.getSimpleName();
|
private static final String TAG = SearchListActivity.class.getSimpleName();
|
||||||
|
|
||||||
@@ -91,65 +84,73 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
private static final String PHOTO_URL_FIELD = "photoUrl";
|
private static final String PHOTO_URL_FIELD = "photoUrl";
|
||||||
private static final String MARKER_LATITUDE_FIELD = "latitude";
|
private static final String MARKER_LATITUDE_FIELD = "latitude";
|
||||||
private static final String MARKER_LONGITUDE_FIELD = "longitude";
|
private static final String MARKER_LONGITUDE_FIELD = "longitude";
|
||||||
|
|
||||||
private static final String TRACK_ID_FIELD = "trackId";
|
private static final String TRACK_ID_FIELD = "trackId";
|
||||||
private static final String MARKER_ID_FIELD = "markerId";
|
private static final String MARKER_ID_FIELD = "markerId";
|
||||||
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
|
|
||||||
private SearchEngine searchEngine;
|
private SearchEngine searchEngine;
|
||||||
private SearchRecentSuggestions searchRecentSuggestions;
|
|
||||||
private ArrayAdapter<Map<String, Object>> arrayAdapter;
|
private ArrayAdapter<Map<String, Object>> arrayAdapter;
|
||||||
|
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
|
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
|
|
||||||
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
// Callback when an item is selected in the contextual action mode
|
// Callback when an item is selected in the contextual action mode
|
||||||
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||||
@Override
|
|
||||||
public boolean onClick(int itemId, int[] positions, long[] ids) {
|
|
||||||
return handleContextItem(itemId, positions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
|
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
|
||||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||||
boolean isSingleSelection = positions.length == 1;
|
boolean isSingleSelection = positions.length == 1;
|
||||||
|
|
||||||
boolean isSingleSelectionTrack;
|
boolean isSingleSelectionTrack;
|
||||||
if (isSingleSelection) {
|
if (isSingleSelection) {
|
||||||
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
|
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
|
||||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
|
||||||
|
|
||||||
isSingleSelectionTrack = item.get(MARKER_ID_FIELD) == null;
|
isSingleSelectionTrack = item.get(MARKER_ID_FIELD) == null;
|
||||||
} else {
|
} else {
|
||||||
isSingleSelectionTrack = false;
|
isSingleSelectionTrack = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not recording, one item, item is a track
|
// Not recording, one item, item is a track
|
||||||
menu.findItem(R.id.list_context_menu_share).setVisible(!isRecording && isSingleSelection && isSingleSelectionTrack);
|
MenuItem shareMenuItem = menu.findItem(R.id.list_context_menu_share);
|
||||||
//TODO Setup shareIntent.
|
if (isSingleSelectionTrack) {
|
||||||
|
shareMenuItem.setVisible(!isRecording);
|
||||||
|
}
|
||||||
|
|
||||||
// One item, item is a marker
|
// One item, item is a marker
|
||||||
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection && !isSingleSelectionTrack);
|
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection && !isSingleSelectionTrack);
|
||||||
// One item, can be a track or a marker
|
// One item, can be a track or a marker
|
||||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||||
// One item. If track, no restriction.
|
// One item. If track, no restriction.
|
||||||
menu.findItem(R.id.list_context_menu_delete).setVisible(isSingleSelection && (isSingleSelectionTrack));
|
menu.findItem(R.id.list_context_menu_delete).setVisible(isSingleSelection && isSingleSelectionTrack);
|
||||||
// Disable select all, no action is available for multiple selection
|
// Disable select all, no action is available for multiple selection
|
||||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(false);
|
menu.findItem(R.id.list_context_menu_select_all).setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onClick(int itemId, int[] positions, long[] ids) {
|
||||||
|
return handleContextItem(itemId, positions);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
|
||||||
private final OnSharedPreferenceChangeListener
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
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);
|
metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) {
|
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) {
|
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -167,22 +168,22 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
|
||||||
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||||
searchEngine = new SearchEngine(contentProviderUtils);
|
searchEngine = new SearchEngine(contentProviderUtils);
|
||||||
searchRecentSuggestions = SearchEngineProvider.newHelper(this);
|
|
||||||
|
|
||||||
arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) {
|
arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
|
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
|
||||||
View view;
|
View view = convertView;
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
view = getLayoutInflater().inflate(R.layout.list_item, parent, false);
|
view = getLayoutInflater().inflate(R.layout.list_item, parent, false);
|
||||||
} else {
|
|
||||||
view = convertView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> resultMap = getItem(position);
|
Map<String, Object> resultMap = getItem(position);
|
||||||
boolean isRecording = (boolean) resultMap.get(IS_RECORDING_FIELD);
|
boolean isRecording = (boolean) resultMap.get(IS_RECORDING_FIELD);
|
||||||
boolean isPaused = (boolean) resultMap.get(IS_PAUSED_FIELD);
|
boolean isPaused = (boolean) resultMap.get(IS_PAUSED_FIELD);
|
||||||
@@ -213,18 +214,16 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||||
Intent intent;
|
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class);
|
||||||
if (markerId != null) {
|
if (markerId != null) {
|
||||||
intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class)
|
intent = intent.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
||||||
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
|
||||||
} else {
|
} else {
|
||||||
intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class)
|
intent = intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
|
||||||
}
|
}
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +232,7 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
super.onStart();
|
super.onStart();
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -256,43 +255,11 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
public void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.search_list, menu);
|
|
||||||
menu.findItem(R.id.search_list_search).setVisible(false);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.search_list_search) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
|
||||||
getMenuInflater().inflate(R.menu.list_context_menu, menu);
|
|
||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
|
||||||
contextualActionModeCallback.onPrepare(menu, new int[]{info.position}, new long[]{info.id}, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
|
||||||
if (handleContextItem(item.getItemId(), new int[]{info.position})) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onContextItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
||||||
@@ -317,12 +284,12 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||||
Intent intent;
|
Intent intent;
|
||||||
switch (itemId) {
|
switch (itemId) {
|
||||||
case R.id.list_context_menu_share:
|
|
||||||
//TODO
|
|
||||||
Log.e(TAG, "Not implemented");
|
|
||||||
return true;
|
|
||||||
case R.id.list_context_menu_show_on_map:
|
case R.id.list_context_menu_show_on_map:
|
||||||
intent = IntentUtils.newShowOnMapIntent((double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + "");
|
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);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_edit:
|
case R.id.list_context_menu_edit:
|
||||||
@@ -335,12 +302,12 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
}
|
}
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
|
||||||
// Close the search result since its content can change after edit.
|
// Close the searchable result since its content can change after edit.
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_delete:
|
case R.id.list_context_menu_delete:
|
||||||
if (markerId != null) {
|
if (markerId != null) {
|
||||||
DeleteMarkerDialogFragment.newInstance(new long[]{markerId}).show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
|
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), new long[]{markerId});
|
||||||
} else {
|
} else {
|
||||||
deleteTracks(new long[]{trackId});
|
deleteTracks(new long[]{trackId});
|
||||||
}
|
}
|
||||||
@@ -369,7 +336,7 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do the search.
|
* Do the searchable.
|
||||||
*
|
*
|
||||||
* @param textQuery the query
|
* @param textQuery the query
|
||||||
*/
|
*/
|
||||||
@@ -386,9 +353,6 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
arrayAdapter.addAll(displayResults);
|
arrayAdapter.addAll(displayResults);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save the query as a suggestion for the future
|
|
||||||
searchRecentSuggestions.saveRecentQuery(query.textQuery, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -401,13 +365,11 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
//TODO Replace use of map<string, object>, but rather provide Track or Waypoint directly.
|
//TODO Replace use of map<string, object>, but rather provide Track or Waypoint directly.
|
||||||
ArrayList<Map<String, Object>> output = new ArrayList<>(scoredResults.size());
|
ArrayList<Map<String, Object>> output = new ArrayList<>(scoredResults.size());
|
||||||
for (ScoredResult result : scoredResults) {
|
for (ScoredResult result : scoredResults) {
|
||||||
Map<String, Object> resultMap = new HashMap<>();
|
|
||||||
if (result.track != null) {
|
if (result.track != null) {
|
||||||
prepareTrackForDisplay(result.track, resultMap);
|
output.add(prepareTrackForDisplay(result.track));
|
||||||
} else {
|
} else {
|
||||||
prepareMarkerForDisplay(result.waypoint, resultMap);
|
output.add(prepareMarkerForDisplay(result.waypoint));
|
||||||
}
|
}
|
||||||
output.add(resultMap);
|
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -415,15 +377,13 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
/**
|
/**
|
||||||
* Prepares a marker for display by filling in a result map.
|
* Prepares a marker for display by filling in a result map.
|
||||||
*
|
*
|
||||||
* @param waypoint the marker
|
* @param waypoint the marker
|
||||||
* @param resultMap the result map
|
* @return the result map
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
private Map<String, Object> prepareMarkerForDisplay(Waypoint waypoint) {
|
||||||
private void prepareMarkerForDisplay(Waypoint waypoint, Map<String, Object> resultMap) {
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
/*
|
|
||||||
* TODO: It may be more appropriate to obtain the track name as a join in
|
//TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the searchable.
|
||||||
* the retrieval phase of the search.
|
|
||||||
*/
|
|
||||||
String trackName = null;
|
String trackName = null;
|
||||||
long trackId = waypoint.getTrackId();
|
long trackId = waypoint.getTrackId();
|
||||||
if (trackId != -1L) {
|
if (trackId != -1L) {
|
||||||
@@ -453,17 +413,20 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
|
|
||||||
resultMap.put(MARKER_LATITUDE_FIELD, waypoint.getLocation().getLatitude());
|
resultMap.put(MARKER_LATITUDE_FIELD, waypoint.getLocation().getLatitude());
|
||||||
resultMap.put(MARKER_LONGITUDE_FIELD, waypoint.getLocation().getLongitude());
|
resultMap.put(MARKER_LONGITUDE_FIELD, waypoint.getLocation().getLongitude());
|
||||||
|
|
||||||
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares a track for display by filling in a result map.
|
* Prepares a track for display by filling in a result map.
|
||||||
*
|
*
|
||||||
* @param track the track
|
* @param track the track
|
||||||
* @param resultMap the result map
|
* @return the result map
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
private Map<String, Object> prepareTrackForDisplay(Track track) {
|
||||||
private void prepareTrackForDisplay(Track track, Map<String, Object> resultMap) {
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
TripStatistics tripStatitics = track.getTripStatistics();
|
|
||||||
|
TripStatistics tripStatistics = track.getTripStatistics();
|
||||||
String icon = track.getIcon();
|
String icon = track.getIcon();
|
||||||
String category = icon != null && !icon.equals("") ? null : track.getCategory();
|
String category = icon != null && !icon.equals("") ? null : track.getCategory();
|
||||||
|
|
||||||
@@ -472,15 +435,17 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
|||||||
resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon));
|
resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon));
|
||||||
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track);
|
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track);
|
||||||
resultMap.put(NAME_FIELD, track.getName());
|
resultMap.put(NAME_FIELD, track.getName());
|
||||||
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatitics.getTotalTime()));
|
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatistics.getTotalTime()));
|
||||||
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatitics.getTotalDistance(), metricUnits));
|
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatistics.getTotalDistance(), metricUnits));
|
||||||
resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId()));
|
resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId()));
|
||||||
resultMap.put(START_TIME_FIELD, tripStatitics.getStartTime());
|
resultMap.put(START_TIME_FIELD, tripStatistics.getStartTime());
|
||||||
resultMap.put(CATEGORY_FIELD, category);
|
resultMap.put(CATEGORY_FIELD, category);
|
||||||
resultMap.put(DESCRIPTION_FIELD, track.getDescription());
|
resultMap.put(DESCRIPTION_FIELD, track.getDescription());
|
||||||
resultMap.put(PHOTO_URL_FIELD, null);
|
resultMap.put(PHOTO_URL_FIELD, null);
|
||||||
resultMap.put(TRACK_ID_FIELD, track.getId());
|
resultMap.put(TRACK_ID_FIELD, track.getId());
|
||||||
resultMap.put(MARKER_ID_FIELD, null);
|
resultMap.put(MARKER_ID_FIELD, null);
|
||||||
|
|
||||||
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.StrictMode;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code that is executed when the application starts.
|
||||||
|
* <p>
|
||||||
|
* NOTE: How often actual application startup happens depends on the OS.
|
||||||
|
* Not every start of an activity will trigger this.
|
||||||
|
*/
|
||||||
|
public class Startup extends Application {
|
||||||
|
|
||||||
|
private final static String TAG = Startup.class.getSimpleName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
//Include version information into stack traces.
|
||||||
|
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
|
||||||
|
|
||||||
|
//Set default values of preferences on first start.
|
||||||
|
PreferencesUtils.resetPreferences(this, false);
|
||||||
|
|
||||||
|
|
||||||
|
//In debug builds: show thread and VM warnings.
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(TAG, "Enabling strict mode");
|
||||||
|
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||||
|
.detectAll()
|
||||||
|
.penaltyLog()
|
||||||
|
.build());
|
||||||
|
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||||
|
.detectAll()
|
||||||
|
.penaltyLog()
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import android.widget.TabHost;
|
|||||||
import android.widget.TabHost.TabContentFactory;
|
import android.widget.TabHost.TabContentFactory;
|
||||||
import android.widget.TabWidget;
|
import android.widget.TabWidget;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
@@ -79,6 +80,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@NonNull
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
TabInfo info = tabInfos.get(position);
|
TabInfo info = tabInfos.get(position);
|
||||||
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
|
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
|
||||||
@@ -97,9 +99,8 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
|||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position) {
|
public void onPageSelected(int position) {
|
||||||
/*
|
/*
|
||||||
* Unfortunately when TabHost changes the current tab, it kindly also takes
|
* Unfortunately when TabHost changes the current tab, it kindly also takes care of putting focus on it when not in touch mode.
|
||||||
* care of putting focus on it when not in touch mode. The jerk. This hack
|
* The jerk. This hack tries to prevent this from pulling focus out of our ViewPager.
|
||||||
* tries to prevent this from pulling focus out of our ViewPager.
|
|
||||||
*/
|
*/
|
||||||
TabWidget tabWidget = tabHost.getTabWidget();
|
TabWidget tabWidget = tabHost.getTabWidget();
|
||||||
int oldFocusability = tabWidget.getDescendantFocusability();
|
int oldFocusability = tabWidget.getDescendantFocusability();
|
||||||
@@ -122,15 +123,14 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
|||||||
private final Class<?> clss;
|
private final Class<?> clss;
|
||||||
private final Bundle bundle;
|
private final Bundle bundle;
|
||||||
|
|
||||||
public TabInfo(Class<?> clss, Bundle bunlde) {
|
TabInfo(Class<?> clss, Bundle bundle) {
|
||||||
this.clss = clss;
|
this.clss = clss;
|
||||||
this.bundle = bunlde;
|
this.bundle = bundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the
|
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the {@link TabHost} API.
|
||||||
* {@link TabHost} API.
|
|
||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
@@ -138,7 +138,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
|||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
public DummyTabFactory(Context context) {
|
DummyTabFactory(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import de.dennisguse.opentracks.services.ITrackRecordingService;
|
|||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track controller for record, pause, resume, and stop.
|
* Track controller for record, pause, resume, and stop.
|
||||||
@@ -67,7 +66,7 @@ public class TrackController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
|
TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
|
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
|
||||||
this.alwaysShow = alwaysShow;
|
this.alwaysShow = alwaysShow;
|
||||||
@@ -121,12 +120,12 @@ public class TrackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onResume(boolean recording, boolean paused) {
|
void onResume(boolean recording, boolean paused) {
|
||||||
isResumed = true;
|
isResumed = true;
|
||||||
update(recording, paused);
|
update(recording, paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPause() {
|
void onPause() {
|
||||||
isResumed = false;
|
isResumed = false;
|
||||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,34 +17,28 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Pair;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.ShareActionProvider;
|
import android.view.WindowManager;
|
||||||
import android.widget.TabHost;
|
import android.widget.TabHost;
|
||||||
import android.widget.TabHost.TabSpec;
|
import android.widget.TabHost.TabSpec;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.app.TaskStackBuilder;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
@@ -53,18 +47,15 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|||||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
|
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.FrequencyDialogFragment;
|
|
||||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StatsUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackUtils;
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,15 +64,16 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
|||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class TrackDetailActivity extends AbstractTrackActivity implements ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
public class TrackDetailActivity extends AbstractListActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||||
|
|
||||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
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 PHOTO_URI_KEY = "photo_uri_key";
|
||||||
private static final String HAS_PHOTO_KEY = "has_photo_key";
|
private static final String HAS_PHOTO_KEY = "has_photo_key";
|
||||||
private static final String JPEG_EXTENSION = "jpeg";
|
|
||||||
|
|
||||||
private static final int CAMERA_REQUEST_CODE = 5;
|
private static final int CAMERA_REQUEST_CODE = 5;
|
||||||
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
|
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
|
||||||
@@ -102,12 +94,12 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// After binding changes (is available), update the total time in
|
// After binding changes (is available), update the total time in trackController.
|
||||||
// trackController.
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -115,8 +107,9 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
if (hasPhoto && photoUri != null) {
|
if (hasPhoto && photoUri != null) {
|
||||||
hasPhoto = false;
|
hasPhoto = false;
|
||||||
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString());
|
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString());
|
||||||
long markerId = TrackRecordingServiceConnectionUtils.addMarker(TrackDetailActivity.this, trackRecordingServiceConnection, waypointCreationRequest);
|
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, waypointCreationRequest);
|
||||||
if (markerId != -1L) {
|
if (markerId != -1L) {
|
||||||
|
//TODO: Make configurable.
|
||||||
FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri);
|
FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,16 +121,19 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||||
* class. Anonymous inner class will get garbage collected.
|
* class. Anonymous inner class will get garbage collected.
|
||||||
*/
|
*/
|
||||||
private final OnSharedPreferenceChangeListener
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) {
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) {
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||||
|
setLockscreenPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
@@ -152,24 +148,24 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private MenuItem insertMarkerMenuItem;
|
private MenuItem insertMarkerMenuItem;
|
||||||
private MenuItem insertPhotoMenuItem;
|
private MenuItem insertPhotoMenuItem;
|
||||||
private MenuItem markerListMenuItem;
|
private MenuItem markerListMenuItem;
|
||||||
private MenuItem shareMenuItem;
|
private MenuItem shareMenuItem;
|
||||||
private MenuItem voiceFrequencyMenuItem;
|
|
||||||
private MenuItem splitFrequencyMenuItem;
|
|
||||||
private final OnClickListener recordListener = new OnClickListener() {
|
private final OnClickListener recordListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (recordingTrackPaused) {
|
if (recordingTrackPaused) {
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
updateMenuItems(false);
|
updateMenuItems(false);
|
||||||
TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection);
|
trackRecordingServiceConnection.resumeTrack();
|
||||||
trackController.update(true, false);
|
trackController.update(true, false);
|
||||||
} else {
|
} else {
|
||||||
// Recording -> Paused
|
// Recording -> Paused
|
||||||
updateMenuItems(true);
|
updateMenuItems(true);
|
||||||
TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection);
|
trackRecordingServiceConnection.pauseTrack();
|
||||||
trackController.update(true, true);
|
trackController.update(true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +174,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
private final OnClickListener stopListener = new OnClickListener() {
|
private final OnClickListener stopListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
TrackRecordingServiceConnectionUtils.stopRecording(TrackDetailActivity.this, trackRecordingServiceConnection, true);
|
trackRecordingServiceConnection.stopRecording(TrackDetailActivity.this, true);
|
||||||
updateMenuItems(true);
|
updateMenuItems(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -186,6 +182,8 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
||||||
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
||||||
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
||||||
@@ -198,16 +196,16 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||||
trackDataHub = TrackDataHub.newInstance(this);
|
trackDataHub = TrackDataHub.newInstance(this);
|
||||||
|
|
||||||
tabHost = findViewById(android.R.id.tabhost);
|
tabHost = findViewById(R.id.tackdetail_tabhost);
|
||||||
tabHost.setup();
|
tabHost.setup();
|
||||||
|
|
||||||
ViewPager viewPager = findViewById(R.id.pager);
|
ViewPager viewPager = findViewById(R.id.pager);
|
||||||
TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager);
|
TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager);
|
||||||
|
|
||||||
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab), getResources().getDrawable(R.drawable.ic_tab_stats));
|
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
|
||||||
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
|
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
|
||||||
|
|
||||||
TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab), getResources().getDrawable(R.drawable.ic_tab_chart));
|
TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab));
|
||||||
tabsAdapter.addTab(chartTabSpec, ChartFragment.class, null);
|
tabsAdapter.addTab(chartTabSpec, ChartFragment.class, null);
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
@@ -222,6 +220,24 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener);
|
trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToWindow() {
|
||||||
|
setLockscreenPolicy();
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLockscreenPolicy() {
|
||||||
|
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.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 {
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@@ -229,7 +245,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||||
|
|
||||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
trackDataHub.start();
|
trackDataHub.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +274,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
|
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
|
||||||
if (photoUri != null) {
|
if (photoUri != null) {
|
||||||
@@ -275,14 +291,6 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hasPhoto = resultCode == RESULT_OK;
|
hasPhoto = resultCode == RESULT_OK;
|
||||||
|
|
||||||
if (hasPhoto) {
|
|
||||||
//Register photo in media scanner
|
|
||||||
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
|
||||||
mediaScanIntent.setData(photoUri);
|
|
||||||
this.sendBroadcast(mediaScanIntent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
}
|
}
|
||||||
@@ -294,6 +302,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
public void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
}
|
}
|
||||||
@@ -305,14 +314,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||||
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
||||||
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
||||||
|
|
||||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||||
ShareActionProvider shareActionProvider = (ShareActionProvider) shareMenuItem.getActionProvider();
|
|
||||||
//TODO: Share the actual track when track is finished? How to get the file path or create a new file?
|
|
||||||
shareActionProvider.setShareIntent(IntentUtils.newShareFileIntent(this, trackId, "", TrackFileFormat.KML));
|
|
||||||
|
|
||||||
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);
|
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
@@ -334,26 +336,26 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
|
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_insert_photo:
|
case R.id.track_detail_insert_photo:
|
||||||
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
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);
|
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
takePicture();
|
createWaypointWithPicture();
|
||||||
|
return true;
|
||||||
|
case R.id.track_detail_menu_show_on_map:
|
||||||
|
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_markers:
|
case R.id.track_detail_markers:
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_voice_frequency:
|
|
||||||
FrequencyDialogFragment.newInstance(R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.string.menu_voice_frequency)
|
|
||||||
.show(getSupportFragmentManager(), FrequencyDialogFragment.FREQUENCY_DIALOG_TAG);
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_split_frequency:
|
|
||||||
FrequencyDialogFragment.newInstance(R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.string.menu_split_frequency)
|
|
||||||
.show(getSupportFragmentManager(), FrequencyDialogFragment.FREQUENCY_DIALOG_TAG);
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_edit:
|
case R.id.track_detail_edit:
|
||||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
@@ -371,30 +373,17 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void takePicture() {
|
|
||||||
File dir = FileUtils.getPhotoDir(trackId);
|
|
||||||
FileUtils.ensureDirectoryExists(dir);
|
|
||||||
|
|
||||||
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
|
|
||||||
File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, JPEG_EXTENSION));
|
|
||||||
|
|
||||||
photoUri = FileProvider.getUriForFile(this, FileUtils.FILEPROVIDER, file);
|
|
||||||
Log.d(TAG, "Taking photo to URI: " + photoUri);
|
|
||||||
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
|
||||||
.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
|
|
||||||
startActivityForResult(intent, CAMERA_REQUEST_CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
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();
|
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||||
} else {
|
} else {
|
||||||
takePicture();
|
createWaypointWithPicture();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,10 +412,16 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
/**
|
/**
|
||||||
* Gets the track id.
|
* Gets the track id.
|
||||||
*/
|
*/
|
||||||
|
// TODO Seems to be no used.
|
||||||
|
@Deprecated
|
||||||
public long getTrackId() {
|
public long getTrackId() {
|
||||||
return trackId;
|
return trackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setBluetoothHeartRateSensorName(String sensorName) {
|
||||||
|
StatsUtils.setBluetoothHeartRateSensorName(this, sensorName, isRecording());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the data in the intent.
|
* Handles the data in the intent.
|
||||||
*/
|
*/
|
||||||
@@ -468,8 +463,6 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
||||||
shareMenuItem.setVisible(!isRecording());
|
shareMenuItem.setVisible(!isRecording());
|
||||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
voiceFrequencyMenuItem.setVisible(isRecording());
|
|
||||||
splitFrequencyMenuItem.setVisible(isRecording());
|
|
||||||
String title;
|
String title;
|
||||||
if (isRecording()) {
|
if (isRecording()) {
|
||||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||||
@@ -480,8 +473,14 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
|||||||
setTitle(title);
|
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) {
|
public void chooseActivityType(String category) {
|
||||||
ChooseActivityTypeDialogFragment.newInstance(category).show(getSupportFragmentManager(), ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
|
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,28 +28,27 @@ import android.widget.Button;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackUtils;
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity that let's the user see and edit the user editable track meta
|
* An activity that let's the user see and edit the user editable track meta data such as track name, activity type, and track description.
|
||||||
* data such as track name, activity type, and track description.
|
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class TrackEditActivity extends AbstractActivity implements ChooseActivityTypeCaller {
|
public class TrackEditActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_NEW_TRACK = "new_track";
|
public static final String EXTRA_NEW_TRACK = "new_track";
|
||||||
|
|
||||||
private static final String TAG = TrackEditActivity.class.getSimpleName();
|
private static final String TAG = TrackEditActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final String ICON_VALUE_KEY = "icon_value_key";
|
private static final String ICON_VALUE_KEY = "icon_value_key";
|
||||||
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
@@ -57,7 +56,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
private Track track;
|
private Track track;
|
||||||
private String iconValue;
|
private String iconValue;
|
||||||
|
|
||||||
private EditText name;
|
private EditText nameEditText;
|
||||||
private AutoCompleteTextView activityType;
|
private AutoCompleteTextView activityType;
|
||||||
private Spinner activityTypeIcon;
|
private Spinner activityTypeIcon;
|
||||||
private EditText description;
|
private EditText description;
|
||||||
@@ -67,7 +66,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||||
Long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
if (trackId == -1L) {
|
if (trackId == -1L) {
|
||||||
Log.e(TAG, "invalid trackId");
|
Log.e(TAG, "invalid trackId");
|
||||||
finish();
|
finish();
|
||||||
@@ -82,14 +81,13 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = findViewById(R.id.track_edit_name);
|
nameEditText = findViewById(R.id.track_edit_name);
|
||||||
name.setText(track.getName());
|
nameEditText.setText(track.getName());
|
||||||
|
|
||||||
activityType = findViewById(R.id.track_edit_activity_type);
|
activityType = findViewById(R.id.track_edit_activity_type);
|
||||||
activityType.setText(track.getCategory());
|
activityType.setText(track.getCategory());
|
||||||
|
|
||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||||
this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
|
||||||
activityType.setAdapter(adapter);
|
activityType.setAdapter(adapter);
|
||||||
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -122,9 +120,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
@Override
|
@Override
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
|
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||||
getSupportFragmentManager(),
|
|
||||||
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -133,9 +129,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
@Override
|
@Override
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||||
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
|
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||||
getSupportFragmentManager(),
|
|
||||||
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -144,11 +138,11 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
description = findViewById(R.id.track_edit_description);
|
description = findViewById(R.id.track_edit_description);
|
||||||
description.setText(track.getDescription());
|
description.setText(track.getDescription());
|
||||||
|
|
||||||
Button save = findViewById(R.id.track_edit_save);
|
Button saveButton = findViewById(R.id.track_edit_save);
|
||||||
save.setOnClickListener(new View.OnClickListener() {
|
saveButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
TrackUtils.updateTrack(TrackEditActivity.this, track, name.getText().toString(),
|
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
|
||||||
activityType.getText().toString(), description.getText().toString(),
|
activityType.getText().toString(), description.getText().toString(),
|
||||||
contentProviderUtils);
|
contentProviderUtils);
|
||||||
finish();
|
finish();
|
||||||
@@ -175,7 +169,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -185,7 +179,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putString(ICON_VALUE_KEY, iconValue);
|
outState.putString(ICON_VALUE_KEY, iconValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,8 @@ import android.content.pm.PackageManager;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.StrictMode;
|
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -38,13 +35,12 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.SearchView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
@@ -60,12 +56,13 @@ import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
|||||||
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.ServiceUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackUtils;
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,12 +70,14 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
|||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class TrackListActivity extends AbstractTrackActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
||||||
|
|
||||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
|
private static final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
|
||||||
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
|
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
|
||||||
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
|
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
|
||||||
|
|
||||||
// The following are set in onCreate
|
// The following are set in onCreate
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
@@ -86,6 +85,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
private TrackController trackController;
|
private TrackController trackController;
|
||||||
private ListView listView;
|
private ListView listView;
|
||||||
private ResourceCursorAdapter resourceCursorAdapter;
|
private ResourceCursorAdapter resourceCursorAdapter;
|
||||||
|
|
||||||
private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
|
private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||||
@@ -103,20 +103,18 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
resourceCursorAdapter.swapCursor(null);
|
resourceCursorAdapter.swapCursor(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
|
|
||||||
// Callback when an item is selected in the contextual action mode
|
// Callback when an item is selected in the contextual action mode
|
||||||
private final ContextualActionModeCallback
|
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||||
contextualActionModeCallback = new ContextualActionModeCallback() {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
|
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
|
||||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
boolean isSingleSelection = trackIds.length == 1;
|
||||||
boolean isSingleSelection = ids.length == 1;
|
|
||||||
|
|
||||||
menu.findItem(R.id.list_context_menu_share).setVisible(!isRecording && isSingleSelection);
|
|
||||||
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(false);
|
|
||||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
||||||
}
|
}
|
||||||
@@ -126,31 +124,28 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
return handleContextItem(itemId, ids);
|
return handleContextItem(itemId, ids);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
|
||||||
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note that sharedPreferenceChangeListenr cannot be an anonymous inner class.
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
|
||||||
* Anonymous inner class will get garbage collected.
|
* Anonymous inner class will get garbage collected.
|
||||||
*/
|
*/
|
||||||
private final OnSharedPreferenceChangeListener
|
private final OnSharedPreferenceChangeListener
|
||||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
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);
|
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(
|
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) {
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
|
||||||
recordingTrackId = PreferencesUtils.getLong(
|
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||||
TrackListActivity.this, R.string.recording_track_id_key);
|
|
||||||
if (key != null && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
|
||||||
trackRecordingServiceConnection.startAndBind();
|
trackRecordingServiceConnection.startAndBind();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(
|
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
TrackListActivity.this, R.string.recording_track_paused_key))) {
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this,
|
|
||||||
R.string.recording_track_paused_key,
|
|
||||||
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -158,40 +153,42 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
public void run() {
|
public void run() {
|
||||||
TrackListActivity.this.invalidateOptionsMenu();
|
TrackListActivity.this.invalidateOptionsMenu();
|
||||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||||
trackController.update(isRecording, recordingTrackPaused);
|
trackController.update(isRecording, recordingTrackPaused);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Menu items
|
// Menu items
|
||||||
private MenuItem searchMenuItem;
|
private MenuItem searchMenuItem;
|
||||||
private MenuItem startGpsMenuItem;
|
private MenuItem startGpsMenuItem;
|
||||||
private MenuItem aggregatedStatisticsMenuItem;
|
|
||||||
private MenuItem deleteAllMenuItem;
|
private MenuItem deleteAllMenuItem;
|
||||||
|
|
||||||
private final OnClickListener stopListener = new OnClickListener() {
|
private final OnClickListener stopListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
updateMenuItems(false, false);
|
updateMenuItems(false, false);
|
||||||
TrackRecordingServiceConnectionUtils.stopRecording(
|
trackRecordingServiceConnection.stopRecording(TrackListActivity.this, true);
|
||||||
TrackListActivity.this, trackRecordingServiceConnection, true);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private boolean startGps = false; // true to start gps
|
private boolean startGps = false; // true to start gps
|
||||||
|
|
||||||
private boolean startNewRecording = false; // true to start a new recording
|
private boolean startNewRecording = false; // true to start a new recording
|
||||||
|
|
||||||
// Callback when the trackRecordingServiceConnection binding changes.
|
// Callback when the trackRecordingServiceConnection binding changes.
|
||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
/*
|
/*
|
||||||
* After binding changes (e.g., becomes available), update the total time
|
* After binding changes (e.g., becomes available), update the total time in trackController.
|
||||||
* in trackController.
|
|
||||||
*/
|
*/
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
trackController.update(recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, recordingTrackPaused);
|
trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -212,8 +209,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
Toast.makeText(
|
Toast.makeText(TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
|
||||||
TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
|
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
if (startGps) {
|
if (startGps) {
|
||||||
@@ -222,25 +218,24 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnClickListener recordListener = new OnClickListener() {
|
private final OnClickListener recordListener = new OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||||
// Not recording -> Recording
|
// Not recording -> Recording
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
startRecording();
|
startRecording();
|
||||||
} else {
|
} else if (recordingTrackPaused) {
|
||||||
if (recordingTrackPaused) {
|
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection);
|
trackRecordingServiceConnection.resumeTrack();
|
||||||
trackController.update(true, false);
|
trackController.update(true, false);
|
||||||
} else {
|
} else {
|
||||||
// Recording -> Paused
|
// Recording -> Paused
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection);
|
trackRecordingServiceConnection.pauseTrack();
|
||||||
trackController.update(true, true);
|
trackController.update(true, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -248,17 +243,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
Log.d(TAG, "Enabling strict mode");
|
|
||||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
|
||||||
.detectAll()
|
|
||||||
.penaltyLog()
|
|
||||||
.build());
|
|
||||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
|
||||||
.detectAll()
|
|
||||||
.penaltyLog()
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
@@ -267,14 +252,17 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener);
|
trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener);
|
||||||
|
|
||||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
|
||||||
// Show trackController when search dialog is dismissed
|
// Show trackController when search dialog is dismissed
|
||||||
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
||||||
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
|
if (searchManager != null) {
|
||||||
@Override
|
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
|
||||||
public void onDismiss() {
|
@Override
|
||||||
trackController.show();
|
public void onDismiss() {
|
||||||
}
|
trackController.show();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
listView = findViewById(R.id.track_list);
|
listView = findViewById(R.id.track_list);
|
||||||
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
||||||
@@ -286,6 +274,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
@@ -316,7 +305,8 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
listView.setAdapter(resourceCursorAdapter);
|
listView.setAdapter(resourceCursorAdapter);
|
||||||
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
|
||||||
|
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||||
|
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
|
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
|
||||||
showStartupDialogs();
|
showStartupDialogs();
|
||||||
@@ -328,7 +318,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -338,8 +328,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
// Update UI
|
// Update UI
|
||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
trackController.onResume(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
||||||
trackController.onResume(isRecording, recordingTrackPaused);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -379,10 +368,9 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
getMenuInflater().inflate(R.menu.track_list, menu);
|
getMenuInflater().inflate(R.menu.track_list, menu);
|
||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||||
AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, trackController);
|
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
||||||
|
|
||||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||||
aggregatedStatisticsMenuItem = menu.findItem(R.id.track_list_aggregated_statistics);
|
|
||||||
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
|
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
@@ -390,14 +378,13 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
boolean isGpsStarted = TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this);
|
boolean isGpsStarted = ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||||
updateMenuItems(isGpsStarted, isRecording);
|
updateMenuItems(isGpsStarted, isRecording);
|
||||||
|
|
||||||
View searchView = searchMenuItem.getActionView();
|
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
if (searchView instanceof SearchView) {
|
searchView.setQuery("", false);
|
||||||
((SearchView) searchView).setQuery("", false);
|
|
||||||
}
|
|
||||||
return super.onPrepareOptionsMenu(menu);
|
return super.onPrepareOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,11 +394,11 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.track_list_start_gps:
|
case R.id.track_list_start_gps:
|
||||||
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||||
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||||
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} else {
|
} else {
|
||||||
startGps = !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this);
|
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||||
|
|
||||||
// Show toast
|
// Show toast
|
||||||
Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT);
|
Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT);
|
||||||
@@ -438,10 +425,6 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_list_aggregated_statistics:
|
|
||||||
intent = IntentUtils.newIntent(this, AggregatedStatsActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
case R.id.track_list_delete_all:
|
case R.id.track_list_delete_all:
|
||||||
deleteTracks(new long[]{-1L});
|
deleteTracks(new long[]{-1L});
|
||||||
return true;
|
return true;
|
||||||
@@ -453,25 +436,6 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
|
||||||
getMenuInflater().inflate(R.menu.list_context_menu, menu);
|
|
||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
|
||||||
contextualActionModeCallback.onPrepare(
|
|
||||||
menu, new int[]{info.position}, new long[]{info.id}, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
|
||||||
if (handleContextItem(item.getItemId(), new long[]{info.id})) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onContextItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) {
|
if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) {
|
||||||
@@ -528,13 +492,9 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
startGpsMenuItem.setVisible(!isRecording);
|
startGpsMenuItem.setVisible(!isRecording);
|
||||||
if (!isRecording) {
|
if (!isRecording) {
|
||||||
startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps);
|
startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps);
|
||||||
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_menu_stop_gps : R.drawable.ic_menu_start_gps);
|
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
|
||||||
TrackIconUtils.setMenuIconColor(startGpsMenuItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (aggregatedStatisticsMenuItem != null) {
|
|
||||||
aggregatedStatisticsMenuItem.setVisible(hasTrack);
|
|
||||||
}
|
|
||||||
if (deleteAllMenuItem != null) {
|
if (deleteAllMenuItem != null) {
|
||||||
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
|
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
|
||||||
}
|
}
|
||||||
@@ -548,10 +508,9 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
trackRecordingServiceConnection.startAndBind();
|
trackRecordingServiceConnection.startAndBind();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the binding has happened, then invoke the callback to start a new
|
* If the binding has happened, then invoke the callback to start a new recording.
|
||||||
* recording. If the binding hasn't happened, then invoking the callback
|
* If the binding hasn't happened, then invoking the callback will have no effect.
|
||||||
* will have no effect. But when the binding occurs, the callback will get
|
* But when the binding occurs, the callback will get invoked.
|
||||||
* invoked.
|
|
||||||
*/
|
*/
|
||||||
bindChangedCallback.run();
|
bindChangedCallback.run();
|
||||||
}
|
}
|
||||||
@@ -564,13 +523,18 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
|||||||
* @return true if handled.
|
* @return true if handled.
|
||||||
*/
|
*/
|
||||||
private boolean handleContextItem(int itemId, long[] trackIds) {
|
private boolean handleContextItem(int itemId, long[] trackIds) {
|
||||||
|
Intent intent;
|
||||||
switch (itemId) {
|
switch (itemId) {
|
||||||
|
case R.id.list_context_menu_show_on_map:
|
||||||
|
IntentUtils.showTrackOnMap(this, trackIds);
|
||||||
|
return true;
|
||||||
case R.id.list_context_menu_share:
|
case R.id.list_context_menu_share:
|
||||||
//TODO
|
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||||
Log.e(TAG, "Not implemented");
|
intent = Intent.createChooser(intent, null);
|
||||||
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_edit:
|
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]);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package de.dennisguse.opentracks.android;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a {@link ContentResolver} and provides access as {@link IContentResolver}.
|
||||||
|
*/
|
||||||
|
public class ContentResolverWrapper implements IContentResolver {
|
||||||
|
|
||||||
|
private ContentResolver contentResolver;
|
||||||
|
|
||||||
|
public ContentResolverWrapper(ContentResolver contentResolver) {
|
||||||
|
this.contentResolver = contentResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Uri insert(@NonNull Uri url, @Nullable ContentValues values) {
|
||||||
|
return contentResolver.insert(url, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] values) {
|
||||||
|
return contentResolver.bulkInsert(url, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
|
||||||
|
return contentResolver.query(uri, projection, selection, selectionArgs, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String where, @Nullable String[] selectionArgs) {
|
||||||
|
return contentResolver.update(uri, values, where, selectionArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int delete(@NonNull Uri url, @Nullable String where, @Nullable String[] selectionArgs) {
|
||||||
|
return contentResolver.delete(url, where, selectionArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.dennisguse.opentracks.android;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresPermission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the interface actually shared by {@link android.content.ContentProvider} and {@link android.content.ContentResolver}.
|
||||||
|
* So, both can be used interchangeably.
|
||||||
|
*/
|
||||||
|
public interface IContentResolver {
|
||||||
|
@Nullable
|
||||||
|
Uri insert(@RequiresPermission.Write @NonNull Uri url, @Nullable ContentValues values);
|
||||||
|
|
||||||
|
int bulkInsert(@RequiresPermission.Write @NonNull Uri url, @NonNull ContentValues[] values);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Cursor query(@RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder);
|
||||||
|
|
||||||
|
int update(@RequiresPermission.Write @NonNull Uri uri, @Nullable ContentValues values, @Nullable String where, @Nullable String[] selectionArgs);
|
||||||
|
|
||||||
|
int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable String where, @Nullable String[] selectionArgs);
|
||||||
|
}
|
||||||
@@ -36,10 +36,20 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|||||||
public interface ContentProviderUtils {
|
public interface ContentProviderUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The authority (the first part of the URI) for the app's content
|
* Maximum number of waypoints that will be loaded at one time.
|
||||||
* provider.
|
|
||||||
*/
|
*/
|
||||||
String AUTHORITY = BuildConfig.APPLICATION_ID;
|
int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authority (the first part of the URI) for the app's content provider.
|
||||||
|
*/
|
||||||
|
String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base URI for the app's content provider.
|
||||||
|
*/
|
||||||
|
String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default {@link LocationFactory} which creates a location each time.
|
* The default {@link LocationFactory} which creates a location each time.
|
||||||
*/
|
*/
|
||||||
@@ -51,7 +61,8 @@ public interface ContentProviderUtils {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears a track. Removes waypoints and trackpoints. Only keeps the track id.
|
* Clears a track: removes waypoints and trackpoints.
|
||||||
|
* Only keeps the track id.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
@@ -77,20 +88,19 @@ public interface ContentProviderUtils {
|
|||||||
void deleteTrack(Context context, long trackId);
|
void deleteTrack(Context context, long trackId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all the tracks. If no track exists, an empty list is returned.
|
* Gets all the tracks.
|
||||||
* <p>
|
* If no track exists, an empty list is returned.
|
||||||
* Note that the returned tracks do not have any track points attached.
|
* NOTE: the returned tracks do not have any track points attached.
|
||||||
*/
|
*/
|
||||||
List<Track> getAllTracks();
|
List<Track> getAllTracks();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the last track. Returns null if doesn't exist.
|
* Gets the last track or null.
|
||||||
*/
|
*/
|
||||||
Track getLastTrack();
|
Track getLastTrack();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a track by a track id. Returns null if not found.
|
* Gets a track by a track id or null
|
||||||
* <p>
|
|
||||||
* Note that the returned track doesn't have any track points attached.
|
* Note that the returned track doesn't have any track points attached.
|
||||||
*
|
*
|
||||||
* @param trackId the track id.
|
* @param trackId the track id.
|
||||||
@@ -98,8 +108,8 @@ public interface ContentProviderUtils {
|
|||||||
Track getTrack(long trackId);
|
Track getTrack(long trackId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a track cursor. The caller owns the returned cursor and is responsible
|
* Gets a track cursor.
|
||||||
* for closing it.
|
* The caller owns the returned cursor and is responsible for closing it.
|
||||||
*
|
*
|
||||||
* @param selection the selection. Can be null
|
* @param selection the selection. Can be null
|
||||||
* @param selectionArgs the selection arguments. Can be null
|
* @param selectionArgs the selection arguments. Can be null
|
||||||
@@ -109,8 +119,7 @@ public interface ContentProviderUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a track.
|
* Inserts a track.
|
||||||
* <p>
|
* NOTE: This doesn't insert any track points.
|
||||||
* Note: This doesn't insert any track points.
|
|
||||||
*
|
*
|
||||||
* @param track the track
|
* @param track the track
|
||||||
* @return the content provider URI of the inserted track.
|
* @return the content provider URI of the inserted track.
|
||||||
@@ -119,8 +128,7 @@ public interface ContentProviderUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a track.
|
* Updates a track.
|
||||||
* <p>
|
* NOTE: This doesn't update any track points.
|
||||||
* Note: This doesn't update any track points.
|
|
||||||
*
|
*
|
||||||
* @param track the track
|
* @param track the track
|
||||||
*/
|
*/
|
||||||
@@ -134,19 +142,19 @@ public interface ContentProviderUtils {
|
|||||||
Waypoint createWaypoint(Cursor cursor);
|
Waypoint createWaypoint(Cursor cursor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a waypoint. If deleting a statistics waypoint, this will also
|
* Deletes a waypoint.
|
||||||
* correct the next statistics waypoint after the deleted one to reflect the
|
* If deleting a statistics waypoint, this will also correct the next statistics waypoint after the deleted one to reflect the deletion.
|
||||||
* deletion. The generator is used to update the next statistics waypoint.
|
* The generator is used to update the next statistics waypoint.
|
||||||
*
|
*
|
||||||
* @param waypointId the waypoint id
|
* @param waypointId the waypoint id
|
||||||
* @param descriptionGenerator the description generator. Can be null for
|
* @param descriptionGenerator the description generator. Can be null for waypoint marker
|
||||||
* waypoint marker
|
|
||||||
*/
|
*/
|
||||||
void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator);
|
void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the first waypoint id for a track. The first waypoint is special as it
|
* Gets the first waypoint id for a track.
|
||||||
* contains the stats for the track. Returns -1L if it doesn't exist.
|
* The first waypoint is special as it contains the stats for the track.
|
||||||
|
* Returns -1L if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
@@ -161,8 +169,8 @@ public interface ContentProviderUtils {
|
|||||||
Waypoint getLastWaypoint(long trackId, WaypointType waypointType);
|
Waypoint getLastWaypoint(long trackId, WaypointType waypointType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the next waypoint number for a type. Returns -1 if not able to get the
|
* Gets the next waypoint number for a type.
|
||||||
* next waypoint number.
|
* Returns -1 if not able to get the next waypoint number.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param waypointType the waypoint type
|
* @param waypointType the waypoint type
|
||||||
@@ -170,15 +178,16 @@ public interface ContentProviderUtils {
|
|||||||
int getNextWaypointNumber(long trackId, WaypointType waypointType);
|
int getNextWaypointNumber(long trackId, WaypointType waypointType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a waypoint from a waypoint id. Returns null if not found.
|
* Gets a waypoint from a waypoint id.
|
||||||
|
* Returns null if not found.
|
||||||
*
|
*
|
||||||
* @param waypointId the waypoint id
|
* @param waypointId the waypoint id
|
||||||
*/
|
*/
|
||||||
Waypoint getWaypoint(long waypointId);
|
Waypoint getWaypoint(long waypointId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a waypoint cursor. The caller owns the returned cursor and is
|
* Gets a waypoint cursor.
|
||||||
* responsible for closing it.
|
* he caller owns the returned cursor and is responsible for closing it.
|
||||||
*
|
*
|
||||||
* @param selection the selection. Can be null
|
* @param selection the selection. Can be null
|
||||||
* @param selectionArgs the selection arguments. Can be null
|
* @param selectionArgs the selection arguments. Can be null
|
||||||
@@ -189,13 +198,12 @@ public interface ContentProviderUtils {
|
|||||||
Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints);
|
Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a waypoint cursor for a track. The caller owns the returned cursor and
|
* Gets a waypoint cursor for a track.
|
||||||
* is responsible for closing it.
|
* The caller owns the returned cursor and is responsible for closing it.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param minWaypointId the minimum waypoint id. -1L to ignore
|
* @param minWaypointId the minimum waypoint id. -1L to ignore
|
||||||
* @param maxWaypoints the maximum number of waypoints to return. -1 for no
|
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
|
||||||
* limit
|
|
||||||
*/
|
*/
|
||||||
Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints);
|
Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints);
|
||||||
|
|
||||||
@@ -215,7 +223,8 @@ public interface ContentProviderUtils {
|
|||||||
Uri insertWaypoint(Waypoint waypoint);
|
Uri insertWaypoint(Waypoint waypoint);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a waypoint. Returns true if successful.
|
* Updates a waypoint.
|
||||||
|
* Returns true if successful.
|
||||||
*
|
*
|
||||||
* @param waypoint the waypoint
|
* @param waypoint the waypoint
|
||||||
*/
|
*/
|
||||||
@@ -240,14 +249,16 @@ public interface ContentProviderUtils {
|
|||||||
Location createTrackPoint(Cursor cursor);
|
Location createTrackPoint(Cursor cursor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the first location id for a track. Returns -1L if it doesn't exist.
|
* Gets the first location id for a track.
|
||||||
|
* Returns -1L if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
long getFirstTrackPointId(long trackId);
|
long getFirstTrackPointId(long trackId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the last location id for a track. Returns -1L if it doesn't exist.
|
* Gets the last location id for a track.
|
||||||
|
* Returns -1L if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
@@ -263,57 +274,44 @@ public interface ContentProviderUtils {
|
|||||||
long getTrackPointId(long trackId, Location location);
|
long getTrackPointId(long trackId, Location location);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the first valid location for a track. Returns null if it doesn't
|
* Gets the first valid location for a track.
|
||||||
* exist.
|
* Returns null if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
Location getFirstValidTrackPoint(long trackId);
|
Location getFirstValidTrackPoint(long trackId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the last valid location for a track. Returns null if it doesn't exist.
|
* Gets the last valid location for a track.
|
||||||
|
* Returns null if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
Location getLastValidTrackPoint(long trackId);
|
Location getLastValidTrackPoint(long trackId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the last valid location.
|
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
|
||||||
*/
|
|
||||||
Location getLastValidTrackPoint();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a location cursor. The caller owns the returned cursor and is
|
|
||||||
* responsible for closing it.
|
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
* @param startTrackPointId the starting track point id. -1L to ignore
|
||||||
* @param maxLocations maximum number of locations to return. -1 for no limit
|
* @param maxLocations maximum number of locations to return. -1 for no limit
|
||||||
* @param descending true to sort the result in descending order (latest
|
* @param descending true to sort the result in descending order (latest location first)
|
||||||
* location first)
|
|
||||||
*/
|
*/
|
||||||
Cursor getTrackPointCursor(
|
Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending);
|
||||||
long trackId, long startTrackPointId, int maxLocations, boolean descending);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new read-only iterator over a given track's points. It provides a
|
* Creates a new read-only iterator over a given track's points.
|
||||||
* lightweight way of iterating over long tracks without failing due to the
|
* It provides a lightweight way of iterating over long tracks without failing due to the underlying cursor limitations.
|
||||||
* underlying cursor limitations. Since it's a read-only iterator,
|
* Since it's a read-only iterator, {@link Iterator#remove()} always throws {@link UnsupportedOperationException}.
|
||||||
* {@link Iterator#remove()} always throws
|
* Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link LocationFactory#createLocation()} and populates it with information retrieved from the record.
|
||||||
* {@link UnsupportedOperationException}. Each call to
|
* When done with iteration, {@link LocationIterator#close()} must be called.
|
||||||
* {@link LocationIterator#next()} may advance to the next DB record, and if
|
|
||||||
* so, the iterator calls {@link LocationFactory#createLocation()} and
|
|
||||||
* populates it with information retrieved from the record. When done with
|
|
||||||
* iteration, {@link LocationIterator#close()} must be called.
|
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
* @param startTrackPointId the starting track point id. -1L to ignore
|
||||||
* @param descending true to sort the result in descending order (latest
|
* @param descending true to sort the result in descending order (latest location first)
|
||||||
* location first)
|
|
||||||
* @param locationFactory the location factory
|
* @param locationFactory the location factory
|
||||||
*/
|
*/
|
||||||
LocationIterator getTrackPointLocationIterator(
|
LocationIterator getTrackPointLocationIterator(long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory);
|
||||||
long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a track point.
|
* Inserts a track point.
|
||||||
@@ -325,10 +323,9 @@ public interface ContentProviderUtils {
|
|||||||
Uri insertTrackPoint(Location location, long trackId);
|
Uri insertTrackPoint(Location location, long trackId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lightweight wrapper around the original {@link Cursor} with a method to
|
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
|
||||||
* clean up.
|
|
||||||
*/
|
*/
|
||||||
interface LocationIterator extends Iterator<Location> {
|
interface LocationIterator extends Iterator<Location>, AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the most recently retrieved track point id by {@link #next()}.
|
* Gets the most recently retrieved track point id by {@link #next()}.
|
||||||
@@ -347,8 +344,8 @@ public interface ContentProviderUtils {
|
|||||||
interface LocationFactory {
|
interface LocationFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link Location}. An implementation can create new
|
* Creates a new {@link Location}.
|
||||||
* instances or reuse existing instances for optimization.
|
* An implementation can create new instances or reuse existing instances for optimization.
|
||||||
*/
|
*/
|
||||||
Location createLocation();
|
Location createLocation();
|
||||||
}
|
}
|
||||||
@@ -358,41 +355,12 @@ public interface ContentProviderUtils {
|
|||||||
*/
|
*/
|
||||||
class Factory {
|
class Factory {
|
||||||
|
|
||||||
private static Factory instance = new Factory();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link ContentProviderUtils}.
|
* Creates an instance of {@link ContentProviderUtils}.
|
||||||
*
|
*
|
||||||
* @param context the context
|
* @param context the context
|
||||||
*/
|
*/
|
||||||
public static ContentProviderUtils get(Context context) {
|
public static ContentProviderUtils get(Context context) {
|
||||||
return instance.newForContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the factory instance.
|
|
||||||
*/
|
|
||||||
public static Factory getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overrides the factory instance for testing. Don't forget to set it back
|
|
||||||
* to the original value after testing.
|
|
||||||
*
|
|
||||||
* @param factory the factory
|
|
||||||
*/
|
|
||||||
public static void overrideInstance(Factory factory) {
|
|
||||||
instance = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of {@link ContentProviderUtils}. Allows subclasses
|
|
||||||
* to override for testing.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
*/
|
|
||||||
protected ContentProviderUtils newForContext(Context context) {
|
|
||||||
return new ContentProviderUtilsImpl(context.getContentResolver());
|
return new ContentProviderUtilsImpl(context.getContentResolver());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,18 +24,21 @@ import android.location.Location;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.android.ContentResolverWrapper;
|
||||||
|
import de.dennisguse.opentracks.android.IContentResolver;
|
||||||
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ContentProviderUtils} implementation.
|
* {@link ContentProviderUtils} implementation.
|
||||||
|
* Allows to use {@link ContentResolver} and {@link android.content.ContentProvider} interchangeably via {@link IContentResolver}.
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
@@ -45,10 +48,14 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
|||||||
|
|
||||||
private static final int MAX_LATITUDE = 90000000;
|
private static final int MAX_LATITUDE = 90000000;
|
||||||
|
|
||||||
private final ContentResolver contentResolver;
|
private final IContentResolver contentResolver;
|
||||||
private int defaultCursorBatchSize = 2000;
|
private int defaultCursorBatchSize = 2000;
|
||||||
|
|
||||||
public ContentProviderUtilsImpl(ContentResolver contentResolver) {
|
public ContentProviderUtilsImpl(ContentResolver contentResolver) {
|
||||||
|
this.contentResolver = new ContentResolverWrapper(contentResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentProviderUtilsImpl(IContentResolver contentResolver) {
|
||||||
this.contentResolver = contentResolver;
|
this.contentResolver = contentResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,8 +723,7 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
|||||||
if (maxWaypoints >= 0) {
|
if (maxWaypoints >= 0) {
|
||||||
sortOrder += " LIMIT " + maxWaypoints;
|
sortOrder += " LIMIT " + maxWaypoints;
|
||||||
}
|
}
|
||||||
return contentResolver.query(
|
return contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
||||||
WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -837,16 +843,7 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Location getLastValidTrackPoint() {
|
public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) {
|
||||||
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from "
|
|
||||||
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.LATITUDE + "<="
|
|
||||||
+ MAX_LATITUDE + ")";
|
|
||||||
return findTrackPointBy(selection, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor getTrackPointCursor(
|
|
||||||
long trackId, long startTrackPointId, int maxLocations, boolean descending) {
|
|
||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -963,8 +960,7 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri insertTrackPoint(Location location, long trackId) {
|
public Uri insertTrackPoint(Location location, long trackId) {
|
||||||
return contentResolver.insert(
|
return contentResolver.insert(TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId));
|
||||||
TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1074,10 +1070,8 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
|||||||
* @param selectionArgs the selection arguments
|
* @param selectionArgs the selection arguments
|
||||||
* @param sortOrder the sort order
|
* @param sortOrder the sort order
|
||||||
*/
|
*/
|
||||||
private Cursor getTrackPointCursor(
|
private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
return contentResolver.query(TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
||||||
return contentResolver.query(
|
|
||||||
TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utilities for serializing primitive types.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class ContentTypeIds {
|
|
||||||
|
|
||||||
public static final byte BOOLEAN_TYPE_ID = 0;
|
|
||||||
public static final byte LONG_TYPE_ID = 1;
|
|
||||||
public static final byte INT_TYPE_ID = 2;
|
|
||||||
public static final byte FLOAT_TYPE_ID = 3;
|
|
||||||
public static final byte DOUBLE_TYPE_ID = 4;
|
|
||||||
public static final byte STRING_TYPE_ID = 5;
|
|
||||||
public static final byte BLOB_TYPE_ID = 6;
|
|
||||||
private ContentTypeIds() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,6 @@ import android.content.ContentProvider;
|
|||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.UriMatcher;
|
import android.content.UriMatcher;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.SQLException;
|
import android.database.SQLException;
|
||||||
@@ -29,42 +28,39 @@ import android.database.sqlite.SQLiteException;
|
|||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.database.sqlite.SQLiteQueryBuilder;
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ContentProvider} that handles access to track points, tracks, and
|
* A {@link ContentProvider} that handles access to track points, tracks, and waypoints tables.
|
||||||
* waypoints tables.
|
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class CustomContentProvider extends ContentProvider {
|
public abstract class CustomContentProvider extends ContentProvider {
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int DATABASE_VERSION = 23;
|
static final int DATABASE_VERSION = 23;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String DATABASE_NAME = "database.db";
|
static final String DATABASE_NAME = "database.db";
|
||||||
|
|
||||||
private static final String TAG = CustomContentProvider.class.getSimpleName();
|
private static final String TAG = CustomContentProvider.class.getSimpleName();
|
||||||
|
|
||||||
private final UriMatcher uriMatcher;
|
private final UriMatcher uriMatcher;
|
||||||
|
|
||||||
private SQLiteDatabase db;
|
private SQLiteDatabase db;
|
||||||
|
|
||||||
public CustomContentProvider() {
|
public CustomContentProvider() {
|
||||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_ID.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_ID.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_ID.ordinal());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -277,23 +273,6 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
|
||||||
try {
|
|
||||||
File file = new File(uri.getPath());
|
|
||||||
if (file.exists()) {
|
|
||||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
}
|
|
||||||
throw new FileNotFoundException(uri.getPath());
|
|
||||||
} finally {
|
|
||||||
File dir = new File(getContext().getCacheDir(), FileUtils.PLAY_TRACKS_DIR);
|
|
||||||
for (File file : dir.listFiles()) {
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
getContext().revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link UrlType} for a url.
|
* Gets the {@link UrlType} for a url.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.Constants;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,12 +30,12 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
|||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class DataSource {
|
class DataSource {
|
||||||
|
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
private final SharedPreferences sharedPreferences;
|
private final SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
public DataSource(Context context) {
|
DataSource(Context context) {
|
||||||
contentResolver = context.getContentResolver();
|
contentResolver = context.getContentResolver();
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
}
|
}
|
||||||
@@ -47,7 +46,7 @@ public class DataSource {
|
|||||||
* @param uri the uri
|
* @param uri the uri
|
||||||
* @param observer the observer
|
* @param observer the observer
|
||||||
*/
|
*/
|
||||||
public void registerContentObserver(Uri uri, ContentObserver observer) {
|
void registerContentObserver(Uri uri, ContentObserver observer) {
|
||||||
contentResolver.registerContentObserver(uri, false, observer);
|
contentResolver.registerContentObserver(uri, false, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ public class DataSource {
|
|||||||
*
|
*
|
||||||
* @param observer the observer
|
* @param observer the observer
|
||||||
*/
|
*/
|
||||||
public void unregisterContentObserver(ContentObserver observer) {
|
void unregisterContentObserver(ContentObserver observer) {
|
||||||
contentResolver.unregisterContentObserver(observer);
|
contentResolver.unregisterContentObserver(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ public class DataSource {
|
|||||||
*
|
*
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
*/
|
*/
|
||||||
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +73,7 @@ public class DataSource {
|
|||||||
*
|
*
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
*/
|
*/
|
||||||
public void unregisterOnSharedPreferenceChangeListener(
|
void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||||
OnSharedPreferenceChangeListener listener) {
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,17 +26,19 @@ import java.util.EnumSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data source manager. Creates observers/listeners and manages their
|
* Creates observers/listeners and manages their registration with {@link DataSource}.
|
||||||
* registration with {@link DataSource}. The observers/listeners calls
|
* The observers/listeners calls {@link DataSourceListener} when data changes.
|
||||||
* {@link DataSourceListener} when data changes.
|
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class DataSourceManager {
|
class DataSourceManager {
|
||||||
|
|
||||||
private static final String TAG = DataSourceManager.class.getSimpleName();
|
private static final String TAG = DataSourceManager.class.getSimpleName();
|
||||||
|
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
|
|
||||||
private final DataSourceListener dataSourceListener;
|
private final DataSourceListener dataSourceListener;
|
||||||
|
|
||||||
// Registered listeners
|
// Registered listeners
|
||||||
private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
|
private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
@@ -44,7 +46,8 @@ public class DataSourceManager {
|
|||||||
private final WaypointsTableObserver waypointsTableObserver;
|
private final WaypointsTableObserver waypointsTableObserver;
|
||||||
private final TrackPointsTableObserver trackPointsTableObserver;
|
private final TrackPointsTableObserver trackPointsTableObserver;
|
||||||
private final PreferenceListener preferenceListener;
|
private final PreferenceListener preferenceListener;
|
||||||
public DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
|
|
||||||
|
DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.dataSourceListener = dataSourceListener;
|
this.dataSourceListener = dataSourceListener;
|
||||||
|
|
||||||
@@ -60,7 +63,7 @@ public class DataSourceManager {
|
|||||||
*
|
*
|
||||||
* @param listeners the listeners
|
* @param listeners the listeners
|
||||||
*/
|
*/
|
||||||
public void updateListeners(EnumSet<TrackDataType> listeners) {
|
void updateListeners(EnumSet<TrackDataType> listeners) {
|
||||||
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
|
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -155,7 +158,7 @@ public class DataSourceManager {
|
|||||||
/**
|
/**
|
||||||
* Unregisters all listeners with data source.
|
* Unregisters all listeners with data source.
|
||||||
*/
|
*/
|
||||||
public void unregisterAllListeners() {
|
void unregisterAllListeners() {
|
||||||
for (TrackDataType trackDataType : TrackDataType.values()) {
|
for (TrackDataType trackDataType : TrackDataType.values()) {
|
||||||
unregisterListener(trackDataType);
|
unregisterListener(trackDataType);
|
||||||
}
|
}
|
||||||
@@ -168,7 +171,7 @@ public class DataSourceManager {
|
|||||||
*/
|
*/
|
||||||
private class TracksTableObserver extends ContentObserver {
|
private class TracksTableObserver extends ContentObserver {
|
||||||
|
|
||||||
public TracksTableObserver() {
|
TracksTableObserver() {
|
||||||
super(handler);
|
super(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +188,7 @@ public class DataSourceManager {
|
|||||||
*/
|
*/
|
||||||
private class WaypointsTableObserver extends ContentObserver {
|
private class WaypointsTableObserver extends ContentObserver {
|
||||||
|
|
||||||
public WaypointsTableObserver() {
|
WaypointsTableObserver() {
|
||||||
super(handler);
|
super(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +205,7 @@ public class DataSourceManager {
|
|||||||
*/
|
*/
|
||||||
private class TrackPointsTableObserver extends ContentObserver {
|
private class TrackPointsTableObserver extends ContentObserver {
|
||||||
|
|
||||||
public TrackPointsTableObserver() {
|
TrackPointsTableObserver() {
|
||||||
super(handler);
|
super(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,8 @@ package de.dennisguse.opentracks.content;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for an object that can generate descriptions of track and
|
* An interface for an object that can generate descriptions of track and waypoint.
|
||||||
* waypoint.
|
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
@@ -31,12 +28,9 @@ public interface DescriptionGenerator {
|
|||||||
* Generates a track description.
|
* Generates a track description.
|
||||||
*
|
*
|
||||||
* @param track the track
|
* @param track the track
|
||||||
* @param distances a vector of distances to generate the elevation chart
|
|
||||||
* @param elevations a vector of elevations to generate the elevation chart
|
|
||||||
* @param html true to output html, false to output plain text
|
* @param html true to output html, false to output plain text
|
||||||
*/
|
*/
|
||||||
String generateTrackDescription(
|
String generateTrackDescription(Track track, boolean html);
|
||||||
Track track, Vector<Double> distances, Vector<Double> elevations, boolean html);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a waypoint description from a trip statistics.
|
* Generate a waypoint description from a trip statistics.
|
||||||
|
|||||||
@@ -17,11 +17,10 @@
|
|||||||
package de.dennisguse.opentracks.content;
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
@@ -44,7 +43,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateTrackDescription(Track track, Vector<Double> distances, Vector<Double> elevations, boolean html) {
|
public String generateTrackDescription(Track track, boolean html) {
|
||||||
String paragraphSeparator = html ? HTML_PARAGRAPH_SEPARATOR : TEXT_PARAGRAPH_SEPARATOR;
|
String paragraphSeparator = html ? HTML_PARAGRAPH_SEPARATOR : TEXT_PARAGRAPH_SEPARATOR;
|
||||||
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
|
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
@@ -97,8 +96,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
// Total distance
|
// Total distance
|
||||||
writeDistance(
|
writeDistance(stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
|
||||||
stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
|
|
||||||
|
|
||||||
// Total time
|
// Total time
|
||||||
writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak);
|
writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak);
|
||||||
@@ -110,19 +108,16 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak);
|
writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak);
|
||||||
|
|
||||||
// Average moving speed
|
// Average moving speed
|
||||||
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed,
|
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, lineBreak);
|
||||||
lineBreak);
|
|
||||||
|
|
||||||
// Max speed
|
// Max speed
|
||||||
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
|
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
|
||||||
|
|
||||||
// Average pace
|
// Average pace
|
||||||
writePace(
|
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
|
||||||
stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
|
|
||||||
|
|
||||||
// Average moving pace
|
// Average moving pace
|
||||||
writePace(stats.getAverageMovingSpeed(), builder,
|
writePace(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_pace_in_minute, lineBreak);
|
||||||
R.string.description_average_moving_pace_in_minute, lineBreak);
|
|
||||||
|
|
||||||
// Fastest pace
|
// Fastest pace
|
||||||
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
|
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
|
||||||
@@ -134,8 +129,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
|
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
|
||||||
|
|
||||||
// Elevation gain
|
// Elevation gain
|
||||||
writeElevation(
|
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||||
stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
|
||||||
|
|
||||||
// Max grade
|
// Max grade
|
||||||
writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak);
|
writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak);
|
||||||
@@ -144,9 +138,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak);
|
writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak);
|
||||||
|
|
||||||
// Recorded time
|
// Recorded time
|
||||||
builder.append(
|
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
|
||||||
context.getString(R.string.description_recorded_time,
|
|
||||||
StringUtils.formatDateTime(context, stats.getStartTime())));
|
|
||||||
builder.append(lineBreak);
|
builder.append(lineBreak);
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
@@ -191,8 +183,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
* @param lineBreak line break string
|
* @param lineBreak line break string
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void writeSpeed(
|
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||||
double speed, StringBuilder builder, int resId, String lineBreak) {
|
|
||||||
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
|
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
|
||||||
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
|
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
|
||||||
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
|
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
|
||||||
@@ -209,18 +200,13 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
|
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||||
String[] paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
|
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
|
||||||
String[] paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
|
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
|
||||||
|
|
||||||
if (paceInMetrics[0] == null) {
|
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
|
||||||
paceInMetrics[0] = context.getString(R.string.value_unknown);
|
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
|
||||||
}
|
|
||||||
|
|
||||||
if (paceInImperial[0] == null) {
|
builder.append(context.getString(resId, formattedPaceMetrics, formattedPaceImperial));
|
||||||
paceInImperial[0] = context.getString(R.string.value_unknown);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append(context.getString(resId, paceInMetrics[0], paceInImperial[0]));
|
|
||||||
builder.append(lineBreak);
|
builder.append(lineBreak);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +219,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
* @param lineBreak line break string
|
* @param lineBreak line break string
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void writeElevation(
|
void writeElevation(double elevation, StringBuilder builder, int resId, String lineBreak) {
|
||||||
double elevation, StringBuilder builder, int resId, String lineBreak) {
|
|
||||||
long elevationInM = Math.round(elevation);
|
long elevationInM = Math.round(elevation);
|
||||||
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
|
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
|
||||||
builder.append(context.getString(resId, elevationInM, elevationInFt));
|
builder.append(context.getString(resId, elevationInM, elevationInFt));
|
||||||
@@ -251,8 +236,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) {
|
void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) {
|
||||||
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L
|
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L : Math.round(grade * 100);
|
||||||
: Math.round(grade * 100);
|
|
||||||
builder.append(context.getString(resId, gradeInPercent));
|
builder.append(context.getString(resId, gradeInPercent));
|
||||||
builder.append(lineBreak);
|
builder.append(lineBreak);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,17 +20,18 @@ import android.location.Location;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engine for searching for tracks and waypoints by text.
|
* Engine for searching for tracks and waypoints by text.
|
||||||
*
|
*
|
||||||
@@ -97,34 +98,35 @@ public class SearchEngine {
|
|||||||
/**
|
/**
|
||||||
* Oldest timestamp for which we rank based on time (2000-01-01 00:00:00.000)
|
* Oldest timestamp for which we rank based on time (2000-01-01 00:00:00.000)
|
||||||
*/
|
*/
|
||||||
private static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L;
|
public static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comparador for scored results.
|
* Comparador for scored results.
|
||||||
*/
|
*/
|
||||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR =
|
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = new Comparator<ScoredResult>() {
|
||||||
new Comparator<ScoredResult>() {
|
@Override
|
||||||
@Override
|
public int compare(ScoredResult r1, ScoredResult r2) {
|
||||||
public int compare(ScoredResult r1, ScoredResult r2) {
|
// Score ordering.
|
||||||
// Score ordering.
|
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
if (scoreDiff != 0) {
|
||||||
if (scoreDiff != 0) {
|
return scoreDiff;
|
||||||
return scoreDiff;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Make tracks come before waypoints.
|
// Make tracks come before waypoints.
|
||||||
if (r1.waypoint != null && r2.track != null) {
|
if (r1.waypoint != null && r2.track != null) {
|
||||||
return 1;
|
return 1;
|
||||||
} else if (r1.track != null && r2.waypoint != null) {
|
} else if (r1.track != null && r2.waypoint != null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, use arbitrary ordering, by ID.
|
||||||
|
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
|
||||||
|
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
|
||||||
|
long idDiff = id2 - id1;
|
||||||
|
return Long.signum(idDiff);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Finally, use arbitrary ordering, by ID.
|
|
||||||
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
|
|
||||||
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
|
|
||||||
long idDiff = id2 - id1;
|
|
||||||
return Long.signum(idDiff);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private final ContentProviderUtils providerUtils;
|
private final ContentProviderUtils providerUtils;
|
||||||
|
|
||||||
public SearchEngine(ContentProviderUtils providerUtils) {
|
public SearchEngine(ContentProviderUtils providerUtils) {
|
||||||
@@ -134,26 +136,22 @@ public class SearchEngine {
|
|||||||
/**
|
/**
|
||||||
* Squashes a number by calculating 1 / log (1 + x).
|
* Squashes a number by calculating 1 / log (1 + x).
|
||||||
*/
|
*/
|
||||||
|
//TODO Why is this done?
|
||||||
private static double squash(double x) {
|
private static double squash(double x) {
|
||||||
return 1.0 / Math.log1p(x);
|
return 1.0 / Math.log1p(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a search query and returns a set of sorted results.
|
* Executes a searchable query and returns a set of sorted results.
|
||||||
*
|
*
|
||||||
* @param query the query to execute
|
* @param query the query to execute
|
||||||
* @return a set of results, sorted according to their score
|
* @return a set of results, sorted according to their score
|
||||||
*/
|
*/
|
||||||
public SortedSet<ScoredResult> search(SearchQuery query) {
|
public SortedSet<ScoredResult> search(SearchQuery query) {
|
||||||
ArrayList<Track> tracks = new ArrayList<>();
|
|
||||||
ArrayList<Waypoint> waypoints = new ArrayList<>();
|
|
||||||
TreeSet<ScoredResult> scoredResults = new TreeSet<>(SCORED_RESULT_COMPARATOR);
|
TreeSet<ScoredResult> scoredResults = new TreeSet<>(SCORED_RESULT_COMPARATOR);
|
||||||
|
|
||||||
retrieveTracks(query, tracks);
|
scoreTrackResults(retrieveTracks(query), query, scoredResults);
|
||||||
retrieveWaypoints(query, waypoints);
|
scoreWaypointResults(retrieveWaypoints(query), query, scoredResults);
|
||||||
|
|
||||||
scoreTrackResults(tracks, query, scoredResults);
|
|
||||||
scoreWaypointResults(waypoints, query, scoredResults);
|
|
||||||
|
|
||||||
return scoredResults;
|
return scoredResults;
|
||||||
}
|
}
|
||||||
@@ -161,18 +159,19 @@ public class SearchEngine {
|
|||||||
/**
|
/**
|
||||||
* Retrieves tracks matching the given query from the database.
|
* Retrieves tracks matching the given query from the database.
|
||||||
*
|
*
|
||||||
* @param query the query to retrieve for
|
* @param query the query to retrieve for
|
||||||
* @param tracks list to fill with the resulting tracks
|
|
||||||
*/
|
*/
|
||||||
private void retrieveTracks(SearchQuery query, ArrayList<Track> tracks) {
|
private List<Track> retrieveTracks(SearchQuery query) {
|
||||||
|
ArrayList<Track> tracks = new ArrayList<>();
|
||||||
|
|
||||||
String queryLikeSelection = "%" + query.textQuery + "%";
|
String queryLikeSelection = "%" + query.textQuery + "%";
|
||||||
String[] trackSelectionArgs = new String[]{
|
String[] trackSelectionArgs = new String[]{
|
||||||
queryLikeSelection,
|
queryLikeSelection,
|
||||||
queryLikeSelection,
|
queryLikeSelection,
|
||||||
queryLikeSelection};
|
queryLikeSelection
|
||||||
|
};
|
||||||
|
|
||||||
try (Cursor cursor = providerUtils.getTrackCursor(
|
try (Cursor cursor = providerUtils.getTrackCursor(TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) {
|
||||||
TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) {
|
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
tracks.ensureCapacity(cursor.getCount());
|
tracks.ensureCapacity(cursor.getCount());
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
@@ -180,22 +179,26 @@ public class SearchEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves waypoints matching the given query from the database.
|
* Retrieves waypoints matching the given query from the database.
|
||||||
*
|
*
|
||||||
* @param query the query to retrieve for
|
* @param query the query to retrieve for
|
||||||
* @param waypoints list to fill with the resulting waypoints
|
|
||||||
*/
|
*/
|
||||||
private void retrieveWaypoints(SearchQuery query, ArrayList<Waypoint> waypoints) {
|
private List<Waypoint> retrieveWaypoints(SearchQuery query) {
|
||||||
|
ArrayList<Waypoint> waypoints = new ArrayList<>();
|
||||||
|
|
||||||
String queryLikeSelection2 = "%" + query.textQuery + "%";
|
String queryLikeSelection2 = "%" + query.textQuery + "%";
|
||||||
String[] waypointSelectionArgs = new String[]{
|
String[] waypointSelectionArgs = new String[]{
|
||||||
queryLikeSelection2,
|
queryLikeSelection2,
|
||||||
queryLikeSelection2,
|
queryLikeSelection2,
|
||||||
queryLikeSelection2};
|
queryLikeSelection2
|
||||||
try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs,
|
};
|
||||||
WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) {
|
|
||||||
|
try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs, WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
waypoints.ensureCapacity(cursor.getCount());
|
waypoints.ensureCapacity(cursor.getCount());
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
@@ -206,6 +209,8 @@ public class SearchEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return waypoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,7 +243,6 @@ public class SearchEngine {
|
|||||||
score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory());
|
score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory());
|
||||||
|
|
||||||
TripStatistics statistics = track.getTripStatistics();
|
TripStatistics statistics = track.getTripStatistics();
|
||||||
// TODO: Also boost for proximity to the currently-centered position on the map.
|
|
||||||
score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude());
|
score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude());
|
||||||
|
|
||||||
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
|
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
|
||||||
@@ -281,7 +285,6 @@ public class SearchEngine {
|
|||||||
|
|
||||||
Location location = waypoint.getLocation();
|
Location location = waypoint.getLocation();
|
||||||
score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory());
|
score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory());
|
||||||
// TODO: Also boost for proximity to the currently-centered position on the map.
|
|
||||||
score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude());
|
score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude());
|
||||||
score *= getTimeBoost(query, location.getTime());
|
score *= getTimeBoost(query, location.getTime());
|
||||||
|
|
||||||
@@ -302,8 +305,7 @@ public class SearchEngine {
|
|||||||
* @param category the category of the track or waypoint
|
* @param category the category of the track or waypoint
|
||||||
* @return the total boost to be applied to the result
|
* @return the total boost to be applied to the result
|
||||||
*/
|
*/
|
||||||
private double getTitleBoost(SearchQuery query,
|
private double getTitleBoost(SearchQuery query, String name, String description, String category) {
|
||||||
String name, String description, String category) {
|
|
||||||
// Title boost: track name > description > category.
|
// Title boost: track name > description > category.
|
||||||
double boost = 1.0;
|
double boost = 1.0;
|
||||||
if (name.toLowerCase(Locale.getDefault()).contains(query.textQuery)) {
|
if (name.toLowerCase(Locale.getDefault()).contains(query.textQuery)) {
|
||||||
@@ -332,8 +334,7 @@ public class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Score recent tracks higher.
|
// Score recent tracks higher.
|
||||||
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S
|
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
|
||||||
* UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
|
|
||||||
if (timeAgoHours > 0L) {
|
if (timeAgoHours > 0L) {
|
||||||
return squash(timeAgoHours);
|
return squash(timeAgoHours);
|
||||||
} else {
|
} else {
|
||||||
@@ -364,6 +365,7 @@ public class SearchEngine {
|
|||||||
|
|
||||||
// Score tracks close to the current location higher.
|
// Score tracks close to the current location higher.
|
||||||
double distanceKm = distanceResults[0] * UnitConversions.M_TO_KM;
|
double distanceKm = distanceResults[0] * UnitConversions.M_TO_KM;
|
||||||
|
|
||||||
if (distanceKm > 0.0) {
|
if (distanceKm > 0.0) {
|
||||||
// Use the inverse of the amortized distance.
|
// Use the inverse of the amortized distance.
|
||||||
return squash(distanceKm);
|
return squash(distanceKm);
|
||||||
@@ -374,15 +376,15 @@ public class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description of a search query, along with all contextual data needed to execute it.
|
* Description of a searchable query, along with all contextual data needed to execute it.
|
||||||
*/
|
*/
|
||||||
public static class SearchQuery {
|
public static class SearchQuery {
|
||||||
public final String textQuery;
|
final String textQuery;
|
||||||
public final Location currentLocation;
|
final Location currentLocation;
|
||||||
public final long currentTrackId;
|
final long currentTrackId;
|
||||||
public final long currentTimestamp;
|
final long currentTimestamp;
|
||||||
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId,
|
|
||||||
long currentTimestamp) {
|
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, long currentTimestamp) {
|
||||||
this.textQuery = textQuery.toLowerCase(Locale.getDefault());
|
this.textQuery = textQuery.toLowerCase(Locale.getDefault());
|
||||||
this.currentLocation = currentLocation;
|
this.currentLocation = currentLocation;
|
||||||
this.currentTrackId = currentTrackId;
|
this.currentTrackId = currentTrackId;
|
||||||
@@ -391,17 +393,19 @@ public class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description of a search result which has been retrieved and scored.
|
* Description of a searchable result which has been retrieved and scored.
|
||||||
*/
|
*/
|
||||||
public static class ScoredResult {
|
public static class ScoredResult {
|
||||||
public final Track track;
|
public final Track track;
|
||||||
public final Waypoint waypoint;
|
public final Waypoint waypoint;
|
||||||
public final double score;
|
final double score;
|
||||||
|
|
||||||
ScoredResult(Track track, double score) {
|
ScoredResult(Track track, double score) {
|
||||||
this.track = track;
|
this.track = track;
|
||||||
this.waypoint = null;
|
this.waypoint = null;
|
||||||
this.score = score;
|
this.score = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScoredResult(Waypoint waypoint, double score) {
|
ScoredResult(Waypoint waypoint, double score) {
|
||||||
this.track = null;
|
this.track = null;
|
||||||
this.waypoint = waypoint;
|
this.waypoint = waypoint;
|
||||||
|
|||||||
@@ -1,46 +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.content.SearchRecentSuggestionsProvider;
|
|
||||||
import android.provider.SearchRecentSuggestions;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.BuildConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Content provider for search suggestions.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class SearchEngineProvider extends SearchRecentSuggestionsProvider {
|
|
||||||
|
|
||||||
private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".search";
|
|
||||||
private static final int MODE = DATABASE_MODE_QUERIES;
|
|
||||||
|
|
||||||
public SearchEngineProvider() {
|
|
||||||
setupSuggestions(AUTHORITY, MODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Also add suggestions from the database.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and returns a helper for adding recent queries or clearing the recent query history.
|
|
||||||
*/
|
|
||||||
public static SearchRecentSuggestions newHelper(Context context) {
|
|
||||||
return new SearchRecentSuggestions(context, AUTHORITY, MODE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,11 +28,6 @@ public class SensorDataSetLocation extends Location {
|
|||||||
|
|
||||||
private SensorDataSet sensorDataSet = null;
|
private SensorDataSet sensorDataSet = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of this location from the provider.
|
|
||||||
*/
|
|
||||||
private int id = -1;
|
|
||||||
|
|
||||||
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
|
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
|
||||||
super(location);
|
super(location);
|
||||||
this.sensorDataSet = sensorDataSet;
|
this.sensorDataSet = sensorDataSet;
|
||||||
@@ -50,17 +45,8 @@ public class SensorDataSetLocation extends Location {
|
|||||||
this.sensorDataSet = sensorDataSet;
|
this.sensorDataSet = sensorDataSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
sensorDataSet = null;
|
sensorDataSet = null;
|
||||||
id = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.content.pm.ProviderInfo;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.android.IContentResolver;
|
||||||
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
|
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files.
|
||||||
|
* The actual content of the virtual files is generated by using the functionality defined in {@link CustomContentProvider}.
|
||||||
|
*
|
||||||
|
* Moreover, it manages access to OpenTrack's database via {@link CustomContentProvider}.
|
||||||
|
*
|
||||||
|
* Explanation:
|
||||||
|
* Although a request is handled by a {@link android.content.ContentProvider} (with temporarily granted permission), Android's security infrastructure prevents forwarding queries to non-exported {@link android.content.ContentProvider}.
|
||||||
|
* Thus, if {@link ShareContentProvider} and {@link CustomContentProvider} would be two different instances, the data would not be accessible to external apps.
|
||||||
|
* While handling a request {@link ShareContentProvider} could `grantPermissions()` to the calling app for {@link CustomContentProvider}'s URI.
|
||||||
|
* However, while handling the request this would allow the calling app to actually contact {@link CustomContentProvider} directly and get access to stored data that should remain private.
|
||||||
|
*/
|
||||||
|
public class ShareContentProvider extends CustomContentProvider implements IContentResolver {
|
||||||
|
|
||||||
|
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||||
|
|
||||||
|
public static final String TAG = ShareContentProvider.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int URI_GPX = 0;
|
||||||
|
private static final int URI_KML_ONLY = 1;
|
||||||
|
private static final int URI_KML_WITH_TRACKDETAIL = 2;
|
||||||
|
private static final int URI_KML_WITH_TRACKDETAIL_SENSORDATA = 3;
|
||||||
|
|
||||||
|
private static final int URI_KMZ_ONLY_TRACK = 4;
|
||||||
|
private static final int URI_KMZ_WITH_TRACKDETAIL = 5;
|
||||||
|
private static final int URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA = 6;
|
||||||
|
private static final int URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES = 7;
|
||||||
|
|
||||||
|
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
|
private static final String TRACKID_DELIMITER = "_";
|
||||||
|
|
||||||
|
static {
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*", URI_GPX);
|
||||||
|
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_ONLY_TRACK.getName() + "/*", URI_KML_ONLY);
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL.getName() + "/*", URI_KML_WITH_TRACKDETAIL);
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_ONLY_TRACK.getName() + "/*", URI_KMZ_ONLY_TRACK);
|
||||||
|
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL);
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pair<Uri, String> createURI(long[] trackIds, @NonNull TrackFileFormat trackFileFormat) {
|
||||||
|
if (trackIds.length == 0) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (long trackId : trackIds) {
|
||||||
|
builder.append(trackId).append(TRACKID_DELIMITER);
|
||||||
|
}
|
||||||
|
builder.deleteCharAt(builder.lastIndexOf(TRACKID_DELIMITER));
|
||||||
|
|
||||||
|
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TracksColumns.TABLE_NAME + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
|
||||||
|
String mime = getTypeMime(uri);
|
||||||
|
|
||||||
|
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
|
||||||
|
|
||||||
|
return new Pair<>(uri, mime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long[] parseURI(Uri uri) {
|
||||||
|
String lastPathSegment = uri.getLastPathSegment();
|
||||||
|
if (lastPathSegment == null) {
|
||||||
|
return new long[]{};
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileExtension = "." + getTrackFileFormat(uri).getExtension();
|
||||||
|
String[] lastPathSegmentSplit = lastPathSegment.replace(fileExtension, "").split(TRACKID_DELIMITER);
|
||||||
|
|
||||||
|
long[] trackIds = new long[lastPathSegmentSplit.length];
|
||||||
|
for (int i = 0; i < trackIds.length; i++) {
|
||||||
|
trackIds[i] = Long.valueOf(lastPathSegmentSplit[i]);
|
||||||
|
}
|
||||||
|
return trackIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not allow to be exported via AndroidManifest.
|
||||||
|
* Check that caller has permissions to access {@link CustomContentProvider}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
|
||||||
|
super.attachInfo(context, info);
|
||||||
|
|
||||||
|
// Sanity check our security
|
||||||
|
if (info.exported) {
|
||||||
|
throw new UnsupportedOperationException("Provider must not be exported");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.grantUriPermissions) {
|
||||||
|
throw new SecurityException("Provider must grant uri permissions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) {
|
||||||
|
switch (uriMatcher.match(uri)) {
|
||||||
|
case URI_GPX:
|
||||||
|
return TrackFileFormat.GPX;
|
||||||
|
|
||||||
|
case URI_KML_ONLY:
|
||||||
|
return TrackFileFormat.KML_ONLY_TRACK;
|
||||||
|
case URI_KML_WITH_TRACKDETAIL:
|
||||||
|
return TrackFileFormat.KML_WITH_TRACKDETAIL;
|
||||||
|
case URI_KML_WITH_TRACKDETAIL_SENSORDATA:
|
||||||
|
return TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||||
|
|
||||||
|
case URI_KMZ_ONLY_TRACK:
|
||||||
|
return TrackFileFormat.KMZ_ONLY_TRACK;
|
||||||
|
case URI_KMZ_WITH_TRACKDETAIL:
|
||||||
|
return TrackFileFormat.KMZ_WITH_TRACKDETAIL;
|
||||||
|
case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA:
|
||||||
|
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||||
|
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES:
|
||||||
|
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getTypeMime(@NonNull Uri uri) {
|
||||||
|
return getTrackFileFormat(uri).getMimeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
|
||||||
|
if (uriMatcher.match(uri) == -1) {
|
||||||
|
return super.query(uri, projection, selection, selectionArgs, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentProvider has already checked granted permissions
|
||||||
|
if (projection == null) {
|
||||||
|
projection = COLUMNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] cols = new String[projection.length];
|
||||||
|
Object[] values = new Object[projection.length];
|
||||||
|
int i = 0;
|
||||||
|
for (String col : projection) {
|
||||||
|
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
|
||||||
|
cols[i] = OpenableColumns.DISPLAY_NAME;
|
||||||
|
values[i++] = uri.getLastPathSegment();
|
||||||
|
} else if (OpenableColumns.SIZE.equals(col)) {
|
||||||
|
cols[i] = OpenableColumns.SIZE;
|
||||||
|
values[i++] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cols = Arrays.copyOf(cols, i);
|
||||||
|
values = Arrays.copyOf(values, i);
|
||||||
|
|
||||||
|
final MatrixCursor cursor = new MatrixCursor(cols, 1);
|
||||||
|
cursor.addRow(values);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getType(@NonNull Uri uri) {
|
||||||
|
String mime = getTypeMime(uri);
|
||||||
|
if (mime != null) {
|
||||||
|
return mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getType(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtilsImpl(this);
|
||||||
|
|
||||||
|
long[] trackIds = parseURI(uri);
|
||||||
|
final Track[] tracks = new Track[trackIds.length];
|
||||||
|
for (int i = 0; i < trackIds.length; i++) {
|
||||||
|
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks, null);
|
||||||
|
|
||||||
|
PipeDataWriter pipeDataWriter = new PipeDataWriter<String>() {
|
||||||
|
@Override
|
||||||
|
public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable String args) {
|
||||||
|
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||||
|
trackExporter.writeTrack(getContext(), fileOutputStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return openPipeHelper(uri, getType(uri), null, null, pipeDataWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,12 +17,11 @@
|
|||||||
package de.dennisguse.opentracks.content;
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A track.
|
* A track.
|
||||||
@@ -30,82 +29,29 @@ import java.util.ArrayList;
|
|||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class Track implements Parcelable {
|
public class Track {
|
||||||
|
|
||||||
public static final Parcelable.Creator<Track> CREATOR = new Parcelable.Creator<Track>() {
|
|
||||||
@Override
|
|
||||||
public Track createFromParcel(Parcel in) {
|
|
||||||
return new Track(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Track[] newArray(int size) {
|
|
||||||
return new Track[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private long id = -1L;
|
private long id = -1L;
|
||||||
private String name = "";
|
private String name = "";
|
||||||
private String description = "";
|
private String description = "";
|
||||||
private String category = "";
|
private String category = "";
|
||||||
private long startId = -1L;
|
private long startId = -1L;
|
||||||
private long stopId = -1L;
|
private long stopId = -1L;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The number of location points (present even if the points themselves are
|
* The number of location points (present even if the points themselves are not loaded)
|
||||||
* not loaded)
|
|
||||||
*/
|
*/
|
||||||
private int numberOfPoints = 0;
|
private int numberOfPoints = 0;
|
||||||
private String icon = "";
|
private String icon = "";
|
||||||
private long modifiedTime = -1L;
|
|
||||||
private TripStatistics tripStatistics = new TripStatistics();
|
private TripStatistics tripStatistics = new TripStatistics();
|
||||||
|
|
||||||
// Location points (which may not have been loaded)
|
// Location points (which may not have been loaded)
|
||||||
private ArrayList<Location> locations = new ArrayList<>();
|
private List<Location> locations = new ArrayList<>();
|
||||||
|
|
||||||
public Track() {
|
public Track() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Track(Parcel in) {
|
|
||||||
id = in.readLong();
|
|
||||||
name = in.readString();
|
|
||||||
description = in.readString();
|
|
||||||
category = in.readString();
|
|
||||||
startId = in.readLong();
|
|
||||||
stopId = in.readLong();
|
|
||||||
numberOfPoints = in.readInt();
|
|
||||||
icon = in.readString();
|
|
||||||
modifiedTime = in.readLong();
|
|
||||||
|
|
||||||
ClassLoader classLoader = getClass().getClassLoader();
|
|
||||||
tripStatistics = in.readParcelable(classLoader);
|
|
||||||
|
|
||||||
for (int i = 0; i < numberOfPoints; ++i) {
|
|
||||||
Location location = in.readParcelable(classLoader);
|
|
||||||
locations.add(location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeLong(id);
|
|
||||||
dest.writeString(name);
|
|
||||||
dest.writeString(description);
|
|
||||||
dest.writeString(category);
|
|
||||||
dest.writeLong(startId);
|
|
||||||
dest.writeLong(stopId);
|
|
||||||
dest.writeInt(numberOfPoints);
|
|
||||||
dest.writeString(icon);
|
|
||||||
dest.writeLong(modifiedTime);
|
|
||||||
|
|
||||||
dest.writeParcelable(tripStatistics, 0);
|
|
||||||
for (int i = 0; i < numberOfPoints; ++i) {
|
|
||||||
dest.writeParcelable(locations.get(i), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -170,14 +116,6 @@ public class Track implements Parcelable {
|
|||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getModifiedTime() {
|
|
||||||
return modifiedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setModifiedTime(long modifiedTime) {
|
|
||||||
this.modifiedTime = modifiedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TripStatistics getTripStatistics() {
|
public TripStatistics getTripStatistics() {
|
||||||
return tripStatistics;
|
return tripStatistics;
|
||||||
}
|
}
|
||||||
@@ -190,7 +128,7 @@ public class Track implements Parcelable {
|
|||||||
locations.add(location);
|
locations.add(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<Location> getLocations() {
|
public List<Location> getLocations() {
|
||||||
return locations;
|
return locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,24 +35,25 @@ import de.dennisguse.opentracks.util.LocationUtils;
|
|||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track data hub. Receives data from {@link DataSource} and distributes it to
|
* Track data hub. Receives data from {@link DataSource} and distributes it to {@link TrackDataListener} after some processing.
|
||||||
* {@link TrackDataListener} after some processing.
|
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class TrackDataHub implements DataSourceListener {
|
public class TrackDataHub implements DataSourceListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target number of track points displayed by the map overlay. We may display
|
* Target number of track points displayed by the map overlay.
|
||||||
* more than this number of points.
|
* We may display more than this number of points.
|
||||||
*/
|
*/
|
||||||
public static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
|
static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of waypoints to displayed.
|
* Maximum number of waypoints to displayed.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int MAX_DISPLAYED_WAYPOINTS = 128;
|
private static final int MAX_DISPLAYED_WAYPOINTS = 128;
|
||||||
private static final String TAG = TrackDataHub.class.getSimpleName();
|
private static final String TAG = TrackDataHub.class.getSimpleName();
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final TrackDataManager trackDataManager;
|
private final TrackDataManager trackDataManager;
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
@@ -87,8 +88,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
* @param targetNumPoints the target number of points
|
* @param targetNumPoints the target number of points
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
TrackDataHub(Context context, TrackDataManager trackDataManager,
|
private TrackDataHub(Context context, TrackDataManager trackDataManager, ContentProviderUtils contentProviderUtils, int targetNumPoints) {
|
||||||
ContentProviderUtils contentProviderUtils, int targetNumPoints) {
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.trackDataManager = trackDataManager;
|
this.trackDataManager = trackDataManager;
|
||||||
this.contentProviderUtils = contentProviderUtils;
|
this.contentProviderUtils = contentProviderUtils;
|
||||||
@@ -96,17 +96,11 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
resetSamplingState();
|
resetSamplingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance.
|
|
||||||
*/
|
|
||||||
public synchronized static TrackDataHub newInstance(Context context) {
|
public synchronized static TrackDataHub newInstance(Context context) {
|
||||||
return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get(
|
return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get(
|
||||||
context), TARGET_DISPLAYED_TRACK_POINTS);
|
context), TARGET_DISPLAYED_TRACK_POINTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts.
|
|
||||||
*/
|
|
||||||
public void start() {
|
public void start() {
|
||||||
if (started) {
|
if (started) {
|
||||||
Log.i(TAG, "TrackDataHub already started, ignoring start.");
|
Log.i(TAG, "TrackDataHub already started, ignoring start.");
|
||||||
@@ -131,9 +125,6 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops.
|
|
||||||
*/
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (!started) {
|
if (!started) {
|
||||||
Log.i(TAG, "TrackDataHub not started, ignoring stop.");
|
Log.i(TAG, "TrackDataHub not started, ignoring stop.");
|
||||||
@@ -151,11 +142,6 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
dataSourceManager = null;
|
dataSourceManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a track.
|
|
||||||
*
|
|
||||||
* @param trackId the track id
|
|
||||||
*/
|
|
||||||
public void loadTrack(final long trackId) {
|
public void loadTrack(final long trackId) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -222,8 +208,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
* Returns true if the selected track is recording.
|
* Returns true if the selected track is recording.
|
||||||
*/
|
*/
|
||||||
public boolean isSelectedTrackRecording() {
|
public boolean isSelectedTrackRecording() {
|
||||||
return selectedTrackId == recordingTrackId
|
return selectedTrackId == recordingTrackId && PreferencesUtils.isRecording(recordingTrackId);
|
||||||
&& recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,8 +243,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
notifyTrackPointsTableUpdate(
|
notifyTrackPointsTableUpdate(true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
|
||||||
true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
|
|
||||||
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -270,55 +254,44 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (key == null
|
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||||
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||||
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
|
||||||
}
|
}
|
||||||
if (key == null
|
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||||
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(
|
|
||||||
context, R.string.recording_track_paused_key,
|
|
||||||
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
|
||||||
}
|
}
|
||||||
if (key == null
|
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||||
|| key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
|
|
||||||
metricUnits = PreferencesUtils.isMetricUnits(context);
|
metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener :
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
|
||||||
if (trackDataListener.onMetricUnitsChanged(metricUnits)) {
|
if (trackDataListener.onMetricUnitsChanged(metricUnits)) {
|
||||||
loadDataForListener(trackDataListener);
|
loadDataForListener(trackDataListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null
|
if (PreferencesUtils.isKey(context, R.string.stats_rate_key, key)) {
|
||||||
|| key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
|
|
||||||
reportSpeed = PreferencesUtils.isReportSpeed(context);
|
reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener :
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
|
||||||
if (trackDataListener.onReportSpeedChanged(reportSpeed)) {
|
if (trackDataListener.onReportSpeedChanged(reportSpeed)) {
|
||||||
loadDataForListener(trackDataListener);
|
loadDataForListener(trackDataListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null
|
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||||
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
|
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||||
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key,
|
|
||||||
PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener :
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
|
||||||
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
|
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
|
||||||
loadDataForListener(trackDataListener);
|
loadDataForListener(trackDataListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||||
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
|
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
|
||||||
@@ -359,7 +332,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads data for a listener. To be run in the {@link #handler} thread.
|
* Loads data for a listener; to be run in the {@link #handler} thread.
|
||||||
*
|
*
|
||||||
* @param trackDataListener the track data listener.
|
* @param trackDataListener the track data listener.
|
||||||
*/
|
*/
|
||||||
@@ -386,8 +359,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
if (isOnlyListener) {
|
if (isOnlyListener) {
|
||||||
resetSamplingState();
|
resetSamplingState();
|
||||||
}
|
}
|
||||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners
|
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.<TrackDataListener>emptySet();
|
||||||
: Collections.<TrackDataListener>emptySet();
|
|
||||||
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +369,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies track table update. To be run in the {@link #handler} thread.
|
* Notifies track table update; to be run in the {@link #handler} thread.
|
||||||
*
|
*
|
||||||
* @param trackDataListeners the track data listeners to notify
|
* @param trackDataListeners the track data listeners to notify
|
||||||
*/
|
*/
|
||||||
@@ -412,9 +384,8 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies waypoint table update. Currently, reloads all the waypoints up to
|
* Notifies waypoint table update.
|
||||||
* {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler}
|
* Currently, reloads all the waypoints up to {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler} thread.
|
||||||
* thread.
|
|
||||||
*
|
*
|
||||||
* @param trackDataListeners the track data listeners to notify
|
* @param trackDataListeners the track data listeners to notify
|
||||||
*/
|
*/
|
||||||
@@ -448,18 +419,17 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies track points table update. To be run in the {@link #handler}
|
* Notifies track points table update; to be run in the {@link #handler} thread.
|
||||||
* thread.
|
|
||||||
*
|
*
|
||||||
* @param updateSamplingState true to update the sampling state
|
* @param updateSamplingState true to update the sampling state
|
||||||
* @param sampledInListeners the sampled-in listeners
|
* @param sampledInListeners the sampled-in listeners
|
||||||
* @param sampledOutListeners the sampled-out listeners
|
* @param sampledOutListeners the sampled-out listeners
|
||||||
*/
|
*/
|
||||||
private void notifyTrackPointsTableUpdate(boolean updateSamplingState,
|
private void notifyTrackPointsTableUpdate(boolean updateSamplingState, Set<TrackDataListener> sampledInListeners, Set<TrackDataListener> sampledOutListeners) {
|
||||||
Set<TrackDataListener> sampledInListeners, Set<TrackDataListener> sampledOutListeners) {
|
|
||||||
if (sampledInListeners.isEmpty() && sampledOutListeners.isEmpty()) {
|
if (sampledInListeners.isEmpty() && sampledOutListeners.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateSamplingState && numLoadedPoints >= targetNumPoints) {
|
if (updateSamplingState && numLoadedPoints >= targetNumPoints) {
|
||||||
// Reload and resample the track at a lower frequency.
|
// Reload and resample the track at a lower frequency.
|
||||||
Log.i(TAG, "Resampling track after " + numLoadedPoints + " points.");
|
Log.i(TAG, "Resampling track after " + numLoadedPoints + " points.");
|
||||||
@@ -477,11 +447,8 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
||||||
int samplingFrequency = -1;
|
int samplingFrequency = -1;
|
||||||
boolean includeNextPoint = false;
|
boolean includeNextPoint = false;
|
||||||
LocationIterator locationIterator = null;
|
|
||||||
|
|
||||||
try {
|
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
||||||
locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId,
|
|
||||||
localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
|
||||||
|
|
||||||
while (locationIterator.hasNext()) {
|
while (locationIterator.hasNext()) {
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
@@ -509,8 +476,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Also include the last point if the selected track is not recording.
|
// Also include the last point if the selected track is not recording.
|
||||||
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0)
|
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||||
|| (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
|
||||||
includeNextPoint = false;
|
includeNextPoint = false;
|
||||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||||
trackDataListener.onSampledInTrackPoint(location);
|
trackDataListener.onSampledInTrackPoint(location);
|
||||||
@@ -525,10 +491,6 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
localNumLoadedPoints++;
|
localNumLoadedPoints++;
|
||||||
localLastSeenLocationId = locationId;
|
localLastSeenLocationId = locationId;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (locationIterator != null) {
|
|
||||||
locationIterator.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateSamplingState) {
|
if (updateSamplingState) {
|
||||||
@@ -555,7 +517,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
* Creates a {@link DataSource}.
|
* Creates a {@link DataSource}.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected DataSource newDataSource() {
|
private DataSource newDataSource() {
|
||||||
return new DataSource(context);
|
return new DataSource(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,7 +527,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
* @param runnable the runnable
|
* @param runnable the runnable
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected void runInHandlerThread(Runnable runnable) {
|
private void runInHandlerThread(Runnable runnable) {
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
// Use a Throwable to ensure the stack trace is logged.
|
// Use a Throwable to ensure the stack trace is logged.
|
||||||
Log.d(TAG, "handler is null.", new Throwable());
|
Log.d(TAG, "handler is null.", new Throwable());
|
||||||
|
|||||||