Compare commits
156 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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,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"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -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
|
||||
dist: trusty
|
||||
|
||||
env:
|
||||
global:
|
||||
- ADB_INSTALL_TIMEOUT=8
|
||||
- ABI=armeabi-v7a
|
||||
- EMU_FLAVOR=default
|
||||
# PATH order is incredibly important. e.g. the 'emulator' script exists in more than one place!
|
||||
- ANDROID_HOME=/usr/local/android-sdk
|
||||
- TOOLS=${ANDROID_HOME}/tools
|
||||
- PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH}
|
||||
matrix:
|
||||
- API=27 ABI=x86_64
|
||||
|
||||
android:
|
||||
components:
|
||||
# installing tools to start, then use `sdkmanager` below to get the rest
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- android-28
|
||||
|
||||
# Emulator Management: Create, Start and Wait
|
||||
install:
|
||||
- echo y | sdkmanager "platform-tools"
|
||||
- echo y | sdkmanager --channel=4 "emulator"
|
||||
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
- android-wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
|
||||
script:
|
||||
- ./gradlew build
|
||||
#TODO: When all tests are passing, CI should also execute them.
|
||||
#- ./gradlew connectedCheck
|
||||
- ./gradlew clean build
|
||||
- ./gradlew connectedCheck
|
||||
|
||||
sbefore_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
- $HOME/.android/build-cache
|
||||
@@ -0,0 +1,12 @@
|
||||
# OpenTrack: Developer information
|
||||
|
||||
## Create releases for FDroid
|
||||
|
||||
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
|
||||
2. Decide on _version name_ (semantic versioning)
|
||||
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
|
||||
4. Create changelog (`_version code_.txt`)
|
||||
5. Create commit with all changes
|
||||
6. Push commits
|
||||
7. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||
8. Push the tagged commit to public repository
|
||||
@@ -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.
|
||||
|
||||
<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:
|
||||
* __Tracking:__ track your sport and outdoor activities
|
||||
* __Photos and Markers:__ mark interesting locations while tracking
|
||||
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
||||
* __Heart rate:__ supports bluetooth LE sensors
|
||||
* __Export data:__ Export tracks either as KML or GPX
|
||||
* __Voice annoucements__
|
||||
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
||||
* __No Internet access:__ Internet is not used
|
||||
* __Voice annoucements__
|
||||
* __No advertising__
|
||||
* __Privacy:__
|
||||
* _contains in-app analytics_
|
||||
* _does not contain any in-app analytics_
|
||||
* _does not have Google Play Services_
|
||||
* _has no cloud integration_
|
||||
|
||||
@@ -26,6 +26,10 @@ _Please note:_
|
||||
_Tracks_ does not contain a _map_ (as this would required Internet access).
|
||||
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
|
||||
|
||||
<a href="https://liberapay.com/dennis.guse/donate">
|
||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
|
||||
</a>
|
||||
|
||||
## Project history
|
||||
|
||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.2'
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ def getVersionCode = { ->
|
||||
return Integer.parseInt(stdout.toString().trim())
|
||||
}
|
||||
catch (ignored) {
|
||||
return -1;
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,21 +41,22 @@ def getVersionName = { ->
|
||||
return stdout.toString().trim()
|
||||
}
|
||||
catch (ignored) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
//Useful for debugging.
|
||||
//versionCode getVersionCode()
|
||||
//versionName getVersionName()
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
targetSdkVersion 29
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -67,7 +68,7 @@ android {
|
||||
}
|
||||
|
||||
release {
|
||||
minifyEnabled true
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,14 +76,14 @@ android {
|
||||
variant.resValue "string", "applicationId", variant.applicationId
|
||||
}
|
||||
|
||||
//TODO: Should be removed (when LINT does not throw any errors).
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
disable "AppCompatResource" // TODO Disable complaint to use androidx's SearchView as it is null.
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
@@ -94,6 +95,4 @@ dependencies {
|
||||
|
||||
testImplementation 'androidx.test:core:1.2.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,10 @@
|
||||
(tbd): OpenTracks
|
||||
|
||||
Changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
-
|
||||
|
||||
Developer:
|
||||
-
|
||||
@@ -3,6 +3,6 @@ OpenTracks is a sport tracking application that completely respects your privacy
|
||||
Features:
|
||||
* Sport tracking: track your sport and outdoor activities
|
||||
* Photos and markers: mark interesting locations while tracking
|
||||
* Heart rate: supports bluetooth LE sensors
|
||||
* Export data: Export tracks either as KML or GPX
|
||||
* Heart rate: supports Bluetooth LE sensors
|
||||
* Export data: export tracks either as KML or GPX
|
||||
* No Internet access: Internet is not used
|
||||
|
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
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,42 +22,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -60,6 +40,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -85,7 +105,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -105,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
@@ -150,11 +170,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -8,14 +24,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +62,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +75,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
A sport tracker that completely respects your privacy
|
||||
@@ -87,6 +87,8 @@ public class BootReceiverTest {
|
||||
*/
|
||||
private boolean isServiceExisted(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
Assert.assertNotNull(activityManager);
|
||||
|
||||
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||
|
||||
for (int i = 0; i < serviceList.size(); i++) {
|
||||
|
||||
@@ -23,8 +23,7 @@ import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
|
||||
/**
|
||||
* Commons utilities for creating stubs of track, location. The class will be
|
||||
* enriched if needs more similar stubs for test.
|
||||
* Commons utilities for creating stubs of track, location.
|
||||
*
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
@@ -50,8 +49,7 @@ public class TrackStubUtils {
|
||||
public static Track createTrack(int numberOfLocations) {
|
||||
Track track = new Track();
|
||||
for (int i = 0; i < numberOfLocations; i++) {
|
||||
track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE
|
||||
+ i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
|
||||
track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
|
||||
}
|
||||
|
||||
return track;
|
||||
@@ -71,7 +69,7 @@ public class TrackStubUtils {
|
||||
*
|
||||
* @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
|
||||
Location loc = new Location(LOCATION_PROVIDER);
|
||||
loc.setLatitude(latitude);
|
||||
@@ -85,5 +83,4 @@ public class TrackStubUtils {
|
||||
|
||||
return new SensorDataSetLocation(loc, sd);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ public class CustomContentProviderTest {
|
||||
public void setUp() {
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
|
||||
customContentProvider = new CustomContentProvider();
|
||||
customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
@@ -32,7 +33,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
|
||||
@@ -40,7 +40,6 @@ import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
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 TEST_DESC = "Test Desc!";
|
||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||
private static final double INITIAL_LATITUDE = 37.0;
|
||||
private static final double INITIAL_LONGITUDE = -57.0;
|
||||
private static final double ALTITUDE_INTERVAL = 2.5;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils providerUtils;
|
||||
private double INITIAL_LATITUDE = 37.0;
|
||||
private double INITIAL_LONGITUDE = -57.0;
|
||||
private double ALTITUDE_INTERVAL = 2.5;
|
||||
|
||||
@Mock
|
||||
private Cursor cursorMock;
|
||||
@@ -136,8 +136,7 @@ public class CustomContentProviderUtilsImplTest {
|
||||
long lastPointId = initializeTrack(trackId, numPoints);
|
||||
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
|
||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||
LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory);
|
||||
try {
|
||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
||||
while (it.hasNext()) {
|
||||
Location loc = it.next();
|
||||
Assert.assertNotNull(loc);
|
||||
@@ -147,8 +146,6 @@ public class CustomContentProviderUtilsImplTest {
|
||||
: lastPointId - numPoints + locations.size(), it.getLocationId());
|
||||
}
|
||||
Assert.assertEquals(numPoints, locations.size());
|
||||
} finally {
|
||||
it.close();
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
@@ -176,15 +173,12 @@ public class CustomContentProviderUtilsImplTest {
|
||||
// Load all inserted locations.
|
||||
long lastPointId = -1;
|
||||
int counter = 0;
|
||||
LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
try {
|
||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
lastPointId = it.getLocationId();
|
||||
counter++;
|
||||
}
|
||||
} finally {
|
||||
it.close();
|
||||
}
|
||||
|
||||
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
||||
@@ -219,7 +213,6 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Track track = providerUtils.createTrack(cursorMock);
|
||||
Assert.assertEquals(trackId, track.getId());
|
||||
Assert.assertEquals(name, track.getName());
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,26 +262,20 @@ public class CustomContentProviderUtilsImplTest {
|
||||
providerUtils.insertWaypoint(waypoint);
|
||||
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null,
|
||||
TracksColumns._ID);
|
||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(3, tracksCursor.getCount());
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null,
|
||||
null, TrackPointsColumns._ID);
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null,
|
||||
null, WaypointsColumns._ID);
|
||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(1, waypointCursor.getCount());
|
||||
// Delete one track.
|
||||
providerUtils.deleteTrack(context, trackId);
|
||||
// Check whether all data of a track has been deleted.
|
||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null,
|
||||
TracksColumns._ID);
|
||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(2, tracksCursor.getCount());
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null,
|
||||
null, TrackPointsColumns._ID);
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null,
|
||||
null, WaypointsColumns._ID);
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(0, waypointCursor.getCount());
|
||||
}
|
||||
|
||||
@@ -373,12 +360,12 @@ public class CustomContentProviderUtilsImplTest {
|
||||
waypoint.setType(WaypointType.STATISTICS);
|
||||
waypoint.setTripStatistics(statistics);
|
||||
|
||||
Location loc = new Location("test");
|
||||
loc.setLatitude(22);
|
||||
loc.setLongitude(22);
|
||||
loc.setAccuracy((float) 1 / 100.0f);
|
||||
loc.setAltitude(2.5);
|
||||
waypoint.setLocation(loc);
|
||||
Location location = new Location("test");
|
||||
location.setLatitude(22);
|
||||
location.setLongitude(22);
|
||||
location.setAccuracy((float) 1 / 100.0f);
|
||||
location.setAltitude(2.5);
|
||||
waypoint.setLocation(location);
|
||||
providerUtils.insertWaypoint(waypoint);
|
||||
|
||||
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
|
||||
@@ -425,7 +412,6 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Assert.assertEquals(id, waypoint.getId());
|
||||
Assert.assertEquals(name, waypoint.getName());
|
||||
Assert.assertEquals(trackId, waypoint.getTrackId());
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,8 +441,7 @@ public class CustomContentProviderUtilsImplTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateTrackDescription(Track aTrack, Vector<Double> distances,
|
||||
Vector<Double> elevations, boolean html) {
|
||||
public String generateTrackDescription(Track aTrack, boolean html) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -489,19 +474,22 @@ public class CustomContentProviderUtilsImplTest {
|
||||
statistics.setMaxGrade(15.0);
|
||||
statistics.setMinGrade(-25.0);
|
||||
statistics.setBounds(-10000, 20000, 30000, -40000);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setDescription(Long.toString(trackId));
|
||||
waypoint1.setTrackId(trackId);
|
||||
waypoint1.setType(WaypointType.STATISTICS);
|
||||
waypoint1.setTripStatistics(statistics);
|
||||
providerUtils.insertWaypoint(waypoint1);
|
||||
long waypoint1Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
|
||||
|
||||
Waypoint waypoint2 = new Waypoint();
|
||||
waypoint2.setDescription(Long.toString(trackId));
|
||||
waypoint2.setTrackId(trackId);
|
||||
waypoint2.setType(WaypointType.STATISTICS);
|
||||
waypoint2.setTripStatistics(statistics);
|
||||
providerUtils.insertWaypoint(waypoint2);
|
||||
long waypoint2Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
|
||||
|
||||
|
||||
// Delete
|
||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||
@@ -511,15 +499,16 @@ public class CustomContentProviderUtilsImplTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateTrackDescription(Track aTrack, Vector<Double> distances,
|
||||
Vector<Double> elevations, boolean html) {
|
||||
public String generateTrackDescription(Track aTrack, boolean html) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
|
||||
|
||||
Assert.assertNull(providerUtils.getWaypoint(1));
|
||||
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(2)
|
||||
Assert.assertNotNull(providerUtils.getWaypoint(waypoint1Id));
|
||||
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
|
||||
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
|
||||
|
||||
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
||||
.getDescription());
|
||||
}
|
||||
|
||||
@@ -534,13 +523,15 @@ public class CustomContentProviderUtilsImplTest {
|
||||
|
||||
Waypoint waypoint1 = new Waypoint();
|
||||
waypoint1.setTrackId(trackId);
|
||||
long waypoint1id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
|
||||
|
||||
Waypoint waypoint2 = new Waypoint();
|
||||
waypoint2.setTrackId(trackId);
|
||||
providerUtils.insertWaypoint(waypoint1);
|
||||
providerUtils.insertWaypoint(waypoint2);
|
||||
long waypoint2id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
|
||||
|
||||
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.setDescription(TEST_DESC);
|
||||
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.setDescription(TEST_DESC);
|
||||
waypoint.setTrackId(trackId);
|
||||
providerUtils.insertWaypoint(waypoint);
|
||||
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
|
||||
|
||||
// Update
|
||||
waypoint = providerUtils.getWaypoint(1);
|
||||
waypoint = providerUtils.getWaypoint(waypointId);
|
||||
waypoint.setDescription(TEST_DESC_NEW);
|
||||
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(time, location.getTime(), 0.01);
|
||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
||||
verify(cursorMock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -742,7 +733,7 @@ public class CustomContentProviderUtilsImplTest {
|
||||
Track track = getTrack(trackId, 10);
|
||||
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);
|
||||
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
|
||||
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
|
||||
* in descending.
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_desc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
|
||||
* in ascending.
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointCursor_asc() {
|
||||
// Insert track, points at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
providerUtils.insertTrack(track);
|
||||
|
||||
Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, false);
|
||||
Assert.assertEquals(5, cursor.getCount());
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(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.
|
||||
long trackId = System.currentTimeMillis();
|
||||
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);
|
||||
for (int i = 1; i >= 0; i--) {
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
Assert.assertTrue(locationIterator.hasNext());
|
||||
Location location = locationIterator.next();
|
||||
Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId());
|
||||
checkLocation(i, location);
|
||||
Assert.assertEquals(startTrackPointId - i, locationIterator.getLocationId());
|
||||
checkLocation((trackpointIds.length - 1) - i, location);
|
||||
}
|
||||
Assert.assertFalse(locationIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)}
|
||||
* in ascending.
|
||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_asc() {
|
||||
// Insert track, point at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = getTrack(trackId, 10);
|
||||
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);
|
||||
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
Assert.assertTrue(locationIterator.hasNext());
|
||||
Location location = locationIterator.next();
|
||||
Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId());
|
||||
Assert.assertEquals(startTrackPointId + i, locationIterator.getLocationId());
|
||||
|
||||
checkLocation(i, location);
|
||||
}
|
||||
Assert.assertFalse(locationIterator.hasNext());
|
||||
@@ -887,8 +892,7 @@ public class CustomContentProviderUtilsImplTest {
|
||||
/**
|
||||
* Checks the value of a location.
|
||||
*
|
||||
* @param i the index of this location which created in the method
|
||||
* {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
||||
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
||||
* @param location the location to be checked
|
||||
*/
|
||||
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
|
||||
public void testGenerateTrackDescription() {
|
||||
@@ -88,7 +88,7 @@ public class DescriptionGeneratorImplTest {
|
||||
+ "Min grade: 11 %<br>"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||
|
||||
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, null, null, true));
|
||||
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,8 +128,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder,
|
||||
* int, String)}.
|
||||
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteDistance() {
|
||||
@@ -139,8 +138,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int,
|
||||
* String)}.
|
||||
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteTime() {
|
||||
@@ -150,8 +148,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder,
|
||||
* int, String)}.
|
||||
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteSpeed() {
|
||||
@@ -161,8 +158,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder,
|
||||
* int, String)}.
|
||||
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteElevation() {
|
||||
@@ -172,8 +168,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int,
|
||||
* String)}.
|
||||
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWritePace() {
|
||||
@@ -183,8 +178,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
|
||||
* int, String)}.
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade() {
|
||||
@@ -194,8 +188,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
|
||||
* int, String)} with a NaN.
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with a NaN.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade_nan() {
|
||||
@@ -205,8 +198,7 @@ public class DescriptionGeneratorImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
|
||||
* int, String)} with an infinite number.
|
||||
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with an infinite number.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade_infinite() {
|
||||
|
||||
@@ -40,9 +40,8 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
/**
|
||||
* Tests for {@link SearchEngine}.
|
||||
* These are not meant to be quality tests, but instead feature-by-feature tests
|
||||
* (in other words, they don't test the mixing of different score boostings, just
|
||||
* each boosting separately)
|
||||
* These are not meant to be quality tests, but instead feature-by-feature tests.
|
||||
* In other words, they don't test the mixing of different score boostings, just each boosting separately.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@@ -50,7 +49,7 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
public class SearchEngineTest {
|
||||
|
||||
private static final Location HERE = new Location("gps");
|
||||
private static final long NOW = 1234567890000L; // After OLDEST_ALLOWED_TIMESTAMP
|
||||
private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000; // After OLDEST_ALLOWED_TIMESTAMP
|
||||
private ContentProviderUtils providerUtils;
|
||||
private SearchEngine engine;
|
||||
|
||||
|
||||
@@ -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
|
||||
public void testFillDataPoint_sensorIncorrect() {
|
||||
@@ -161,12 +161,11 @@ public class ChartFragmentTest {
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 22 and 23. And for we set using
|
||||
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
* Computes the distance between Latitude 22 and 23.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(),
|
||||
sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
|
||||
Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(), sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
Assert.assertEquals(distance1, point[0], 0.01);
|
||||
|
||||
@@ -176,11 +175,10 @@ public class ChartFragmentTest {
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation3);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 23 and 24. And for we set using
|
||||
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
* Computes the distance between Latitude 23 and 24.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(),
|
||||
sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
|
||||
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
Assert.assertEquals(distance1 + distance2, point[0], 0.01);
|
||||
}
|
||||
@@ -210,15 +208,14 @@ public class ChartFragmentTest {
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* At first, clear old points of elevation, so give true to the second
|
||||
* parameter. Then only one value INITIALLONGTITUDE in buffer.
|
||||
* At first, clear old points of elevation, so give true to the second parameter.
|
||||
* Then only one value INITIAL_ALTITUDE in buffer.
|
||||
*/
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01);
|
||||
|
||||
/*
|
||||
* Send another value to buffer, now there are two values, INITIALALTITUDE
|
||||
* and INITIALALTITUDE * 2.
|
||||
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
|
||||
*/
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
@@ -235,8 +232,8 @@ public class ChartFragmentTest {
|
||||
@Test
|
||||
public void testFillDataPoint_speed() {
|
||||
/*
|
||||
* At first, clear old points of speed, so give true to the second
|
||||
* parameter. It will not be filled in to the speed buffer.
|
||||
* At first, clear old points of speed, so give true to the second parameter.
|
||||
* It will not be filled in to the speed buffer.
|
||||
*/
|
||||
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation1.setSpeed(128.5f);
|
||||
@@ -244,14 +241,13 @@ public class ChartFragmentTest {
|
||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
|
||||
/*
|
||||
* Tests the logic when both metricUnits and reportSpeed are true.This
|
||||
* location will be filled into speed buffer.
|
||||
* Tests the logic when both metricUnits and reportSpeed are true.
|
||||
* This location will be filled into speed buffer.
|
||||
*/
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid, the value
|
||||
* 222 here is doesn't matter.
|
||||
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
|
||||
*/
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222);
|
||||
sensorDataSetLocation2.setSpeed(130);
|
||||
@@ -276,9 +272,8 @@ public class ChartFragmentTest {
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid and the speed
|
||||
* is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION
|
||||
* * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
||||
sensorDataSetLocation2.setSpeed(102);
|
||||
@@ -303,9 +298,8 @@ public class ChartFragmentTest {
|
||||
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid and the speed
|
||||
* is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION
|
||||
* * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
||||
sensorDataSetLocation2.setSpeed(102);
|
||||
@@ -314,8 +308,7 @@ public class ChartFragmentTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get pace value when reportSpeed is false and average
|
||||
* speed is zero.
|
||||
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_pace_zeroSpeed() {
|
||||
|
||||
@@ -49,40 +49,32 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
public abstract class AbstractTestFileTrackImporter {
|
||||
|
||||
protected static final String TRACK_NAME_0 = "blablub";
|
||||
protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
||||
static final String TRACK_NAME_0 = "blablub";
|
||||
static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
||||
|
||||
protected static final String TRACK_NAME_1 = "another track";
|
||||
protected static final String TRACK_DESCRIPTION_1 = "another description";
|
||||
static final double TRACK_LATITUDE = 48.768364;
|
||||
static final double TRACK_LONGITUDE = 9.177886;
|
||||
static final double TRACK_ELEVATION = 324.0;
|
||||
|
||||
protected static final double TRACK_LATITUDE = 48.768364;
|
||||
protected static final double TRACK_LONGITUDE = 9.177886;
|
||||
protected static final double TRACK_ELEVATION = 324.0;
|
||||
static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
||||
static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123";
|
||||
static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
|
||||
static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
|
||||
|
||||
protected static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
||||
protected static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123";
|
||||
protected static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
|
||||
protected static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
|
||||
|
||||
protected static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
||||
protected static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
||||
protected static final long TRACK_ID_0 = 1;
|
||||
protected static final long TRACK_ID_1 = 2;
|
||||
protected static final long TRACK_POINT_ID_0 = 1;
|
||||
protected static final long TRACK_POINT_ID_1 = 2;
|
||||
protected static final long TRACK_POINT_ID_3 = 4;
|
||||
protected static final long WAYPOINT_ID_0 = 1;
|
||||
protected static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
|
||||
protected static final Uri TRACK_ID_1_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build();
|
||||
protected static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
||||
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
||||
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
||||
static final long TRACK_ID_0 = 1;
|
||||
static final long TRACK_POINT_ID_0 = 1;
|
||||
static final long TRACK_POINT_ID_1 = 2;
|
||||
static final long TRACK_POINT_ID_3 = 4;
|
||||
static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
|
||||
private static final long WAYPOINT_ID_0 = 1;
|
||||
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
||||
|
||||
protected final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
static {
|
||||
/*
|
||||
* We can't omit the timezones in the test, otherwise it'll use the local
|
||||
* timezone and fail depending on where the test runner is.
|
||||
*/
|
||||
// We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is.
|
||||
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC");
|
||||
DATE_FORMAT_0.setTimeZone(utc);
|
||||
DATE_FORMAT_1.setTimeZone(utc);
|
||||
@@ -91,7 +83,7 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
@Mock
|
||||
public ContentProviderUtils contentProviderUtils;
|
||||
|
||||
protected Location createLocation(int index, long time) {
|
||||
Location createLocation(int index, long time) {
|
||||
Location location = new Location(LocationManager.GPS_PROVIDER);
|
||||
location.setLatitude(TRACK_LATITUDE + index);
|
||||
location.setLongitude(TRACK_LONGITUDE + index);
|
||||
@@ -120,7 +112,7 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
* @param lastTrack true if it is the last track in the gpx
|
||||
* @param trackId the track id
|
||||
*/
|
||||
protected void expectUpdateTrack(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
|
||||
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
|
||||
contentProviderUtils.updateTrack(trackCaptor.capture());
|
||||
|
||||
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
|
||||
|
||||
@@ -34,11 +34,11 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@link GpxFileTrackImporter}.
|
||||
*
|
||||
@@ -58,6 +58,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
|
||||
+ getTrackPoint(0, null) + getTrackPoint(1, null) + "</trkseg><trkseg>"
|
||||
+ getTrackPoint(2, null) + getTrackPoint(3, null) + "</trkseg></trk></gpx>";
|
||||
|
||||
private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring(0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50);
|
||||
private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "1000.0");
|
||||
private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(TRACK_TIME_0, "invalid");
|
||||
@@ -82,13 +83,9 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests one track with one segment.
|
||||
*/
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
@@ -99,27 +96,27 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests one track with two segments.
|
||||
*/
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
@@ -128,11 +125,17 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
@@ -141,17 +144,12 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests one track with two segments, but no time in the track points.
|
||||
*/
|
||||
@Test
|
||||
public void testOneTrackTwoSegmentsNoTime() {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
@@ -159,61 +157,48 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an invalid xml input.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidXml() {
|
||||
testInvalidGpx(INVALID_XML_GPX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an invalid location.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidLocation() {
|
||||
testInvalidGpx(INVALID_LOCATION_GPX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an invalid time.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidTime() {
|
||||
testInvalidGpx(INVALID_TIME_GPX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an invalid altitude.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidAltitude() {
|
||||
testInvalidGpx(INVALID_ALTITUDE_GPX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an invalid latitude.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidLatitude() {
|
||||
testInvalidGpx(INVALID_LATITUDE_GPX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an invalid longitude.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidLongitude() {
|
||||
testInvalidGpx(INVALID_LONGITUDE_GPX);
|
||||
@@ -222,8 +207,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
private void testInvalidGpx(String xml) {
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
|
||||
// For the following, use StubReturn since we don't care whether they are
|
||||
// invoked or not.
|
||||
// For the following, use StubReturn since we don't care whether they are invoked or not.
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
||||
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||
@@ -234,6 +218,5 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
long trackId = gpxFileTrackImporter.importFile(inputStream);
|
||||
Assert.assertEquals(-1L, trackId);
|
||||
verify(contentProviderUtils);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@link KmlFileTrackImporter}.
|
||||
*
|
||||
@@ -45,13 +45,11 @@ import static org.mockito.Mockito.when;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
|
||||
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX =
|
||||
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
||||
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1)
|
||||
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
|
||||
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX =
|
||||
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
||||
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</gx:Track><gx:Track>"
|
||||
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3)
|
||||
@@ -66,18 +64,12 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
String latitude = Double.toString(TRACK_LATITUDE + index);
|
||||
String longitude = Double.toString(TRACK_LONGITUDE + index);
|
||||
String altitude = Double.toString(TRACK_ELEVATION + index);
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>");
|
||||
return buffer.toString();
|
||||
return "<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests one track with one segment.
|
||||
*/
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
@@ -85,30 +77,31 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint(new Location[]{location1}, eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests one track with two segments.
|
||||
*/
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
contentProviderUtils.clearTrack(context, TRACK_ID_0);
|
||||
@@ -118,11 +111,17 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
||||
expectUpdateTrack(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||
|
||||
// when
|
||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
|
||||
long trackId = kmlFileTrackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
@@ -131,7 +130,6 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
|
||||
verify(contentProviderUtils);
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the
|
||||
* {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four
|
||||
* situations.
|
||||
* Tests the {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four situations.
|
||||
* <ul>
|
||||
* <li>The newIdleTime is bigger than max interval.</li>
|
||||
* <li>The newIdleTime is between min and max interval.</li>
|
||||
@@ -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() {
|
||||
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;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -54,45 +55,76 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
* Tests for the track recording service.
|
||||
*
|
||||
* @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 TrackRecordingServiceTest {
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = new ServiceTestRule();
|
||||
private ContentProviderUtils providerUtils;
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils 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
|
||||
public void setUp() throws Exception {
|
||||
public void setUp() {
|
||||
// Set up the mock content resolver
|
||||
CustomContentProvider customContentProvider = new CustomContentProvider();
|
||||
ContentProvider customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
customContentProvider.attachInfo(context, null);
|
||||
|
||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
||||
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().apply();
|
||||
|
||||
// Disable auto resume by default.
|
||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
|
||||
// No recording track.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
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(createStartIntent(context)));
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
// Ensure that the database is empty after every test
|
||||
providerUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
@Test
|
||||
public void testStartable() throws TimeoutException {
|
||||
Intent intent = new Intent(context, TrackRecordingService.class);
|
||||
mServiceRule.startService(createStartIntent());
|
||||
Assert.assertNotNull(mServiceRule.bindService(createStartIntent()));
|
||||
mServiceRule.startService(createStartIntent(context));
|
||||
Assert.assertNotNull(mServiceRule.bindService(createStartIntent(context)));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testBindable() throws TimeoutException {
|
||||
IBinder service = mServiceRule.bindService(createStartIntent());
|
||||
IBinder service = mServiceRule.bindService(createStartIntent(context));
|
||||
Assert.assertNotNull(service);
|
||||
}
|
||||
|
||||
@@ -100,27 +132,30 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testResumeAfterReboot_shouldResume() throws Exception {
|
||||
// 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.
|
||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent();
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We expect to resume the previous track.
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertEquals(123L, service.getRecordingTrackId());
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_simulateReboot() throws Exception {
|
||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
// Simulate recording a track.
|
||||
@@ -131,12 +166,14 @@ public class TrackRecordingServiceTest {
|
||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent();
|
||||
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
Assert.assertNotNull(service);
|
||||
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
Assert.assertTrue(service.isRecording());
|
||||
}
|
||||
|
||||
@@ -144,16 +181,18 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
|
||||
// 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.
|
||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT,
|
||||
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent();
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it was stopped.
|
||||
@@ -165,43 +204,46 @@ public class TrackRecordingServiceTest {
|
||||
@Test
|
||||
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
||||
// 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.
|
||||
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT,
|
||||
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent();
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it has expired.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
|
||||
// Insert a dummy track.
|
||||
createDummyTrack(123L, System.currentTimeMillis(), true);
|
||||
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
||||
|
||||
// Set the number of attempts to max.
|
||||
updateAutoResumePrefs(TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS,
|
||||
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent();
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
//then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because there were already
|
||||
// too many attempts.
|
||||
// We don't expect to resume the previous track, because there were already too many attempts.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -210,7 +252,10 @@ public class TrackRecordingServiceTest {
|
||||
List<Track> tracks = providerUtils.getAllTracks();
|
||||
Assert.assertTrue(tracks.isEmpty());
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
Intent startIntent = createStartIntent(context);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// Test if we start in no-recording mode by default.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
@@ -219,137 +264,60 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_oldTracks() throws Exception {
|
||||
createDummyTrack(123L, -1L, false);
|
||||
createDummyTrack(trackId, -1L, false);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||
Intent startIntent = createStartIntent(context);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// Just set recording track to a bogus value.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 256L);
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||
|
||||
// Make sure that the service will not start recording and will clear
|
||||
// the bogus track.
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testStartNewTrack_noRecording() throws Exception {
|
||||
// NOTICE: due to the way Android permissions work, if this fails,
|
||||
// uninstall the test apk then retry - the test must be installed *after* the app (go figure).
|
||||
// Reference: http://code.google.com/p/android/issues/detail?id=5521
|
||||
BlockingBroadcastReceiver startReceiver = new BlockingBroadcastReceiver();
|
||||
String startAction = context.getString(R.string.track_started_broadcast_action);
|
||||
context.registerReceiver(startReceiver, new IntentFilter(startAction));
|
||||
|
||||
List<Track> tracks = providerUtils.getAllTracks();
|
||||
Assert.assertTrue(tracks.isEmpty());
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
long id = service.startNewTrack();
|
||||
Assert.assertTrue(id >= 0);
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Track track = providerUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
Assert.assertEquals(id, track.getId());
|
||||
Assert.assertEquals(PreferencesUtils.getString(context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT), track.getCategory());
|
||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||
|
||||
// Verify that the start broadcast was received.
|
||||
Assert.assertTrue(startReceiver.waitUntilReceived(1));
|
||||
List<Intent> receivedIntents = startReceiver.getReceivedIntents();
|
||||
Assert.assertEquals(1, receivedIntents.size());
|
||||
Intent broadcastIntent = receivedIntents.get(0);
|
||||
Assert.assertEquals(startAction, broadcastIntent.getAction());
|
||||
Assert.assertEquals(id, broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
|
||||
|
||||
context.unregisterReceiver(startReceiver);
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||
createDummyTrack(123L, -1L, true);
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertTrue(service.isRecording());
|
||||
|
||||
// Starting a new track when there is a recording should just return -1L.
|
||||
long newTrack = service.startNewTrack();
|
||||
Assert.assertEquals(-1L, newTrack);
|
||||
long newTrackId = service.startNewTrack();
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||
|
||||
Assert.assertEquals(123L, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(123L, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@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);
|
||||
Assert.assertEquals(trackId, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
/*
|
||||
* Ending the current track when there is no recording should not result in
|
||||
* any error.
|
||||
*/
|
||||
// Ending the current track when there is no recording should not result in any error.
|
||||
service.endCurrentTrack();
|
||||
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testIntegration_completeRecordingSession() throws Exception {
|
||||
List<Track> tracks = providerUtils.getAllTracks();
|
||||
Assert.assertTrue(tracks.isEmpty());
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
@@ -359,21 +327,23 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
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.assertFalse(service.isPaused());
|
||||
insertLocation(service);
|
||||
|
||||
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
||||
Assert.assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
||||
long waypointId1 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertNotEquals(-1L, waypointId1);
|
||||
long waypointId2 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertNotEquals(-1L, waypointId2);
|
||||
|
||||
Waypoint wpt = providerUtils.getWaypoint(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_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(123L, wpt.getTrackId());
|
||||
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNotNull(wpt.getTripStatistics());
|
||||
@@ -382,149 +352,7 @@ public class TrackRecordingServiceTest {
|
||||
// TODO: Check waypoint 2.
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
Assert.assertEquals(-1L, waypointId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||
createDummyTrack(123L, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
Assert.assertTrue(service.isRecording());
|
||||
insertLocation(service);
|
||||
|
||||
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT));
|
||||
Waypoint wpt = providerUtils.getWaypoint(1);
|
||||
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
|
||||
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
||||
Assert.assertEquals(WaypointType.WAYPOINT, wpt.getType());
|
||||
Assert.assertEquals(123L, wpt.getTrackId());
|
||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNull(wpt.getTripStatistics());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyDefault() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByDistance() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByTime() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistanceDefault() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistance() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistanceDefault() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistance() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyDefault() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByDistance() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByTime() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws Exception {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDisabled() throws Exception {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingIntervalDefault() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingInterval() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracyDefault() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracy() throws Exception {
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
private Track createDummyTrack(long id, long stopTime, boolean isRecording) {
|
||||
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
||||
Track dummyTrack = new Track();
|
||||
dummyTrack.setId(id);
|
||||
dummyTrack.setName("Dummy Track");
|
||||
@@ -532,18 +360,6 @@ public class TrackRecordingServiceTest {
|
||||
tripStatistics.setStopTime(stopTime);
|
||||
dummyTrack.setTripStatistics(tripStatistics);
|
||||
addTrack(dummyTrack, isRecording);
|
||||
return dummyTrack;
|
||||
}
|
||||
|
||||
private void updateAutoResumePrefs(int attempts, int timeoutMins) {
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins);
|
||||
}
|
||||
|
||||
private Intent createStartIntent() {
|
||||
Intent startIntent = new Intent();
|
||||
startIntent.setClass(context, TrackRecordingService.class);
|
||||
return startIntent;
|
||||
}
|
||||
|
||||
private void addTrack(Track track, boolean isRecording) {
|
||||
@@ -554,56 +370,8 @@ public class TrackRecordingServiceTest {
|
||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
||||
}
|
||||
|
||||
private void fullRecordingSession() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
// Start a track.
|
||||
long id = service.startNewTrack();
|
||||
Assert.assertTrue(id >= 0);
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Track track = providerUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
Assert.assertEquals(id, track.getId());
|
||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
long startTime = System.currentTimeMillis();
|
||||
for (int i = 0; i < 30; i++) {
|
||||
Location loc = new Location("gps");
|
||||
loc.setLongitude(35.0f + i / 10.0f);
|
||||
loc.setLatitude(45.0f - i / 5.0f);
|
||||
loc.setAccuracy(5);
|
||||
loc.setSpeed(10);
|
||||
loc.setTime(startTime + i * 10000);
|
||||
loc.setBearing(3.0f);
|
||||
service.insertTrackPoint(loc);
|
||||
|
||||
if (i % 10 == 0) {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
} else if (i % 7 == 0) {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the track. Validate if it has correct data.
|
||||
service.endCurrentTrack();
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
track = providerUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
Assert.assertEquals(id, track.getId());
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
Assert.assertNotNull(tripStatistics);
|
||||
Assert.assertTrue(tripStatistics.getStartTime() > 0);
|
||||
Assert.assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location and waits for 100ms.
|
||||
*
|
||||
* @param trackRecordingService the track recording service
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
|
||||
Location location = new Location("gps");
|
||||
@@ -614,47 +382,38 @@ public class TrackRecordingServiceTest {
|
||||
location.setTime(System.currentTimeMillis());
|
||||
location.setBearing(3.0f);
|
||||
trackRecordingService.insertTrackPoint(location);
|
||||
|
||||
Thread.sleep(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous/waitable broadcast receiver to be used in testing.
|
||||
*/
|
||||
private class BlockingBroadcastReceiver extends BroadcastReceiver {
|
||||
private static final long MAX_WAIT_TIME_MS = 10000;
|
||||
private final List<Intent> receivedIntents = new ArrayList<>();
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
public List<Intent> getReceivedIntents() {
|
||||
return receivedIntents;
|
||||
}
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
Assert.assertEquals(-1L, waypointId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
Log.d("Test", "Got broadcast: " + intent);
|
||||
synchronized (receivedIntents) {
|
||||
receivedIntents.add(intent);
|
||||
receivedIntents.notifyAll();
|
||||
}
|
||||
}
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
public boolean waitUntilReceived(int receiveCount) {
|
||||
long deadline = System.currentTimeMillis() + MAX_WAIT_TIME_MS;
|
||||
synchronized (receivedIntents) {
|
||||
while (receivedIntents.size() < receiveCount) {
|
||||
try {
|
||||
// Wait releases synchronized lock until it returns
|
||||
receivedIntents.wait(500);
|
||||
} catch (InterruptedException e) {
|
||||
// Do nothing
|
||||
}
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertTrue(service.isRecording());
|
||||
insertLocation(service);
|
||||
|
||||
if (System.currentTimeMillis() > deadline) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
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());
|
||||
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNull(wpt.getTripStatistics());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
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, PreferencesUtils.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, PreferencesUtils.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 {
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
||||
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, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByTime() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
||||
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, PreferencesUtils.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 {
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
||||
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.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 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Tests for {@link AnnouncementPeriodicTaskFactory}.
|
||||
* These tests require Donut+ to run.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@@ -36,6 +38,17 @@ public class AnnouncementPeriodicTaskFactoryTest {
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks.services.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.same;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@link AnnouncementPeriodicTask}.
|
||||
* WARNING: I'm not responsible if your eyes start bleeding while reading this
|
||||
* code. You have been warned. It's still better than no test, though.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AnnouncementPeriodicTaskTest {
|
||||
|
||||
// Use something other than our hardcoded value
|
||||
private static final Locale DEFAULT_LOCALE = Locale.KOREAN;
|
||||
private static final String ANNOUNCEMENT = "I can haz cheeseburger?";
|
||||
private Locale oldDefaultLocale;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private AnnouncementPeriodicTask task;
|
||||
@Mock
|
||||
private AnnouncementPeriodicTask mockTask;
|
||||
private ArgumentCaptor<OnInitListener> initListenerCapture = ArgumentCaptor.forClass(OnInitListener.class);
|
||||
private ArgumentCaptor<PhoneStateListener> phoneListenerCapture = ArgumentCaptor.forClass(PhoneStateListener.class);
|
||||
private TextToSpeechDelegate ttsDelegate;
|
||||
@Mock
|
||||
private TextToSpeechInterface tts;
|
||||
|
||||
// @UsesMocks({
|
||||
// StringUtils.class,
|
||||
// })
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
oldDefaultLocale = Locale.getDefault();
|
||||
Locale.setDefault(DEFAULT_LOCALE);
|
||||
|
||||
// Eww, the effort required just to mock TextToSpeech is insane
|
||||
final AtomicBoolean listenerCalled = new AtomicBoolean();
|
||||
OnInitListener blockingListener = new OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
synchronized (this) {
|
||||
listenerCalled.set(true);
|
||||
notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ttsDelegate = new TextToSpeechDelegate(context, blockingListener);
|
||||
|
||||
// Wait for all async operations done in the constructor to finish.
|
||||
synchronized (blockingListener) {
|
||||
while (!listenerCalled.get()) {
|
||||
// Releases the synchronized lock until we're woken up.
|
||||
blockingListener.wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a partial forwarding mock
|
||||
task = new AnnouncementPeriodicTask(context) {
|
||||
@Override
|
||||
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
|
||||
return mockTask.newTextToSpeech(ctx, onInitListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAnnouncement(TripStatistics stats) {
|
||||
return mockTask.getAnnouncement(stats);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void listenToPhoneState(PhoneStateListener listener, int events) {
|
||||
mockTask.listenToPhoneState(listener, events);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Locale.setDefault(oldDefaultLocale);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart() {
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
Assert.assertNotNull(ttsInitListener);
|
||||
|
||||
ttsInitListener.onInit(TextToSpeech.SUCCESS);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart_notReady() {
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
Assert.assertNotNull(ttsInitListener);
|
||||
|
||||
ttsInitListener.onInit(TextToSpeech.ERROR);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdown() {
|
||||
// First, start
|
||||
doStart();
|
||||
verify(mockTask);
|
||||
reset(mockTask);
|
||||
|
||||
// Then, shut down
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
mockTask.listenToPhoneState(
|
||||
same(phoneListener), eq(PhoneStateListener.LISTEN_NONE));
|
||||
tts.shutdown();
|
||||
task.shutdown();
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun() {
|
||||
// when service data calls
|
||||
TripStatistics stats = new TripStatistics();
|
||||
|
||||
// when announcement building call
|
||||
when(mockTask.getAnnouncement(same(stats))).thenReturn(ANNOUNCEMENT);
|
||||
|
||||
// Put task in "ready" state
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isLanguageAvailable(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
||||
when(tts.setLanguage(DEFAULT_LOCALE)).thenReturn(TextToSpeech.LANG_AVAILABLE);
|
||||
when(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).thenReturn(TextToSpeech.SUCCESS);
|
||||
when(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) any())).thenReturn(0);
|
||||
|
||||
// Run the announcement
|
||||
task.announce(stats);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_notReady() {
|
||||
// Put task in "not ready" state
|
||||
startTask(TextToSpeech.ERROR);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_duringCall() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(false);
|
||||
|
||||
// Run the announcement
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
|
||||
task.run(null);
|
||||
//TODO
|
||||
//verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_ringWhileSpeaking() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(true);
|
||||
when(tts.stop()).thenReturn(TextToSpeech.SUCCESS);
|
||||
|
||||
// Update the state to ringing - this should stop the current announcement.
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
||||
|
||||
// Run the announcement - this should do nothing.
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_whileRinging() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
when(tts.isSpeaking()).thenReturn(false);
|
||||
|
||||
// Run the announcement
|
||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_noService() {
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_noStats() {
|
||||
// when service data calls
|
||||
|
||||
startTask(TextToSpeech.SUCCESS);
|
||||
|
||||
// Run the announcement
|
||||
task.run(null);
|
||||
//TODO
|
||||
//AndroidMock.verify(mockTask, tts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_time_zero() {
|
||||
long time = 0; // 0 seconds
|
||||
Assert.assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time one.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_time_one() {
|
||||
long time = 1 * 1000; // 1 second
|
||||
Assert.assertEquals("0 minutes 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
||||
* numbers with the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_singular_has_hour() {
|
||||
long time = (1 * 60 * 60 * 1000) + (1 * 60 * 1000) + (1 * 1000); // 1 hour 1 minute 1 second
|
||||
Assert.assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
||||
* with the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_plural_has_hour() {
|
||||
long time = (2 * 60 * 60 * 1000) + (2 * 60 * 1000) + (2 * 1000); // 2 hours 2 minutes 2 seconds
|
||||
Assert.assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular
|
||||
* numbers without the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_singular_no_hour() {
|
||||
long time = (1 * 60 * 1000) + (1 * 1000); // 1 minute 1 second
|
||||
Assert.assertEquals("1 minute 1 second", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers
|
||||
* without the hour unit.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAnnounceTime_plural_no_hour() {
|
||||
long time = (2 * 60 * 1000) + (2 * 1000); // 2 minutes 2 seconds
|
||||
Assert.assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time));
|
||||
}
|
||||
|
||||
private void startTask(int state) {
|
||||
reset(tts);
|
||||
doStart();
|
||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
||||
ttsInitListener.onInit(state);
|
||||
//TODO
|
||||
reset(tts);
|
||||
//AndroidMock.resetToDefault(tts);
|
||||
}
|
||||
|
||||
private void doStart() {
|
||||
mockTask.listenToPhoneState(phoneListenerCapture.capture(), eq(PhoneStateListener.LISTEN_CALL_STATE));
|
||||
when(mockTask.newTextToSpeech(same(context), initListenerCapture.capture())).thenReturn(ttsDelegate);
|
||||
|
||||
task.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mockable interface that we delegate TTS calls to.
|
||||
*/
|
||||
interface TextToSpeechInterface {
|
||||
int addEarcon(String earcon, String packagename, int resourceId);
|
||||
|
||||
int addEarcon(String earcon, String filename);
|
||||
|
||||
int addSpeech(String text, String packagename, int resourceId);
|
||||
|
||||
int addSpeech(String text, String filename);
|
||||
|
||||
boolean areDefaultsEnforced();
|
||||
|
||||
String getDefaultEngine();
|
||||
|
||||
Locale getLanguage();
|
||||
|
||||
int isLanguageAvailable(Locale loc);
|
||||
|
||||
boolean isSpeaking();
|
||||
|
||||
int playEarcon(String earcon, int queueMode,
|
||||
HashMap<String, String> params);
|
||||
|
||||
int playSilence(long durationInMs, int queueMode,
|
||||
HashMap<String, String> params);
|
||||
|
||||
int setEngineByPackageName(String enginePackageName);
|
||||
|
||||
int setLanguage(Locale loc);
|
||||
|
||||
int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener);
|
||||
|
||||
int setPitch(float pitch);
|
||||
|
||||
int setSpeechRate(float speechRate);
|
||||
|
||||
void shutdown();
|
||||
|
||||
int speak(String text, int queueMode, HashMap<String, String> params);
|
||||
|
||||
int stop();
|
||||
|
||||
int synthesizeToFile(String text, HashMap<String, String> params,
|
||||
String filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass of {@link TextToSpeech} which delegates calls to the interface
|
||||
* above.
|
||||
* The logic here is stupid and the author is ashamed of having to write it
|
||||
* like this, but basically the issue is that TextToSpeech cannot be mocked
|
||||
* without running its constructor, its constructor runs async operations
|
||||
* which call other methods (and then if the methods are part of a mock we'd
|
||||
* have to set a behavior, but we can't 'cause the object hasn't been fully
|
||||
* built yet).
|
||||
* The logic is that calls made during the constructor (when tts is not yet
|
||||
* set) will go up to the original class, but after tts is set we'll forward
|
||||
* them all to the mock.
|
||||
*/
|
||||
private class TextToSpeechDelegate extends TextToSpeech implements TextToSpeechInterface {
|
||||
public TextToSpeechDelegate(Context context, OnInitListener listener) {
|
||||
super(context, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addEarcon(String earcon, String packagename, int resourceId) {
|
||||
if (tts == null) {
|
||||
return super.addEarcon(earcon, packagename, resourceId);
|
||||
}
|
||||
return tts.addEarcon(earcon, packagename, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addEarcon(String earcon, String filename) {
|
||||
if (tts == null) {
|
||||
return super.addEarcon(earcon, filename);
|
||||
}
|
||||
return tts.addEarcon(earcon, filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addSpeech(String text, String packagename, int resourceId) {
|
||||
if (tts == null) {
|
||||
return super.addSpeech(text, packagename, resourceId);
|
||||
}
|
||||
return tts.addSpeech(text, packagename, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addSpeech(String text, String filename) {
|
||||
if (tts == null) {
|
||||
return super.addSpeech(text, filename);
|
||||
}
|
||||
return tts.addSpeech(text, filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLanguage() {
|
||||
if (tts == null) {
|
||||
return super.getLanguage();
|
||||
}
|
||||
return tts.getLanguage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int isLanguageAvailable(Locale loc) {
|
||||
if (tts == null) {
|
||||
return super.isLanguageAvailable(loc);
|
||||
}
|
||||
return tts.isLanguageAvailable(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpeaking() {
|
||||
if (tts == null) {
|
||||
return super.isSpeaking();
|
||||
}
|
||||
return tts.isSpeaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int playEarcon(String earcon, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.playEarcon(earcon, queueMode, params);
|
||||
}
|
||||
return tts.playEarcon(earcon, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int playSilence(long durationInMs, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.playSilence(durationInMs, queueMode, params);
|
||||
}
|
||||
return tts.playSilence(durationInMs, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setLanguage(Locale loc) {
|
||||
if (tts == null) {
|
||||
return super.setLanguage(loc);
|
||||
}
|
||||
return tts.setLanguage(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener) {
|
||||
if (tts == null) {
|
||||
return super.setOnUtteranceCompletedListener(listener);
|
||||
}
|
||||
return tts.setOnUtteranceCompletedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setPitch(float pitch) {
|
||||
if (tts == null) {
|
||||
return super.setPitch(pitch);
|
||||
}
|
||||
return tts.setPitch(pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setSpeechRate(float speechRate) {
|
||||
if (tts == null) {
|
||||
return super.setSpeechRate(speechRate);
|
||||
}
|
||||
return tts.setSpeechRate(speechRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (tts == null) {
|
||||
super.shutdown();
|
||||
return;
|
||||
}
|
||||
tts.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int speak(
|
||||
String text, int queueMode, HashMap<String, String> params) {
|
||||
if (tts == null) {
|
||||
return super.speak(text, queueMode, params);
|
||||
}
|
||||
return tts.speak(text, queueMode, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stop() {
|
||||
if (tts == null) {
|
||||
return super.stop();
|
||||
}
|
||||
return tts.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int synthesizeToFile(String text, HashMap<String, String> params, String filename) {
|
||||
if (tts == null) {
|
||||
return super.synthesizeToFile(text, params, filename);
|
||||
}
|
||||
return tts.synthesizeToFile(text, params, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,16 +32,13 @@ public class FileUtilsTest extends TestCase {
|
||||
* Tests {@link FileUtils#getPath(String...)}.
|
||||
*/
|
||||
public void testBuildExternalDirectoryPath() {
|
||||
String expectedName = Environment.getExternalStorageDirectory() + File.separator
|
||||
+ FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator
|
||||
+ "c";
|
||||
String expectedName = Environment.getExternalStorageDirectory() + File.separator + FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator + "c";
|
||||
String dirName = FileUtils.getPath("a", "b", "c");
|
||||
assertEquals(expectedName, dirName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the
|
||||
* file is new.
|
||||
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file is new.
|
||||
*/
|
||||
public void testBuildUniqueFileName_new() {
|
||||
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
|
||||
* file exists already.
|
||||
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file exists already.
|
||||
*/
|
||||
public void testBuildUniqueFileName_exist() {
|
||||
// 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
|
||||
* Chinese and Russian). Verifies that they are allowed.
|
||||
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
|
||||
* Verifies that they are allowed.
|
||||
*/
|
||||
public void testSanitizeFileName_i18n() {
|
||||
String name = "您好-привет";
|
||||
@@ -79,8 +75,8 @@ public class FileUtilsTest extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32
|
||||
* characters. Verifies that they are allowed.
|
||||
* Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32 characters.
|
||||
* Verifies that they are allowed.
|
||||
*/
|
||||
public void testSanitizeFileName_special_characters() {
|
||||
String name = "$%'-_@~`!(){}^#&+,;=[] ";
|
||||
@@ -89,8 +85,8 @@ public class FileUtilsTest extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped
|
||||
* characters in a row. Verifies that they are collapsed into one underscore.
|
||||
* Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped characters in a row.
|
||||
* Verifies that they are collapsed into one underscore.
|
||||
*/
|
||||
public void testSanitizeFileName_collapse() {
|
||||
String name = "hello//there";
|
||||
@@ -99,8 +95,8 @@ public class FileUtilsTest extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link FileUtils#truncateFileName(File, String, String)}. Verifies
|
||||
* the a long file name is truncated.
|
||||
* Tests {@link FileUtils#truncateFileName(File, String, String)}.
|
||||
* Verifies the a long file name is truncated.
|
||||
*/
|
||||
public void testTruncateFileName() {
|
||||
File directory = new File("/dir1/dir2/");
|
||||
@@ -115,7 +111,6 @@ public class FileUtilsTest extends TestCase {
|
||||
for (int i = 0; i < truncated.length(); i++) {
|
||||
assertEquals('a', truncated.charAt(i));
|
||||
}
|
||||
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH,
|
||||
new File(directory, truncated + suffix).getPath().length());
|
||||
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public class StringUtilsTest {
|
||||
assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300);
|
||||
assertGetTime("2010-05-04T03:02:01.35", 2010, 5, 4, 3, 2, 1, 350);
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3525Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 353);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,8 +149,7 @@ public class StringUtilsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time
|
||||
* zone.
|
||||
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time zone.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_fractionalAndTimezone() {
|
||||
@@ -161,8 +160,7 @@ public class StringUtilsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the {@link StringUtils#getTime(String)} returns the expected
|
||||
* values.
|
||||
* Asserts the {@link StringUtils#getTime(String)} returns the expected values.
|
||||
*
|
||||
* @param xmlDateTime the xml date time string
|
||||
* @param year the expected year
|
||||
@@ -173,8 +171,7 @@ public class StringUtilsTest {
|
||||
* @param second the expected second
|
||||
* @param millisecond the expected milliseconds
|
||||
*/
|
||||
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute,
|
||||
int second, int millisecond) {
|
||||
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute, int second, int millisecond) {
|
||||
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
calendar.set(year, month - 1, day, hour, minute, second);
|
||||
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
|
||||
@@ -185,7 +182,7 @@ public class StringUtilsTest {
|
||||
* Tests {@link StringUtils#getTimeParts(long)} with a positive number.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeParts_postive() {
|
||||
public void testGetTimeParts_positive() {
|
||||
int[] parts = StringUtils.getTimeParts(61000);
|
||||
Assert.assertEquals(1, parts[0]);
|
||||
Assert.assertEquals(1, parts[1]);
|
||||
@@ -202,4 +199,24 @@ public class StringUtilsTest {
|
||||
Assert.assertEquals(-1, parts[1]);
|
||||
Assert.assertEquals(0, parts[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDecimal() {
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 0));
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.1, 0));
|
||||
Assert.assertEquals("1", StringUtils.formatDecimal(1.1, 0));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10, 0));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10.1, 0));
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(-0.1, 0));
|
||||
|
||||
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 2));
|
||||
Assert.assertEquals("0.1", StringUtils.formatDecimal(0.1, 2));
|
||||
Assert.assertEquals("1.1", StringUtils.formatDecimal(1.1, 2));
|
||||
Assert.assertEquals("10", StringUtils.formatDecimal(10, 2));
|
||||
Assert.assertEquals("10.1", StringUtils.formatDecimal(10.1, 2));
|
||||
Assert.assertEquals("10.11", StringUtils.formatDecimal(10.111, 2));
|
||||
Assert.assertEquals("-0.1", StringUtils.formatDecimal(-0.1, 2));
|
||||
|
||||
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,7 @@ public class TrackNameUtilsTest {
|
||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||
|
||||
/**
|
||||
* Tests when the track_name_key is
|
||||
* settings_recording_track_name_date_local_value.
|
||||
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_date_local() {
|
||||
@@ -55,8 +54,7 @@ public class TrackNameUtilsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests when the track_name_key is
|
||||
* settings_recording_track_name_date_iso_8601_value.
|
||||
* Tests when the track_name_key is settings_recording_track_name_date_iso_8601_value.
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_date_iso_8601() {
|
||||
@@ -66,8 +64,7 @@ public class TrackNameUtilsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests when the track_name_key is
|
||||
* settings_recording_track_name_number_value.
|
||||
* Tests when the track_name_key is settings_recording_track_name_number_value.
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_number() {
|
||||
|
||||
@@ -14,75 +14,24 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="2947"
|
||||
android:versionName="v3.0.1"> <!-- For FDroid. -->
|
||||
android:versionCode="3101"
|
||||
android:versionName="v3.2.4"> <!-- For FDroid. -->
|
||||
<!-- Permission to initialize services -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- Permissions to record locations -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- Permissions to access sensors -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<!-- Permission to read/write external storage -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<!-- Required features -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<!-- Optional features -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.faketouch"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.compass"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony.gsm"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen.multitouch"
|
||||
android:required="false" />
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
<application
|
||||
android:name=".InitPreferences"
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@drawable/ic_logo"
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/ThemeCustom"
|
||||
tools:replace="android:label">
|
||||
android:theme="@style/ThemeCustom">
|
||||
|
||||
<!-- Activities -->
|
||||
<activity android:name=".AboutActivity" />
|
||||
<activity
|
||||
android:name=".AggregatedStatsActivity"
|
||||
android:label="@string/menu_aggregated_statistics" />
|
||||
|
||||
<activity android:name=".DeleteActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".io.file.importer.ImportActivity"
|
||||
android:theme="@style/ThemeCustomTransparent">
|
||||
@@ -100,8 +49,11 @@ limitations under the License.
|
||||
<data android:scheme="file" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MarkerEditActivity" />
|
||||
|
||||
<activity android:name=".MarkerDetailActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".MarkerListActivity"
|
||||
android:label="@string/menu_markers">
|
||||
@@ -109,6 +61,7 @@ limitations under the License.
|
||||
android:name="android.app.default_searchable"
|
||||
android:value=".SearchListActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".SearchListActivity"
|
||||
android:launchMode="singleTop">
|
||||
@@ -117,10 +70,15 @@ limitations under the License.
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/search" />
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
<activity android:name=".TrackDetailActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".TrackDetailActivity"
|
||||
android:screenOrientation="userPortrait" />
|
||||
|
||||
<activity android:name=".TrackEditActivity" />
|
||||
|
||||
<activity android:name=".TrackListActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -130,59 +88,19 @@ limitations under the License.
|
||||
android:name="android.app.default_searchable"
|
||||
android:value=".SearchListActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".io.file.exporter.ExportActivity"
|
||||
android:theme="@style/ThemeCustomTransparent" />
|
||||
<activity android:name=".settings.RecordingSettingsActivity" />
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
<activity android:name=".settings.StatsSettingsActivity" />
|
||||
<activity
|
||||
android:name=".widgets.TrackWidgetConfigActivity"
|
||||
android:label="@string/track_widget_config_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".services.ControlRecordingService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".services.TrackRecordingService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:icon="@drawable/ic_arrow_320"
|
||||
android:label="@string/recording_service" />
|
||||
<!-- Receivers -->
|
||||
<receiver android:name=".BootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".widgets.TrackWidgetProvider">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="TRACK_PAUSED" />
|
||||
<action android:name="TRACK_RESUMED" />
|
||||
<action android:name="TRACK_STARTED" />
|
||||
<action android:name="TRACK_STOPPED" />
|
||||
<action android:name="TRACK_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/track_widget_info" />
|
||||
</receiver>
|
||||
<!-- Providers -->
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
<provider
|
||||
android:name=".content.CustomContentProvider"
|
||||
android:authorities="${applicationId}"
|
||||
android:name=".content.ShareContentProvider"
|
||||
android:authorities="${applicationId}.content"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
<!-- Search suggestion provider -->
|
||||
<provider
|
||||
android:name=".content.SearchEngineProvider"
|
||||
android:authorities="${applicationId}.search"
|
||||
android:exported="false" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
@@ -193,5 +111,71 @@ limitations under the License.
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- Receivers -->
|
||||
<receiver android:name=".BootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Providers -->
|
||||
<service
|
||||
android:name=".services.TrackRecordingService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
</application>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
<!-- Permissions to record locations -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.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>
|
||||
|
||||
@@ -18,16 +18,14 @@ package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.view.Menu;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
/**
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public abstract class AbstractActivity extends FragmentActivity {
|
||||
public abstract class AbstractActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -37,16 +35,13 @@ public abstract class AbstractActivity extends FragmentActivity {
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
setContentView(getLayoutResId());
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the layout resource id.
|
||||
*/
|
||||
protected abstract int getLayoutResId();
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
TrackIconUtils.setMenuIconColor(menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* An abstract class for the following common tasks across
|
||||
* {@link TrackListActivity}, {@link TrackDetailActivity}, and
|
||||
* {@link SearchListActivity}:
|
||||
* <p>
|
||||
* - share track <br>
|
||||
* - delete tracks <br>
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
|
||||
|
||||
private static final String TAG = AbstractListActivity.class.getSimpleName();
|
||||
|
||||
protected static final int GPS_REQUEST_CODE = 6;
|
||||
private static final int DELETE_REQUEST_CODE = 3;
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == DELETE_REQUEST_CODE) {
|
||||
onDeleted();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tracks.
|
||||
*
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
protected void deleteTracks(long[] trackIds) {
|
||||
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmDeleteDone(long[] trackIds) {
|
||||
boolean stopRecording = false;
|
||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||
stopRecording = true;
|
||||
} else {
|
||||
long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);
|
||||
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 de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
|
||||
/**
|
||||
* This class encapsulates the meta data for one series of the chart values.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class ChartValueSeries {
|
||||
class ChartValueSeries {
|
||||
|
||||
private static final float STROKE_WIDTH = 2f;
|
||||
|
||||
@@ -68,8 +68,7 @@ public class ChartValueSeries {
|
||||
* @param fillColor the fill color
|
||||
* @param strokeColor the stroke color
|
||||
*/
|
||||
public ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues,
|
||||
int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
|
||||
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
|
||||
this.absoluteMin = absoluteMin;
|
||||
this.absoluteMax = absoluteMax;
|
||||
this.intervalValues = intervalValues;
|
||||
@@ -108,7 +107,7 @@ public class ChartValueSeries {
|
||||
/**
|
||||
* Returns true if the series is enabled.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@@ -117,14 +116,14 @@ public class ChartValueSeries {
|
||||
*
|
||||
* @param enabled true to enable
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the series has data.
|
||||
*/
|
||||
public boolean hasData() {
|
||||
boolean hasData() {
|
||||
return extremityMonitor.hasData();
|
||||
}
|
||||
|
||||
@@ -133,14 +132,14 @@ public class ChartValueSeries {
|
||||
*
|
||||
* @param value the new value
|
||||
*/
|
||||
public void update(double value) {
|
||||
void update(double value) {
|
||||
extremityMonitor.update(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path.
|
||||
*/
|
||||
public Path getPath() {
|
||||
Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -149,7 +148,7 @@ public class ChartValueSeries {
|
||||
*
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
public void drawPath(Canvas canvas) {
|
||||
void drawPath(Canvas canvas) {
|
||||
canvas.drawPath(path, fillPaint);
|
||||
canvas.drawPath(path, strokePaint);
|
||||
}
|
||||
@@ -157,7 +156,7 @@ public class ChartValueSeries {
|
||||
/**
|
||||
* Updates the y axis dimension.
|
||||
*/
|
||||
public void updateDimension() {
|
||||
void updateDimension() {
|
||||
double min = hasData() ? extremityMonitor.getMin() : 0.0;
|
||||
double max = hasData() ? extremityMonitor.getMax() : 1.0;
|
||||
min = Math.max(min, absoluteMin);
|
||||
@@ -204,14 +203,15 @@ public class ChartValueSeries {
|
||||
/**
|
||||
* Gets the interval value.
|
||||
*/
|
||||
public int getInterval() {
|
||||
@VisibleForTesting
|
||||
int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the min marker value.
|
||||
*/
|
||||
public int getMinMarkerValue() {
|
||||
int getMinMarkerValue() {
|
||||
return minMarkerValue;
|
||||
}
|
||||
|
||||
@@ -226,28 +226,28 @@ public class ChartValueSeries {
|
||||
/**
|
||||
* Gets the title id.
|
||||
*/
|
||||
public int getTitleId(boolean metricUnits) {
|
||||
int getTitleId(boolean metricUnits) {
|
||||
return metricUnits ? metricTitleId : imperialTitleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title paint.
|
||||
*/
|
||||
public Paint getTitlePaint() {
|
||||
Paint getTitlePaint() {
|
||||
return titlePaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the marker paint.
|
||||
*/
|
||||
public Paint getMarkerPaint() {
|
||||
Paint getMarkerPaint() {
|
||||
return markerPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the largest marker.
|
||||
*/
|
||||
public String getLargestMarker() {
|
||||
String getLargestMarker() {
|
||||
String minMarker = numberFormat.format(getMinMarkerValue());
|
||||
String maxMarker = numberFormat.format(getMaxMarkerValue());
|
||||
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
|
||||
@@ -258,7 +258,7 @@ public class ChartValueSeries {
|
||||
*
|
||||
* @param value the value
|
||||
*/
|
||||
public String formatMarker(int value) {
|
||||
String formatMarker(int 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.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());
|
||||
|
||||
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
|
||||
* will be reloaded.
|
||||
* Sets chart by distance.
|
||||
* It is expected that after changing this value, data will be reloaded.
|
||||
*
|
||||
* @param value true for by distance, false for by time
|
||||
*/
|
||||
@@ -449,8 +449,7 @@ public class ChartView extends View {
|
||||
synchronized (waypoints) {
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
Waypoint waypoint = waypoints.get(i);
|
||||
int distance = Math.abs(
|
||||
getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
||||
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearestWaypoint = waypoint;
|
||||
@@ -567,7 +566,7 @@ public class ChartView extends View {
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
private void drawSeriesTitles(Canvas canvas) {
|
||||
int[] titleDimensions = getTitleDimenions();
|
||||
int[] titleDimensions = getTitleDimensions();
|
||||
int lines = titleDimensions[0];
|
||||
int lineHeight = titleDimensions[1];
|
||||
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
|
||||
* the number of lines and the second element is the line height.
|
||||
* Gets the title dimensions.
|
||||
* 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 lineHeight = 0;
|
||||
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
|
||||
* dimensions change.
|
||||
* Updates paths. The path needs to be updated any time after the data or the dimensions change.
|
||||
*/
|
||||
private void updatePaths() {
|
||||
synchronized (chartData) {
|
||||
@@ -867,7 +865,7 @@ public class ChartView extends View {
|
||||
}
|
||||
|
||||
leftBorder = (int) (density * BORDER + markerLength);
|
||||
int[] titleDimensions = getTitleDimenions();
|
||||
int[] titleDimensions = getTitleDimensions();
|
||||
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
||||
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
||||
// 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.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* An activity to display marker detail info.
|
||||
* <p>
|
||||
* Allows to swipe to the next and previous marker.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller {
|
||||
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
|
||||
|
||||
private ArrayList<Long> markerIds;
|
||||
private List<Long> markerIds;
|
||||
|
||||
@Override
|
||||
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)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
/*
|
||||
* Yes, this will skip the first waypoint and that is intentional as the
|
||||
* first waypoint holds the stats for the track.
|
||||
*/
|
||||
while (cursor.moveToNext()) {
|
||||
Waypoint current = contentProviderUtils.createWaypoint(cursor);
|
||||
for (int i = 0; i < cursor.getCount(); i++) {
|
||||
Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor);
|
||||
|
||||
markerIds.add(current.getId());
|
||||
if (current.getId() == markerId) {
|
||||
markerIndex = markerIds.size() - 1;
|
||||
if (!currentMarker.isTripStatistics()) {
|
||||
markerIds.add(currentMarker.getId());
|
||||
if (currentMarker.getId() == markerId) {
|
||||
markerIndex = markerIds.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ViewPager viewPager = findViewById(R.id.maker_detail_activity_view_pager);
|
||||
viewPager.setAdapter(new MarkerDetailPagerAdapter(getSupportFragmentManager()));
|
||||
if (markerIndex != -1) {
|
||||
viewPager.setCurrentItem(markerIndex);
|
||||
}
|
||||
final ViewPager viewPager = findViewById(R.id.maker_detail_activity_view_pager);
|
||||
final MarkerDetailPagerAdapter markerAdapter = new MarkerDetailPagerAdapter(getSupportFragmentManager());
|
||||
viewPager.setAdapter(markerAdapter);
|
||||
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
|
||||
@@ -108,14 +127,20 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
*/
|
||||
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
public MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
||||
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Fragment getItem(int position) {
|
||||
String title = getString(R.string.marker_title, position + 1, getCount());
|
||||
return MarkerDetailFragment.newInstance(markerIds.get(position), title);
|
||||
return MarkerDetailFragment.newInstance(markerIds.get(position));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return getString(R.string.marker_title, position + 1, getCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,8 +30,6 @@ import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
/**
|
||||
* 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_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||
private long trackId;
|
||||
private long markerId;
|
||||
@@ -91,7 +90,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,14 +120,14 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
} else {
|
||||
saveMarker();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
if (newMarker) {
|
||||
statisticsSection.setVisibility(View.GONE);
|
||||
waypointSection.setVisibility(View.VISIBLE);
|
||||
int nextWaypointNumber = trackId == -1L ? -1
|
||||
: ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
||||
int nextWaypointNumber = trackId == -1L ? -1 : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
||||
if (nextWaypointNumber == -1) {
|
||||
nextWaypointNumber = 0;
|
||||
}
|
||||
@@ -168,8 +167,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypointDescription.getText().toString(),
|
||||
null,
|
||||
null);
|
||||
TrackRecordingServiceConnectionUtils.addMarker(
|
||||
this, trackRecordingServiceConnection, waypointCreationRequest);
|
||||
trackRecordingServiceConnection.addMarker(this, waypointCreationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,14 +22,11 @@ import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
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.fragments.DeleteMarkerDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
/**
|
||||
* Activity to show a list of markers in a track.
|
||||
@@ -66,8 +63,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
|
||||
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
|
||||
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
/*
|
||||
@@ -173,7 +173,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
}
|
||||
};
|
||||
listView.setAdapter(resourceCursorAdapter);
|
||||
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
|
||||
LoaderManager.getInstance(this).initLoader(0, null, new LoaderCallbacks<Cursor>() {
|
||||
@NonNull
|
||||
@@ -232,7 +232,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.marker_list_search);
|
||||
AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, null);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, null);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -254,24 +254,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
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.
|
||||
*
|
||||
@@ -284,10 +266,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
if (markerIds.length == 1) {
|
||||
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerIds[0]);
|
||||
//TODO Use IntentUtils.newShowOnMapIntent()
|
||||
startActivity(intent);
|
||||
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(markerIds[0]));
|
||||
}
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
@@ -301,8 +280,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
if (markerIds.length > 1 && markerIds.length == listView.getCount()) {
|
||||
markerIds = new long[]{-1L};
|
||||
}
|
||||
DeleteMarkerDialogFragment.newInstance(markerIds)
|
||||
.show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
|
||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_select_all:
|
||||
int size = listView.getCount();
|
||||
|
||||
@@ -17,48 +17,22 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Bundle;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
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.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -66,14 +40,33 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.SearchEngine;
|
||||
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
|
||||
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.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
|
||||
* <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();
|
||||
|
||||
@@ -91,55 +84,63 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
private static final String PHOTO_URL_FIELD = "photoUrl";
|
||||
private static final String MARKER_LATITUDE_FIELD = "latitude";
|
||||
private static final String MARKER_LONGITUDE_FIELD = "longitude";
|
||||
|
||||
private static final String TRACK_ID_FIELD = "trackId";
|
||||
private static final String MARKER_ID_FIELD = "markerId";
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
private SearchEngine searchEngine;
|
||||
private SearchRecentSuggestions searchRecentSuggestions;
|
||||
|
||||
private ArrayAdapter<Map<String, Object>> arrayAdapter;
|
||||
|
||||
private boolean metricUnits = true;
|
||||
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
|
||||
// Callback when an item is selected in the contextual action mode
|
||||
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||
@Override
|
||||
public boolean onClick(int itemId, int[] positions, long[] ids) {
|
||||
return handleContextItem(itemId, positions);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 isSingleSelectionTrack;
|
||||
if (isSingleSelection) {
|
||||
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;
|
||||
} else {
|
||||
isSingleSelectionTrack = false;
|
||||
}
|
||||
|
||||
// Not recording, one item, item is a track
|
||||
menu.findItem(R.id.list_context_menu_share).setVisible(!isRecording && isSingleSelection && isSingleSelectionTrack);
|
||||
//TODO Setup shareIntent.
|
||||
MenuItem shareMenuItem = menu.findItem(R.id.list_context_menu_share);
|
||||
if (isSingleSelectionTrack) {
|
||||
shareMenuItem.setVisible(!isRecording);
|
||||
}
|
||||
|
||||
// One item, item is a marker
|
||||
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection && !isSingleSelectionTrack);
|
||||
// One item, can be a track or a marker
|
||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||
// 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
|
||||
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
|
||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) {
|
||||
@@ -171,18 +172,16 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
searchEngine = new SearchEngine(contentProviderUtils);
|
||||
searchRecentSuggestions = SearchEngineProvider.newHelper(this);
|
||||
|
||||
arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
|
||||
View view;
|
||||
View view = convertView;
|
||||
if (convertView == null) {
|
||||
view = getLayoutInflater().inflate(R.layout.list_item, parent, false);
|
||||
} else {
|
||||
view = convertView;
|
||||
}
|
||||
|
||||
Map<String, Object> resultMap = getItem(position);
|
||||
boolean isRecording = (boolean) resultMap.get(IS_RECORDING_FIELD);
|
||||
boolean isPaused = (boolean) resultMap.get(IS_PAUSED_FIELD);
|
||||
@@ -213,18 +212,16 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||
Intent intent;
|
||||
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class);
|
||||
if (markerId != null) {
|
||||
intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
||||
} else {
|
||||
intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@@ -233,7 +230,7 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
super.onStart();
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,43 +253,11 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(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
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
||||
@@ -317,12 +282,12 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||
Intent intent;
|
||||
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:
|
||||
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);
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
@@ -335,12 +300,12 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
}
|
||||
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();
|
||||
return true;
|
||||
case R.id.list_context_menu_delete:
|
||||
if (markerId != null) {
|
||||
DeleteMarkerDialogFragment.newInstance(new long[]{markerId}).show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
|
||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), new long[]{markerId});
|
||||
} else {
|
||||
deleteTracks(new long[]{trackId});
|
||||
}
|
||||
@@ -369,7 +334,7 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the search.
|
||||
* Do the searchable.
|
||||
*
|
||||
* @param textQuery the query
|
||||
*/
|
||||
@@ -386,9 +351,6 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
arrayAdapter.addAll(displayResults);
|
||||
}
|
||||
});
|
||||
|
||||
// Save the query as a suggestion for the future
|
||||
searchRecentSuggestions.saveRecentQuery(query.textQuery, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,13 +363,11 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
//TODO Replace use of map<string, object>, but rather provide Track or Waypoint directly.
|
||||
ArrayList<Map<String, Object>> output = new ArrayList<>(scoredResults.size());
|
||||
for (ScoredResult result : scoredResults) {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
if (result.track != null) {
|
||||
prepareTrackForDisplay(result.track, resultMap);
|
||||
output.add(prepareTrackForDisplay(result.track));
|
||||
} else {
|
||||
prepareMarkerForDisplay(result.waypoint, resultMap);
|
||||
output.add(prepareMarkerForDisplay(result.waypoint));
|
||||
}
|
||||
output.add(resultMap);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@@ -415,15 +375,13 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
/**
|
||||
* Prepares a marker for display by filling in a result map.
|
||||
*
|
||||
* @param waypoint the marker
|
||||
* @param resultMap the result map
|
||||
* @param waypoint the marker
|
||||
* @return the result map
|
||||
*/
|
||||
@Deprecated
|
||||
private void prepareMarkerForDisplay(Waypoint waypoint, Map<String, Object> resultMap) {
|
||||
/*
|
||||
* TODO: It may be more appropriate to obtain the track name as a join in
|
||||
* the retrieval phase of the search.
|
||||
*/
|
||||
private Map<String, Object> prepareMarkerForDisplay(Waypoint waypoint) {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
|
||||
//TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the searchable.
|
||||
String trackName = null;
|
||||
long trackId = waypoint.getTrackId();
|
||||
if (trackId != -1L) {
|
||||
@@ -453,17 +411,20 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
|
||||
resultMap.put(MARKER_LATITUDE_FIELD, waypoint.getLocation().getLatitude());
|
||||
resultMap.put(MARKER_LONGITUDE_FIELD, waypoint.getLocation().getLongitude());
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a track for display by filling in a result map.
|
||||
*
|
||||
* @param track the track
|
||||
* @param resultMap the result map
|
||||
* @param track the track
|
||||
* @return the result map
|
||||
*/
|
||||
@Deprecated
|
||||
private void prepareTrackForDisplay(Track track, Map<String, Object> resultMap) {
|
||||
TripStatistics tripStatitics = track.getTripStatistics();
|
||||
private Map<String, Object> prepareTrackForDisplay(Track track) {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
String icon = track.getIcon();
|
||||
String category = icon != null && !icon.equals("") ? null : track.getCategory();
|
||||
|
||||
@@ -472,15 +433,17 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
|
||||
resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon));
|
||||
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track);
|
||||
resultMap.put(NAME_FIELD, track.getName());
|
||||
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatitics.getTotalTime()));
|
||||
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatitics.getTotalDistance(), metricUnits));
|
||||
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatistics.getTotalTime()));
|
||||
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatistics.getTotalDistance(), metricUnits));
|
||||
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(DESCRIPTION_FIELD, track.getDescription());
|
||||
resultMap.put(PHOTO_URL_FIELD, null);
|
||||
resultMap.put(TRACK_ID_FIELD, track.getId());
|
||||
resultMap.put(MARKER_ID_FIELD, null);
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,9 +97,8 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
/*
|
||||
* Unfortunately when TabHost changes the current tab, it kindly also takes
|
||||
* care of putting focus on it when not in touch mode. The jerk. This hack
|
||||
* tries to prevent this from pulling focus out of our ViewPager.
|
||||
* Unfortunately when TabHost changes the current tab, it kindly also takes care of putting focus on it when not in touch mode.
|
||||
* The jerk. This hack tries to prevent this from pulling focus out of our ViewPager.
|
||||
*/
|
||||
TabWidget tabWidget = tabHost.getTabWidget();
|
||||
int oldFocusability = tabWidget.getDescendantFocusability();
|
||||
@@ -122,15 +121,14 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
||||
private final Class<?> clss;
|
||||
private final Bundle bundle;
|
||||
|
||||
public TabInfo(Class<?> clss, Bundle bunlde) {
|
||||
TabInfo(Class<?> clss, Bundle bundle) {
|
||||
this.clss = clss;
|
||||
this.bundle = bunlde;
|
||||
this.bundle = bundle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the
|
||||
* {@link TabHost} API.
|
||||
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the {@link TabHost} API.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@@ -138,7 +136,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
||||
|
||||
private final Context context;
|
||||
|
||||
public DummyTabFactory(Context context) {
|
||||
DummyTabFactory(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.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
/**
|
||||
* 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.trackRecordingServiceConnection = trackRecordingServiceConnection;
|
||||
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;
|
||||
update(recording, paused);
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
void onPause() {
|
||||
isResumed = false;
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
}
|
||||
|
||||
@@ -17,34 +17,28 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ShareActionProvider;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabHost.TabSpec;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
import androidx.core.content.FileProvider;
|
||||
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.Track;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
@@ -53,18 +47,14 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.FrequencyDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
/**
|
||||
@@ -73,15 +63,16 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @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_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
||||
|
||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||
private static final String PHOTO_URI_KEY = "photo_uri_key";
|
||||
private static final String HAS_PHOTO_KEY = "has_photo_key";
|
||||
private static final String JPEG_EXTENSION = "jpeg";
|
||||
|
||||
private static final int CAMERA_REQUEST_CODE = 5;
|
||||
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
|
||||
@@ -103,11 +94,12 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
// Preferences
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
private boolean showOnLockScreen = PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN;
|
||||
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// After binding changes (is available), update the total time in
|
||||
// trackController.
|
||||
// After binding changes (is available), update the total time in trackController.
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -115,8 +107,9 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
if (hasPhoto && photoUri != null) {
|
||||
hasPhoto = false;
|
||||
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) {
|
||||
//TODO: Make configurable.
|
||||
FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri);
|
||||
}
|
||||
}
|
||||
@@ -140,6 +133,11 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
}
|
||||
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key))) {
|
||||
showOnLockScreen = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key, PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN);
|
||||
setLockscreenPolicy();
|
||||
}
|
||||
|
||||
if (key == null) return;
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -152,24 +150,24 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
private MenuItem shareMenuItem;
|
||||
private MenuItem voiceFrequencyMenuItem;
|
||||
private MenuItem splitFrequencyMenuItem;
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false);
|
||||
TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(true);
|
||||
TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
@@ -178,7 +176,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TrackRecordingServiceConnectionUtils.stopRecording(TrackDetailActivity.this, trackRecordingServiceConnection, true);
|
||||
trackRecordingServiceConnection.stopRecording(TrackDetailActivity.this, true);
|
||||
updateMenuItems(true);
|
||||
}
|
||||
};
|
||||
@@ -198,16 +196,16 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||
trackDataHub = TrackDataHub.newInstance(this);
|
||||
|
||||
tabHost = findViewById(android.R.id.tabhost);
|
||||
tabHost = findViewById(R.id.tackdetail_tabhost);
|
||||
tabHost.setup();
|
||||
|
||||
ViewPager viewPager = findViewById(R.id.pager);
|
||||
TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager);
|
||||
|
||||
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab), 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);
|
||||
|
||||
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);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
@@ -222,6 +220,22 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
setLockscreenPolicy();
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
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
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -229,7 +243,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
|
||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
trackDataHub.start();
|
||||
}
|
||||
|
||||
@@ -275,14 +289,6 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -294,6 +300,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
@@ -305,14 +312,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
||||
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
||||
|
||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||
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);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
@@ -334,26 +334,26 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
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:
|
||||
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
||||
return false;
|
||||
}
|
||||
takePicture();
|
||||
createWaypointWithPicture();
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
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:
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
@@ -371,30 +371,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
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
takePicture();
|
||||
createWaypointWithPicture();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@@ -468,8 +455,6 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
||||
shareMenuItem.setVisible(!isRecording());
|
||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
voiceFrequencyMenuItem.setVisible(isRecording());
|
||||
splitFrequencyMenuItem.setVisible(isRecording());
|
||||
String title;
|
||||
if (isRecording()) {
|
||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
@@ -480,8 +465,14 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
|
||||
setTitle(title);
|
||||
}
|
||||
|
||||
private void createWaypointWithPicture() {
|
||||
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, trackId);
|
||||
photoUri = intentAndPhotoUri.second;
|
||||
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
||||
}
|
||||
|
||||
public void chooseActivityType(String category) {
|
||||
ChooseActivityTypeDialogFragment.newInstance(category).show(getSupportFragmentManager(), ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,28 +28,27 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
||||
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
|
||||
* data such as track name, activity type, and track description.
|
||||
* 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.
|
||||
*
|
||||
* @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_NEW_TRACK = "new_track";
|
||||
|
||||
private static final String TAG = TrackEditActivity.class.getSimpleName();
|
||||
|
||||
private static final String ICON_VALUE_KEY = "icon_value_key";
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
@@ -57,7 +56,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
private Track track;
|
||||
private String iconValue;
|
||||
|
||||
private EditText name;
|
||||
private EditText nameEditText;
|
||||
private AutoCompleteTextView activityType;
|
||||
private Spinner activityTypeIcon;
|
||||
private EditText description;
|
||||
@@ -67,7 +66,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
super.onCreate(bundle);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
Long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
if (trackId == -1L) {
|
||||
Log.e(TAG, "invalid trackId");
|
||||
finish();
|
||||
@@ -82,14 +81,13 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
return;
|
||||
}
|
||||
|
||||
name = findViewById(R.id.track_edit_name);
|
||||
name.setText(track.getName());
|
||||
nameEditText = findViewById(R.id.track_edit_name);
|
||||
nameEditText.setText(track.getName());
|
||||
|
||||
activityType = findViewById(R.id.track_edit_activity_type);
|
||||
activityType.setText(track.getCategory());
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
|
||||
this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
activityType.setAdapter(adapter);
|
||||
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
@@ -122,9 +120,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
|
||||
getSupportFragmentManager(),
|
||||
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -133,9 +129,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
|
||||
getSupportFragmentManager(),
|
||||
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -144,11 +138,11 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
description = findViewById(R.id.track_edit_description);
|
||||
description.setText(track.getDescription());
|
||||
|
||||
Button save = findViewById(R.id.track_edit_save);
|
||||
save.setOnClickListener(new View.OnClickListener() {
|
||||
Button saveButton = findViewById(R.id.track_edit_save);
|
||||
saveButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
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(),
|
||||
contentProviderUtils);
|
||||
finish();
|
||||
@@ -175,7 +169,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,7 +179,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(ICON_VALUE_KEY, iconValue);
|
||||
}
|
||||
|
||||
@@ -26,11 +26,8 @@ import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.StrictMode;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
@@ -38,13 +35,12 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
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.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ServiceUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
/**
|
||||
@@ -73,12 +70,14 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
||||
*
|
||||
* @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[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
|
||||
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
|
||||
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
|
||||
|
||||
// The following are set in onCreate
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private SharedPreferences sharedPreferences;
|
||||
@@ -86,6 +85,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
private TrackController trackController;
|
||||
private ListView listView;
|
||||
private ResourceCursorAdapter resourceCursorAdapter;
|
||||
|
||||
private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||
@@ -103,20 +103,18 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
resourceCursorAdapter.swapCursor(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Preferences
|
||||
private boolean metricUnits = true;
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
|
||||
// Callback when an item is selected in the contextual action mode
|
||||
private final ContextualActionModeCallback
|
||||
contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||
|
||||
@Override
|
||||
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
|
||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
boolean isSingleSelection = ids.length == 1;
|
||||
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
|
||||
boolean isSingleSelection = trackIds.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_select_all).setVisible(showSelectAll);
|
||||
}
|
||||
@@ -126,9 +124,11 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
return handleContextItem(itemId, ids);
|
||||
}
|
||||
};
|
||||
|
||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
private final OnSharedPreferenceChangeListener
|
||||
@@ -142,7 +142,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(
|
||||
TrackListActivity.this, R.string.recording_track_id_key);
|
||||
if (key != null && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
trackRecordingServiceConnection.startAndBind();
|
||||
}
|
||||
}
|
||||
@@ -158,40 +158,42 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
public void run() {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Menu items
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem startGpsMenuItem;
|
||||
private MenuItem aggregatedStatisticsMenuItem;
|
||||
private MenuItem deleteAllMenuItem;
|
||||
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
updateMenuItems(false, false);
|
||||
TrackRecordingServiceConnectionUtils.stopRecording(
|
||||
TrackListActivity.this, trackRecordingServiceConnection, true);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this, true);
|
||||
}
|
||||
};
|
||||
|
||||
private boolean startGps = false; // true to start gps
|
||||
|
||||
private boolean startNewRecording = false; // true to start a new recording
|
||||
|
||||
// Callback when the trackRecordingServiceConnection binding changes.
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
/*
|
||||
* After binding changes (e.g., becomes available), update the total time
|
||||
* in trackController.
|
||||
* After binding changes (e.g., becomes available), update the total time in trackController.
|
||||
*/
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackController.update(recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, recordingTrackPaused);
|
||||
trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -212,8 +214,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
Toast.makeText(
|
||||
TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
|
||||
Toast.makeText(TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
if (startGps) {
|
||||
@@ -222,6 +223,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
@@ -232,12 +234,12 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false, true);
|
||||
TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(false, true);
|
||||
TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
@@ -248,18 +250,6 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
@@ -267,14 +257,17 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
// Show trackController when search dialog is dismissed
|
||||
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
||||
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
trackController.show();
|
||||
}
|
||||
});
|
||||
if (searchManager != null) {
|
||||
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
trackController.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
listView = findViewById(R.id.track_list);
|
||||
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
||||
@@ -286,6 +279,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
startActivity(newIntent);
|
||||
}
|
||||
});
|
||||
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
@@ -316,7 +310,8 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
}
|
||||
};
|
||||
listView.setAdapter(resourceCursorAdapter);
|
||||
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
|
||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
|
||||
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
|
||||
showStartupDialogs();
|
||||
@@ -328,7 +323,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -338,8 +333,7 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
// Update UI
|
||||
this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
trackController.onResume(isRecording, recordingTrackPaused);
|
||||
trackController.onResume(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -379,10 +373,9 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
getMenuInflater().inflate(R.menu.track_list, menu);
|
||||
|
||||
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);
|
||||
aggregatedStatisticsMenuItem = menu.findItem(R.id.track_list_aggregated_statistics);
|
||||
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
@@ -390,14 +383,13 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
boolean isGpsStarted = TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this);
|
||||
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
boolean isGpsStarted = ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
updateMenuItems(isGpsStarted, isRecording);
|
||||
|
||||
View searchView = searchMenuItem.getActionView();
|
||||
if (searchView instanceof SearchView) {
|
||||
((SearchView) searchView).setQuery("", false);
|
||||
}
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
searchView.setQuery("", false);
|
||||
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -407,11 +399,11 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
switch (item.getItemId()) {
|
||||
case R.id.track_list_start_gps:
|
||||
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);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
startGps = !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this);
|
||||
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||
|
||||
// Show toast
|
||||
Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT);
|
||||
@@ -438,10 +430,6 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||
startActivity(intent);
|
||||
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:
|
||||
deleteTracks(new long[]{-1L});
|
||||
return true;
|
||||
@@ -453,25 +441,6 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
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
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) {
|
||||
@@ -528,13 +497,9 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
startGpsMenuItem.setVisible(!isRecording);
|
||||
if (!isRecording) {
|
||||
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);
|
||||
TrackIconUtils.setMenuIconColor(startGpsMenuItem);
|
||||
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
|
||||
}
|
||||
}
|
||||
if (aggregatedStatisticsMenuItem != null) {
|
||||
aggregatedStatisticsMenuItem.setVisible(hasTrack);
|
||||
}
|
||||
if (deleteAllMenuItem != null) {
|
||||
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
|
||||
}
|
||||
@@ -548,10 +513,9 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
trackRecordingServiceConnection.startAndBind();
|
||||
|
||||
/*
|
||||
* If the binding has happened, then invoke the callback to start a new
|
||||
* recording. If the binding hasn't happened, then invoking the callback
|
||||
* will have no effect. But when the binding occurs, the callback will get
|
||||
* invoked.
|
||||
* If the binding has happened, then invoke the callback to start a new recording.
|
||||
* If the binding hasn't happened, then invoking the callback will have no effect.
|
||||
* But when the binding occurs, the callback will get invoked.
|
||||
*/
|
||||
bindChangedCallback.run();
|
||||
}
|
||||
@@ -564,13 +528,18 @@ public class TrackListActivity extends AbstractTrackActivity implements ConfirmD
|
||||
* @return true if handled.
|
||||
*/
|
||||
private boolean handleContextItem(int itemId, long[] trackIds) {
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, trackIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
//TODO
|
||||
Log.e(TAG, "Not implemented");
|
||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* The authority (the first part of the URI) for the app's content
|
||||
* provider.
|
||||
* Maximum number of waypoints that will be loaded at one time.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -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
|
||||
*/
|
||||
@@ -77,20 +88,19 @@ public interface ContentProviderUtils {
|
||||
void deleteTrack(Context context, long trackId);
|
||||
|
||||
/**
|
||||
* Gets all the tracks. If no track exists, an empty list is returned.
|
||||
* <p>
|
||||
* Note that the returned tracks do not have any track points attached.
|
||||
* Gets all the tracks.
|
||||
* If no track exists, an empty list is returned.
|
||||
* NOTE: the returned tracks do not have any track points attached.
|
||||
*/
|
||||
List<Track> getAllTracks();
|
||||
|
||||
/**
|
||||
* Gets the last track. Returns null if doesn't exist.
|
||||
* Gets the last track or null.
|
||||
*/
|
||||
Track getLastTrack();
|
||||
|
||||
/**
|
||||
* Gets a track by a track id. Returns null if not found.
|
||||
* <p>
|
||||
* Gets a track by a track id or null
|
||||
* Note that the returned track doesn't have any track points attached.
|
||||
*
|
||||
* @param trackId the track id.
|
||||
@@ -98,8 +108,8 @@ public interface ContentProviderUtils {
|
||||
Track getTrack(long trackId);
|
||||
|
||||
/**
|
||||
* Gets a track cursor. The caller owns the returned cursor and is responsible
|
||||
* for closing it.
|
||||
* Gets a track cursor.
|
||||
* The caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param selection the selection. Can be null
|
||||
* @param selectionArgs the selection arguments. Can be null
|
||||
@@ -109,8 +119,7 @@ public interface ContentProviderUtils {
|
||||
|
||||
/**
|
||||
* Inserts a track.
|
||||
* <p>
|
||||
* Note: This doesn't insert any track points.
|
||||
* NOTE: This doesn't insert any track points.
|
||||
*
|
||||
* @param track the track
|
||||
* @return the content provider URI of the inserted track.
|
||||
@@ -119,8 +128,7 @@ public interface ContentProviderUtils {
|
||||
|
||||
/**
|
||||
* Updates a track.
|
||||
* <p>
|
||||
* Note: This doesn't update any track points.
|
||||
* NOTE: This doesn't update any track points.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
@@ -134,19 +142,19 @@ public interface ContentProviderUtils {
|
||||
Waypoint createWaypoint(Cursor cursor);
|
||||
|
||||
/**
|
||||
* Deletes a waypoint. If deleting a statistics waypoint, this will also
|
||||
* correct the next statistics waypoint after the deleted one to reflect the
|
||||
* deletion. The generator is used to update the next statistics waypoint.
|
||||
* Deletes a waypoint.
|
||||
* If deleting a statistics waypoint, this will also correct the next statistics waypoint after the deleted one to reflect the deletion.
|
||||
* The generator is used to update the next statistics waypoint.
|
||||
*
|
||||
* @param waypointId the waypoint id
|
||||
* @param descriptionGenerator the description generator. Can be null for
|
||||
* waypoint marker
|
||||
* @param descriptionGenerator the description generator. Can be null for waypoint marker
|
||||
*/
|
||||
void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator);
|
||||
|
||||
/**
|
||||
* Gets the first waypoint id for a track. The first waypoint is special as it
|
||||
* contains the stats for the track. Returns -1L if it doesn't exist.
|
||||
* Gets the first waypoint id for a track.
|
||||
* The first waypoint is special as it contains the stats for the track.
|
||||
* Returns -1L if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
@@ -161,8 +169,8 @@ public interface ContentProviderUtils {
|
||||
Waypoint getLastWaypoint(long trackId, WaypointType waypointType);
|
||||
|
||||
/**
|
||||
* Gets the next waypoint number for a type. Returns -1 if not able to get the
|
||||
* next waypoint number.
|
||||
* Gets the next waypoint number for a type.
|
||||
* Returns -1 if not able to get the next waypoint number.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param waypointType the waypoint type
|
||||
@@ -170,15 +178,16 @@ public interface ContentProviderUtils {
|
||||
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
|
||||
*/
|
||||
Waypoint getWaypoint(long waypointId);
|
||||
|
||||
/**
|
||||
* Gets a waypoint cursor. The caller owns the returned cursor and is
|
||||
* responsible for closing it.
|
||||
* Gets a waypoint cursor.
|
||||
* he caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param selection the selection. Can be null
|
||||
* @param selectionArgs the selection arguments. Can be null
|
||||
@@ -189,13 +198,12 @@ public interface ContentProviderUtils {
|
||||
Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints);
|
||||
|
||||
/**
|
||||
* Gets a waypoint cursor for a track. The caller owns the returned cursor and
|
||||
* is responsible for closing it.
|
||||
* Gets a waypoint cursor for a track.
|
||||
* The caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param minWaypointId the minimum waypoint id. -1L to ignore
|
||||
* @param maxWaypoints the maximum number of waypoints to return. -1 for no
|
||||
* limit
|
||||
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
|
||||
*/
|
||||
Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints);
|
||||
|
||||
@@ -215,7 +223,8 @@ public interface ContentProviderUtils {
|
||||
Uri insertWaypoint(Waypoint waypoint);
|
||||
|
||||
/**
|
||||
* Updates a waypoint. Returns true if successful.
|
||||
* Updates a waypoint.
|
||||
* Returns true if successful.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
@@ -240,14 +249,16 @@ public interface ContentProviderUtils {
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -263,57 +274,44 @@ public interface ContentProviderUtils {
|
||||
long getTrackPointId(long trackId, Location location);
|
||||
|
||||
/**
|
||||
* Gets the first valid location for a track. Returns null if it doesn't
|
||||
* exist.
|
||||
* Gets the first valid location for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
Location getFirstValidTrackPoint(long trackId);
|
||||
|
||||
/**
|
||||
* Gets the last valid location for a track. Returns null if it doesn't exist.
|
||||
* Gets the last valid location for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
Location getLastValidTrackPoint(long trackId);
|
||||
|
||||
/**
|
||||
* Gets the last valid location.
|
||||
*/
|
||||
Location getLastValidTrackPoint();
|
||||
|
||||
/**
|
||||
* Creates a location cursor. The caller owns the returned cursor and is
|
||||
* responsible for closing it.
|
||||
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
||||
* @param maxLocations maximum number of locations to return. -1 for no limit
|
||||
* @param descending true to sort the result in descending order (latest
|
||||
* location first)
|
||||
* @param descending true to sort the result in descending order (latest location first)
|
||||
*/
|
||||
Cursor getTrackPointCursor(
|
||||
long trackId, long startTrackPointId, int maxLocations, boolean descending);
|
||||
Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending);
|
||||
|
||||
/**
|
||||
* Creates a new read-only iterator over a given track's points. It provides a
|
||||
* lightweight way of iterating over long tracks without failing due to the
|
||||
* underlying cursor limitations. Since it's a read-only iterator,
|
||||
* {@link Iterator#remove()} always throws
|
||||
* {@link UnsupportedOperationException}. Each call to
|
||||
* {@link LocationIterator#next()} may advance to the next DB record, and if
|
||||
* so, the iterator calls {@link LocationFactory#createLocation()} and
|
||||
* populates it with information retrieved from the record. When done with
|
||||
* iteration, {@link LocationIterator#close()} must be called.
|
||||
* Creates a new read-only iterator over a given track's points.
|
||||
* It provides a lightweight way of iterating over long tracks without failing due to the underlying cursor limitations.
|
||||
* Since it's a read-only iterator, {@link Iterator#remove()} always throws {@link UnsupportedOperationException}.
|
||||
* Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link LocationFactory#createLocation()} and populates it with information retrieved from the record.
|
||||
* When done with iteration, {@link LocationIterator#close()} must be called.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
||||
* @param descending true to sort the result in descending order (latest
|
||||
* location first)
|
||||
* @param descending true to sort the result in descending order (latest location first)
|
||||
* @param locationFactory the location factory
|
||||
*/
|
||||
LocationIterator getTrackPointLocationIterator(
|
||||
long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory);
|
||||
LocationIterator getTrackPointLocationIterator(long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory);
|
||||
|
||||
/**
|
||||
* Inserts a track point.
|
||||
@@ -325,10 +323,9 @@ public interface ContentProviderUtils {
|
||||
Uri insertTrackPoint(Location location, long trackId);
|
||||
|
||||
/**
|
||||
* A lightweight wrapper around the original {@link Cursor} with a method to
|
||||
* clean up.
|
||||
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
|
||||
*/
|
||||
interface LocationIterator extends Iterator<Location> {
|
||||
interface LocationIterator extends Iterator<Location>, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Gets the most recently retrieved track point id by {@link #next()}.
|
||||
@@ -347,8 +344,8 @@ public interface ContentProviderUtils {
|
||||
interface LocationFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Location}. An implementation can create new
|
||||
* instances or reuse existing instances for optimization.
|
||||
* Creates a new {@link Location}.
|
||||
* An implementation can create new instances or reuse existing instances for optimization.
|
||||
*/
|
||||
Location createLocation();
|
||||
}
|
||||
@@ -358,41 +355,12 @@ public interface ContentProviderUtils {
|
||||
*/
|
||||
class Factory {
|
||||
|
||||
private static Factory instance = new Factory();
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link ContentProviderUtils}.
|
||||
*
|
||||
* @param context the 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,18 +24,21 @@ import android.location.Location;
|
||||
import android.net.Uri;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.
|
||||
* Allows to use {@link ContentResolver} and {@link android.content.ContentProvider} interchangeably via {@link IContentResolver}.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
@@ -45,10 +48,14 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
||||
|
||||
private static final int MAX_LATITUDE = 90000000;
|
||||
|
||||
private final ContentResolver contentResolver;
|
||||
private final IContentResolver contentResolver;
|
||||
private int defaultCursorBatchSize = 2000;
|
||||
|
||||
public ContentProviderUtilsImpl(ContentResolver contentResolver) {
|
||||
this.contentResolver = new ContentResolverWrapper(contentResolver);
|
||||
}
|
||||
|
||||
public ContentProviderUtilsImpl(IContentResolver contentResolver) {
|
||||
this.contentResolver = contentResolver;
|
||||
}
|
||||
|
||||
@@ -716,8 +723,7 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
||||
if (maxWaypoints >= 0) {
|
||||
sortOrder += " LIMIT " + maxWaypoints;
|
||||
}
|
||||
return contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
||||
return contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -837,16 +843,7 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getLastValidTrackPoint() {
|
||||
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) {
|
||||
public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) {
|
||||
if (trackId < 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -963,8 +960,7 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
||||
|
||||
@Override
|
||||
public Uri insertTrackPoint(Location location, long trackId) {
|
||||
return contentResolver.insert(
|
||||
TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId));
|
||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1074,10 +1070,8 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
|
||||
* @param selectionArgs the selection arguments
|
||||
* @param sortOrder the sort order
|
||||
*/
|
||||
private Cursor getTrackPointCursor(
|
||||
String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return contentResolver.query(
|
||||
TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
||||
private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String 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.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
@@ -29,42 +28,39 @@ import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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
|
||||
* waypoints tables.
|
||||
* A {@link ContentProvider} that handles access to track points, tracks, and waypoints tables.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class CustomContentProvider extends ContentProvider {
|
||||
public abstract class CustomContentProvider extends ContentProvider {
|
||||
|
||||
@VisibleForTesting
|
||||
static final int DATABASE_VERSION = 23;
|
||||
|
||||
@VisibleForTesting
|
||||
static final String DATABASE_NAME = "database.db";
|
||||
|
||||
private static final String TAG = CustomContentProvider.class.getSimpleName();
|
||||
|
||||
private final UriMatcher uriMatcher;
|
||||
|
||||
private SQLiteDatabase db;
|
||||
|
||||
public CustomContentProvider() {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_ID.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -277,23 +273,6 @@ public class CustomContentProvider extends ContentProvider {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
|
||||
import de.dennisguse.opentracks.Constants;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -31,12 +30,12 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class DataSource {
|
||||
class DataSource {
|
||||
|
||||
private final ContentResolver contentResolver;
|
||||
private final SharedPreferences sharedPreferences;
|
||||
|
||||
public DataSource(Context context) {
|
||||
DataSource(Context context) {
|
||||
contentResolver = context.getContentResolver();
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
}
|
||||
@@ -47,7 +46,7 @@ public class DataSource {
|
||||
* @param uri the uri
|
||||
* @param observer the observer
|
||||
*/
|
||||
public void registerContentObserver(Uri uri, ContentObserver observer) {
|
||||
void registerContentObserver(Uri uri, ContentObserver observer) {
|
||||
contentResolver.registerContentObserver(uri, false, observer);
|
||||
}
|
||||
|
||||
@@ -56,7 +55,7 @@ public class DataSource {
|
||||
*
|
||||
* @param observer the observer
|
||||
*/
|
||||
public void unregisterContentObserver(ContentObserver observer) {
|
||||
void unregisterContentObserver(ContentObserver observer) {
|
||||
contentResolver.unregisterContentObserver(observer);
|
||||
}
|
||||
|
||||
@@ -65,7 +64,7 @@ public class DataSource {
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
|
||||
@@ -74,8 +73,7 @@ public class DataSource {
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void unregisterOnSharedPreferenceChangeListener(
|
||||
OnSharedPreferenceChangeListener listener) {
|
||||
void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,19 @@ import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Data source manager. Creates observers/listeners and manages their
|
||||
* registration with {@link DataSource}. The observers/listeners calls
|
||||
* {@link DataSourceListener} when data changes.
|
||||
* Creates observers/listeners and manages their registration with {@link DataSource}.
|
||||
* The observers/listeners calls {@link DataSourceListener} when data changes.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class DataSourceManager {
|
||||
class DataSourceManager {
|
||||
|
||||
private static final String TAG = DataSourceManager.class.getSimpleName();
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final DataSourceListener dataSourceListener;
|
||||
|
||||
// Registered listeners
|
||||
private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
|
||||
private final Handler handler;
|
||||
@@ -44,7 +46,8 @@ public class DataSourceManager {
|
||||
private final WaypointsTableObserver waypointsTableObserver;
|
||||
private final TrackPointsTableObserver trackPointsTableObserver;
|
||||
private final PreferenceListener preferenceListener;
|
||||
public DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
|
||||
|
||||
DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
|
||||
this.dataSource = dataSource;
|
||||
this.dataSourceListener = dataSourceListener;
|
||||
|
||||
@@ -60,7 +63,7 @@ public class DataSourceManager {
|
||||
*
|
||||
* @param listeners the listeners
|
||||
*/
|
||||
public void updateListeners(EnumSet<TrackDataType> listeners) {
|
||||
void updateListeners(EnumSet<TrackDataType> listeners) {
|
||||
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
|
||||
|
||||
/*
|
||||
@@ -155,7 +158,7 @@ public class DataSourceManager {
|
||||
/**
|
||||
* Unregisters all listeners with data source.
|
||||
*/
|
||||
public void unregisterAllListeners() {
|
||||
void unregisterAllListeners() {
|
||||
for (TrackDataType trackDataType : TrackDataType.values()) {
|
||||
unregisterListener(trackDataType);
|
||||
}
|
||||
@@ -168,7 +171,7 @@ public class DataSourceManager {
|
||||
*/
|
||||
private class TracksTableObserver extends ContentObserver {
|
||||
|
||||
public TracksTableObserver() {
|
||||
TracksTableObserver() {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@@ -185,7 +188,7 @@ public class DataSourceManager {
|
||||
*/
|
||||
private class WaypointsTableObserver extends ContentObserver {
|
||||
|
||||
public WaypointsTableObserver() {
|
||||
WaypointsTableObserver() {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@@ -202,7 +205,7 @@ public class DataSourceManager {
|
||||
*/
|
||||
private class TrackPointsTableObserver extends ContentObserver {
|
||||
|
||||
public TrackPointsTableObserver() {
|
||||
TrackPointsTableObserver() {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,8 @@ package de.dennisguse.opentracks.content;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* An interface for an object that can generate descriptions of track and
|
||||
* waypoint.
|
||||
* An interface for an object that can generate descriptions of track and waypoint.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
@@ -31,12 +28,9 @@ public interface DescriptionGenerator {
|
||||
* Generates a track description.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
String generateTrackDescription(
|
||||
Track track, Vector<Double> distances, Vector<Double> elevations, boolean html);
|
||||
String generateTrackDescription(Track track, boolean html);
|
||||
|
||||
/**
|
||||
* Generate a waypoint description from a trip statistics.
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
@@ -44,7 +43,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
||||
}
|
||||
|
||||
@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 lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@@ -97,8 +96,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// Total distance
|
||||
writeDistance(
|
||||
stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
|
||||
writeDistance(stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
|
||||
|
||||
// Total time
|
||||
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);
|
||||
|
||||
// Average moving speed
|
||||
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed,
|
||||
lineBreak);
|
||||
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, lineBreak);
|
||||
|
||||
// Max speed
|
||||
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
|
||||
|
||||
// Average pace
|
||||
writePace(
|
||||
stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
|
||||
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
|
||||
|
||||
// Average moving pace
|
||||
writePace(stats.getAverageMovingSpeed(), builder,
|
||||
R.string.description_average_moving_pace_in_minute, lineBreak);
|
||||
writePace(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_pace_in_minute, lineBreak);
|
||||
|
||||
// Fastest pace
|
||||
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
|
||||
@@ -134,8 +129,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
||||
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
|
||||
|
||||
// Elevation gain
|
||||
writeElevation(
|
||||
stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||
|
||||
// Max grade
|
||||
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);
|
||||
|
||||
// Recorded time
|
||||
builder.append(
|
||||
context.getString(R.string.description_recorded_time,
|
||||
StringUtils.formatDateTime(context, stats.getStartTime())));
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
|
||||
builder.append(lineBreak);
|
||||
|
||||
return builder.toString();
|
||||
@@ -191,8 +183,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeSpeed(
|
||||
double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
|
||||
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
|
||||
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
|
||||
@@ -209,18 +200,13 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
String[] paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
|
||||
String[] paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
|
||||
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
|
||||
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
|
||||
|
||||
if (paceInMetrics[0] == null) {
|
||||
paceInMetrics[0] = context.getString(R.string.value_unknown);
|
||||
}
|
||||
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
|
||||
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
|
||||
|
||||
if (paceInImperial[0] == null) {
|
||||
paceInImperial[0] = context.getString(R.string.value_unknown);
|
||||
}
|
||||
|
||||
builder.append(context.getString(resId, paceInMetrics[0], paceInImperial[0]));
|
||||
builder.append(context.getString(resId, formattedPaceMetrics, formattedPaceImperial));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
|
||||
@@ -233,8 +219,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeElevation(
|
||||
double elevation, StringBuilder builder, int resId, String lineBreak) {
|
||||
void writeElevation(double elevation, StringBuilder builder, int resId, String lineBreak) {
|
||||
long elevationInM = Math.round(elevation);
|
||||
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
|
||||
builder.append(context.getString(resId, elevationInM, elevationInFt));
|
||||
@@ -251,8 +236,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) {
|
||||
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L
|
||||
: Math.round(grade * 100);
|
||||
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L : Math.round(grade * 100);
|
||||
builder.append(context.getString(resId, gradeInPercent));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
|
||||
@@ -20,17 +20,18 @@ import android.location.Location;
|
||||
|
||||
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.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.SortedSet;
|
||||
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.
|
||||
*
|
||||
@@ -97,34 +98,35 @@ public class SearchEngine {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR =
|
||||
new Comparator<ScoredResult>() {
|
||||
@Override
|
||||
public int compare(ScoredResult r1, ScoredResult r2) {
|
||||
// Score ordering.
|
||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||
if (scoreDiff != 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = new Comparator<ScoredResult>() {
|
||||
@Override
|
||||
public int compare(ScoredResult r1, ScoredResult r2) {
|
||||
// Score ordering.
|
||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||
if (scoreDiff != 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
// Make tracks come before waypoints.
|
||||
if (r1.waypoint != null && r2.track != null) {
|
||||
return 1;
|
||||
} else if (r1.track != null && r2.waypoint != null) {
|
||||
return -1;
|
||||
}
|
||||
// Make tracks come before waypoints.
|
||||
if (r1.waypoint != null && r2.track != null) {
|
||||
return 1;
|
||||
} else if (r1.track != null && r2.waypoint != null) {
|
||||
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;
|
||||
|
||||
public SearchEngine(ContentProviderUtils providerUtils) {
|
||||
@@ -134,26 +136,22 @@ public class SearchEngine {
|
||||
/**
|
||||
* Squashes a number by calculating 1 / log (1 + x).
|
||||
*/
|
||||
//TODO Why is this done?
|
||||
private static double squash(double 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
|
||||
* @return a set of results, sorted according to their score
|
||||
*/
|
||||
public SortedSet<ScoredResult> search(SearchQuery query) {
|
||||
ArrayList<Track> tracks = new ArrayList<>();
|
||||
ArrayList<Waypoint> waypoints = new ArrayList<>();
|
||||
TreeSet<ScoredResult> scoredResults = new TreeSet<>(SCORED_RESULT_COMPARATOR);
|
||||
|
||||
retrieveTracks(query, tracks);
|
||||
retrieveWaypoints(query, waypoints);
|
||||
|
||||
scoreTrackResults(tracks, query, scoredResults);
|
||||
scoreWaypointResults(waypoints, query, scoredResults);
|
||||
scoreTrackResults(retrieveTracks(query), query, scoredResults);
|
||||
scoreWaypointResults(retrieveWaypoints(query), query, scoredResults);
|
||||
|
||||
return scoredResults;
|
||||
}
|
||||
@@ -161,18 +159,19 @@ public class SearchEngine {
|
||||
/**
|
||||
* Retrieves tracks matching the given query from the database.
|
||||
*
|
||||
* @param query the query to retrieve for
|
||||
* @param tracks list to fill with the resulting tracks
|
||||
* @param query the query to retrieve for
|
||||
*/
|
||||
private void retrieveTracks(SearchQuery query, ArrayList<Track> tracks) {
|
||||
private List<Track> retrieveTracks(SearchQuery query) {
|
||||
ArrayList<Track> tracks = new ArrayList<>();
|
||||
|
||||
String queryLikeSelection = "%" + query.textQuery + "%";
|
||||
String[] trackSelectionArgs = new String[]{
|
||||
queryLikeSelection,
|
||||
queryLikeSelection,
|
||||
queryLikeSelection};
|
||||
queryLikeSelection
|
||||
};
|
||||
|
||||
try (Cursor cursor = providerUtils.getTrackCursor(
|
||||
TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) {
|
||||
try (Cursor cursor = providerUtils.getTrackCursor(TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) {
|
||||
if (cursor != null) {
|
||||
tracks.ensureCapacity(cursor.getCount());
|
||||
while (cursor.moveToNext()) {
|
||||
@@ -180,22 +179,26 @@ public class SearchEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves waypoints matching the given query from the database.
|
||||
*
|
||||
* @param query the query to retrieve for
|
||||
* @param waypoints list to fill with the resulting waypoints
|
||||
* @param query the query to retrieve for
|
||||
*/
|
||||
private void retrieveWaypoints(SearchQuery query, ArrayList<Waypoint> waypoints) {
|
||||
private List<Waypoint> retrieveWaypoints(SearchQuery query) {
|
||||
ArrayList<Waypoint> waypoints = new ArrayList<>();
|
||||
|
||||
String queryLikeSelection2 = "%" + query.textQuery + "%";
|
||||
String[] waypointSelectionArgs = new String[]{
|
||||
queryLikeSelection2,
|
||||
queryLikeSelection2,
|
||||
queryLikeSelection2};
|
||||
try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs,
|
||||
WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) {
|
||||
queryLikeSelection2
|
||||
};
|
||||
|
||||
try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs, WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) {
|
||||
if (cursor != null) {
|
||||
waypoints.ensureCapacity(cursor.getCount());
|
||||
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());
|
||||
|
||||
TripStatistics statistics = track.getTripStatistics();
|
||||
// TODO: Also boost for proximity to the currently-centered position on the map.
|
||||
score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude());
|
||||
|
||||
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
|
||||
@@ -281,7 +285,6 @@ public class SearchEngine {
|
||||
|
||||
Location location = waypoint.getLocation();
|
||||
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 *= getTimeBoost(query, location.getTime());
|
||||
|
||||
@@ -302,8 +305,7 @@ public class SearchEngine {
|
||||
* @param category the category of the track or waypoint
|
||||
* @return the total boost to be applied to the result
|
||||
*/
|
||||
private double getTitleBoost(SearchQuery query,
|
||||
String name, String description, String category) {
|
||||
private double getTitleBoost(SearchQuery query, String name, String description, String category) {
|
||||
// Title boost: track name > description > category.
|
||||
double boost = 1.0;
|
||||
if (name.toLowerCase(Locale.getDefault()).contains(query.textQuery)) {
|
||||
@@ -332,8 +334,7 @@ public class SearchEngine {
|
||||
}
|
||||
|
||||
// Score recent tracks higher.
|
||||
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S
|
||||
* UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
|
||||
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
|
||||
if (timeAgoHours > 0L) {
|
||||
return squash(timeAgoHours);
|
||||
} else {
|
||||
@@ -364,6 +365,7 @@ public class SearchEngine {
|
||||
|
||||
// Score tracks close to the current location higher.
|
||||
double distanceKm = distanceResults[0] * UnitConversions.M_TO_KM;
|
||||
|
||||
if (distanceKm > 0.0) {
|
||||
// Use the inverse of the amortized distance.
|
||||
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 final String textQuery;
|
||||
public final Location currentLocation;
|
||||
public final long currentTrackId;
|
||||
public final long currentTimestamp;
|
||||
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId,
|
||||
long currentTimestamp) {
|
||||
final String textQuery;
|
||||
final Location currentLocation;
|
||||
final long currentTrackId;
|
||||
final long currentTimestamp;
|
||||
|
||||
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, long currentTimestamp) {
|
||||
this.textQuery = textQuery.toLowerCase(Locale.getDefault());
|
||||
this.currentLocation = currentLocation;
|
||||
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 final Track track;
|
||||
public final Waypoint waypoint;
|
||||
public final double score;
|
||||
final double score;
|
||||
|
||||
ScoredResult(Track track, double score) {
|
||||
this.track = track;
|
||||
this.waypoint = null;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
ScoredResult(Waypoint waypoint, double score) {
|
||||
this.track = null;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
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.List;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
/**
|
||||
* A track.
|
||||
@@ -30,82 +29,29 @@ import java.util.ArrayList;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @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 String name = "";
|
||||
private String description = "";
|
||||
private String category = "";
|
||||
private long startId = -1L;
|
||||
private long stopId = -1L;
|
||||
|
||||
/*
|
||||
* The number of location points (present even if the points themselves are
|
||||
* not loaded)
|
||||
* The number of location points (present even if the points themselves are not loaded)
|
||||
*/
|
||||
private int numberOfPoints = 0;
|
||||
private String icon = "";
|
||||
private long modifiedTime = -1L;
|
||||
|
||||
private TripStatistics tripStatistics = new TripStatistics();
|
||||
|
||||
// Location points (which may not have been loaded)
|
||||
private ArrayList<Location> locations = new ArrayList<>();
|
||||
private List<Location> locations = new ArrayList<>();
|
||||
|
||||
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() {
|
||||
return id;
|
||||
}
|
||||
@@ -170,14 +116,6 @@ public class Track implements Parcelable {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public long getModifiedTime() {
|
||||
return modifiedTime;
|
||||
}
|
||||
|
||||
public void setModifiedTime(long modifiedTime) {
|
||||
this.modifiedTime = modifiedTime;
|
||||
}
|
||||
|
||||
public TripStatistics getTripStatistics() {
|
||||
return tripStatistics;
|
||||
}
|
||||
@@ -190,7 +128,7 @@ public class Track implements Parcelable {
|
||||
locations.add(location);
|
||||
}
|
||||
|
||||
public ArrayList<Location> getLocations() {
|
||||
public List<Location> getLocations() {
|
||||
return locations;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,24 +35,25 @@ import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Track data hub. Receives data from {@link DataSource} and distributes it to
|
||||
* {@link TrackDataListener} after some processing.
|
||||
* Track data hub. Receives data from {@link DataSource} and distributes it to {@link TrackDataListener} after some processing.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackDataHub implements DataSourceListener {
|
||||
|
||||
/**
|
||||
* Target number of track points displayed by the map overlay. We may display
|
||||
* more than this number of points.
|
||||
* Target number of track points displayed by the map overlay.
|
||||
* 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.
|
||||
*/
|
||||
@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 final Context context;
|
||||
private final TrackDataManager trackDataManager;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
@@ -87,8 +88,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
* @param targetNumPoints the target number of points
|
||||
*/
|
||||
@VisibleForTesting
|
||||
TrackDataHub(Context context, TrackDataManager trackDataManager,
|
||||
ContentProviderUtils contentProviderUtils, int targetNumPoints) {
|
||||
private TrackDataHub(Context context, TrackDataManager trackDataManager, ContentProviderUtils contentProviderUtils, int targetNumPoints) {
|
||||
this.context = context;
|
||||
this.trackDataManager = trackDataManager;
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
@@ -96,17 +96,11 @@ public class TrackDataHub implements DataSourceListener {
|
||||
resetSamplingState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public synchronized static TrackDataHub newInstance(Context context) {
|
||||
return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get(
|
||||
context), TARGET_DISPLAYED_TRACK_POINTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts.
|
||||
*/
|
||||
public void start() {
|
||||
if (started) {
|
||||
Log.i(TAG, "TrackDataHub already started, ignoring start.");
|
||||
@@ -131,9 +125,6 @@ public class TrackDataHub implements DataSourceListener {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops.
|
||||
*/
|
||||
public void stop() {
|
||||
if (!started) {
|
||||
Log.i(TAG, "TrackDataHub not started, ignoring stop.");
|
||||
@@ -151,11 +142,6 @@ public class TrackDataHub implements DataSourceListener {
|
||||
dataSourceManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a track.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
public void loadTrack(final long trackId) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
@@ -222,8 +208,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
* Returns true if the selected track is recording.
|
||||
*/
|
||||
public boolean isSelectedTrackRecording() {
|
||||
return selectedTrackId == recordingTrackId
|
||||
&& recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
return selectedTrackId == recordingTrackId && PreferencesUtils.isRecording(recordingTrackId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,8 +243,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
notifyTrackPointsTableUpdate(
|
||||
true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
|
||||
notifyTrackPointsTableUpdate(true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
|
||||
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
||||
}
|
||||
});
|
||||
@@ -270,47 +254,36 @@ public class TrackDataHub implements DataSourceListener {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (key == null
|
||||
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
|
||||
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(
|
||||
context, R.string.recording_track_paused_key,
|
||||
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
|
||||
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
if (key != null) {
|
||||
for (TrackDataListener trackDataListener :
|
||||
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
if (trackDataListener.onMetricUnitsChanged(metricUnits)) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||
if (key != null) {
|
||||
for (TrackDataListener trackDataListener :
|
||||
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
if (trackDataListener.onReportSpeedChanged(reportSpeed)) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key,
|
||||
PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
||||
if (key != null) {
|
||||
for (TrackDataListener trackDataListener :
|
||||
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -386,8 +359,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
if (isOnlyListener) {
|
||||
resetSamplingState();
|
||||
}
|
||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners
|
||||
: Collections.<TrackDataListener>emptySet();
|
||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.<TrackDataListener>emptySet();
|
||||
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
|
||||
*/
|
||||
@@ -412,9 +384,8 @@ public class TrackDataHub implements DataSourceListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies waypoint table update. Currently, reloads all the waypoints up to
|
||||
* {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler}
|
||||
* thread.
|
||||
* Notifies waypoint table update.
|
||||
* Currently, reloads all the waypoints up to {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler} thread.
|
||||
*
|
||||
* @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}
|
||||
* thread.
|
||||
* Notifies track points table update; to be run in the {@link #handler} thread.
|
||||
*
|
||||
* @param updateSamplingState true to update the sampling state
|
||||
* @param sampledInListeners the sampled-in listeners
|
||||
* @param sampledOutListeners the sampled-out listeners
|
||||
*/
|
||||
private void notifyTrackPointsTableUpdate(boolean updateSamplingState,
|
||||
Set<TrackDataListener> sampledInListeners, Set<TrackDataListener> sampledOutListeners) {
|
||||
private void notifyTrackPointsTableUpdate(boolean updateSamplingState, Set<TrackDataListener> sampledInListeners, Set<TrackDataListener> sampledOutListeners) {
|
||||
if (sampledInListeners.isEmpty() && sampledOutListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateSamplingState && numLoadedPoints >= targetNumPoints) {
|
||||
// Reload and resample the track at a lower frequency.
|
||||
Log.i(TAG, "Resampling track after " + numLoadedPoints + " points.");
|
||||
@@ -477,11 +447,8 @@ public class TrackDataHub implements DataSourceListener {
|
||||
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
||||
int samplingFrequency = -1;
|
||||
boolean includeNextPoint = false;
|
||||
LocationIterator locationIterator = null;
|
||||
|
||||
try {
|
||||
locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId,
|
||||
localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
||||
|
||||
while (locationIterator.hasNext()) {
|
||||
Location location = locationIterator.next();
|
||||
@@ -509,8 +476,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
}
|
||||
} else {
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0)
|
||||
|| (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
includeNextPoint = false;
|
||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||
trackDataListener.onSampledInTrackPoint(location);
|
||||
@@ -525,10 +491,6 @@ public class TrackDataHub implements DataSourceListener {
|
||||
localNumLoadedPoints++;
|
||||
localLastSeenLocationId = locationId;
|
||||
}
|
||||
} finally {
|
||||
if (locationIterator != null) {
|
||||
locationIterator.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (updateSamplingState) {
|
||||
@@ -555,7 +517,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
* Creates a {@link DataSource}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected DataSource newDataSource() {
|
||||
private DataSource newDataSource() {
|
||||
return new DataSource(context);
|
||||
}
|
||||
|
||||
@@ -565,7 +527,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
* @param runnable the runnable
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected void runInHandlerThread(Runnable runnable) {
|
||||
private void runInHandlerThread(Runnable runnable) {
|
||||
if (handler == null) {
|
||||
// Use a Throwable to ensure the stack trace is logged.
|
||||
Log.d(TAG, "handler is null.", new Throwable());
|
||||
|
||||
@@ -57,10 +57,8 @@ public interface TrackDataListener {
|
||||
void onSegmentSplit(Location location);
|
||||
|
||||
/**
|
||||
* Called when finish sending new track points. This gets called after every
|
||||
* batch of calls to {@link #onSampledInTrackPoint(Location)},
|
||||
* {@link #onSampledOutTrackPoint(Location)} and
|
||||
* {@link #onSegmentSplit(Location)}.
|
||||
* Called when finish sending new track points.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)}, {@link #onSampledOutTrackPoint(Location)} and {@link #onSegmentSplit(Location)}.
|
||||
*/
|
||||
void onNewTrackPointsDone();
|
||||
|
||||
@@ -77,9 +75,8 @@ public interface TrackDataListener {
|
||||
void onNewWaypoint(Waypoint waypoint);
|
||||
|
||||
/**
|
||||
* Called when finish sending new waypoints. This gets called after every
|
||||
* batch of calls to {@link #clearWaypoints()} and
|
||||
* {@link #onNewWaypoint(Waypoint)}.
|
||||
* Called when finish sending new waypoints.
|
||||
* This gets called after every batch of calls to {@link #clearWaypoints()} and {@link #onNewWaypoint(Waypoint)}.
|
||||
*/
|
||||
void onNewWaypointsDone();
|
||||
|
||||
|
||||
@@ -26,24 +26,19 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Manages register/unregister {@link TrackDataListener} and keeping the state
|
||||
* for each registered listener.
|
||||
* Manages register/unregister {@link TrackDataListener} and keeping the state for each registered listener.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackDataManager {
|
||||
class TrackDataManager {
|
||||
|
||||
private static final String TAG = TrackDataManager.class.getSimpleName();
|
||||
|
||||
// Map of listener to its track data types
|
||||
private final Map<TrackDataListener, EnumSet<TrackDataType>>
|
||||
listenerToTypesMap = new HashMap<>();
|
||||
private final Map<TrackDataListener, EnumSet<TrackDataType>> listenerToTypesMap = new HashMap<>();
|
||||
|
||||
// Map of track data type to listeners
|
||||
private final Map<TrackDataType, Set<TrackDataListener>>
|
||||
typeToListenersMap = new EnumMap<>(TrackDataType.class);
|
||||
private final Map<TrackDataType, Set<TrackDataListener>> typeToListenersMap = new EnumMap<>(TrackDataType.class);
|
||||
|
||||
public TrackDataManager() {
|
||||
TrackDataManager() {
|
||||
for (TrackDataType trackDataType : TrackDataType.values()) {
|
||||
typeToListenersMap.put(trackDataType, new LinkedHashSet<TrackDataListener>());
|
||||
}
|
||||
@@ -55,8 +50,7 @@ public class TrackDataManager {
|
||||
* @param listener the listener
|
||||
* @param trackDataTypes the track data types the listener is interested
|
||||
*/
|
||||
public void registerListener(
|
||||
TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
|
||||
void registerListener(TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
|
||||
if (listenerToTypesMap.containsKey(listener)) {
|
||||
Log.w(TAG, "Tried to register a listener that is already registered. Ignore.");
|
||||
return;
|
||||
@@ -72,7 +66,7 @@ public class TrackDataManager {
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void unregisterListener(TrackDataListener listener) {
|
||||
void unregisterListener(TrackDataListener listener) {
|
||||
EnumSet<TrackDataType> removedTypes = listenerToTypesMap.remove(listener);
|
||||
if (removedTypes == null) {
|
||||
Log.w(TAG, "Tried to unregister a listener that is not registered. Ignore.");
|
||||
@@ -88,7 +82,7 @@ public class TrackDataManager {
|
||||
/**
|
||||
* Gets the number of {@link TrackDataListener}.
|
||||
*/
|
||||
public int getNumberOfListeners() {
|
||||
int getNumberOfListeners() {
|
||||
return listenerToTypesMap.size();
|
||||
}
|
||||
|
||||
@@ -97,7 +91,7 @@ public class TrackDataManager {
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public EnumSet<TrackDataType> getTrackDataTypes(TrackDataListener listener) {
|
||||
EnumSet<TrackDataType> getTrackDataTypes(TrackDataListener listener) {
|
||||
return listenerToTypesMap.get(listener);
|
||||
}
|
||||
|
||||
@@ -106,14 +100,14 @@ public class TrackDataManager {
|
||||
*
|
||||
* @param type the type
|
||||
*/
|
||||
public Set<TrackDataListener> getListeners(TrackDataType type) {
|
||||
Set<TrackDataListener> getListeners(TrackDataType type) {
|
||||
return typeToListenersMap.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the registered {@link TrackDataType}.
|
||||
*/
|
||||
public EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
|
||||
EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
|
||||
EnumSet<TrackDataType> types = EnumSet.noneOf(TrackDataType.class);
|
||||
for (EnumSet<TrackDataType> value : listenerToTypesMap.values()) {
|
||||
types.addAll(value);
|
||||
|
||||
@@ -19,8 +19,6 @@ package de.dennisguse.opentracks.content;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
|
||||
/**
|
||||
* Constants for the track points table.
|
||||
*
|
||||
@@ -29,7 +27,7 @@ import de.dennisguse.opentracks.BuildConfig;
|
||||
public interface TrackPointsColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "trackpoints";
|
||||
Uri CONTENT_URI = Uri.parse("content://" + BuildConfig.APPLICATION_ID + "/trackpoints");
|
||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/trackpoints");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
|
||||
String DEFAULT_SORT_ORDER = "_id";
|
||||
|
||||
@@ -19,8 +19,6 @@ package de.dennisguse.opentracks.content;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
|
||||
/**
|
||||
* Constants for the tracks table.
|
||||
*
|
||||
@@ -29,25 +27,9 @@ import de.dennisguse.opentracks.BuildConfig;
|
||||
public interface TracksColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "tracks";
|
||||
|
||||
/**
|
||||
* Tracks provider uri.
|
||||
*/
|
||||
Uri CONTENT_URI = Uri.parse("content://" + BuildConfig.APPLICATION_ID + "/tracks");
|
||||
|
||||
/**
|
||||
* Track content type.
|
||||
*/
|
||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/tracks");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
|
||||
|
||||
/**
|
||||
* Track id content type.
|
||||
*/
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
|
||||
|
||||
/**
|
||||
* Tracks table default sort order.
|
||||
*/
|
||||
String DEFAULT_SORT_ORDER = "_id";
|
||||
|
||||
// Columns
|
||||
|
||||
@@ -18,8 +18,6 @@ package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
@@ -29,19 +27,8 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public final class Waypoint implements Parcelable {
|
||||
public final class Waypoint {
|
||||
|
||||
public static final Parcelable.Creator<Waypoint> CREATOR = new Parcelable.Creator<Waypoint>() {
|
||||
@Override
|
||||
public Waypoint createFromParcel(Parcel in) {
|
||||
return new Waypoint(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Waypoint[] newArray(int size) {
|
||||
return new Waypoint[size];
|
||||
}
|
||||
};
|
||||
private long id = -1L;
|
||||
private String name = "";
|
||||
private String description = "";
|
||||
@@ -60,9 +47,7 @@ public final class Waypoint implements Parcelable {
|
||||
public Waypoint() {
|
||||
}
|
||||
|
||||
public Waypoint(String name, String description, String category, String icon, long trackId,
|
||||
WaypointType type, double length, long duration, long startId, long stopId, Location location,
|
||||
TripStatistics tripStatistics, String photoUrl) {
|
||||
public Waypoint(String name, String description, String category, String icon, long trackId, WaypointType type, double length, long duration, long startId, long stopId, Location location, TripStatistics tripStatistics, String photoUrl) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
@@ -78,60 +63,6 @@ public final class Waypoint implements Parcelable {
|
||||
this.photoUrl = photoUrl;
|
||||
}
|
||||
|
||||
private Waypoint(Parcel source) {
|
||||
id = source.readLong();
|
||||
name = source.readString();
|
||||
description = source.readString();
|
||||
category = source.readString();
|
||||
icon = source.readString();
|
||||
trackId = source.readLong();
|
||||
type = WaypointType.values()[source.readInt()];
|
||||
length = source.readDouble();
|
||||
duration = source.readLong();
|
||||
startId = source.readLong();
|
||||
stopId = source.readLong();
|
||||
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
byte hasLocation = source.readByte();
|
||||
if (hasLocation > 0) {
|
||||
location = source.readParcelable(classLoader);
|
||||
}
|
||||
byte hasStats = source.readByte();
|
||||
if (hasStats > 0) {
|
||||
tripStatistics = source.readParcelable(classLoader);
|
||||
}
|
||||
photoUrl = source.readString();
|
||||
}
|
||||
|
||||
@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.writeString(icon);
|
||||
dest.writeLong(trackId);
|
||||
dest.writeInt(type.ordinal());
|
||||
dest.writeDouble(length);
|
||||
dest.writeLong(duration);
|
||||
dest.writeLong(startId);
|
||||
dest.writeLong(stopId);
|
||||
dest.writeByte(location == null ? (byte) 0 : (byte) 1);
|
||||
if (location != null) {
|
||||
dest.writeParcelable(location, 0);
|
||||
}
|
||||
dest.writeByte(tripStatistics == null ? (byte) 0 : (byte) 1);
|
||||
if (tripStatistics != null) {
|
||||
dest.writeParcelable(tripStatistics, 0);
|
||||
}
|
||||
dest.writeString(photoUrl);
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -228,6 +159,10 @@ public final class Waypoint implements Parcelable {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public boolean isTripStatistics() {
|
||||
return tripStatistics != null;
|
||||
}
|
||||
|
||||
public TripStatistics getTripStatistics() {
|
||||
return tripStatistics;
|
||||
}
|
||||
|
||||