Compare commits

..

77 Commits

Author SHA1 Message Date
Dennis Guse 7c0069394f Release v3.1.0 2019-09-02 19:42:40 +02:00
Dennis Guse 379913fb3e Cleanup. 2019-09-02 19:39:58 +02:00
Dennis Guse 542d9ccb77 Request storage permission on import all and export all. 2019-09-02 19:37:55 +02:00
Dennis Guse c8ae3b83b5 Added option to show statistics while recording without unlocking the device. 2019-09-02 18:36:31 +02:00
Dennis Guse 0a046fc100 Cleanup. 2019-09-02 18:02:43 +02:00
Dennis Guse c551a38a8b Show full version name based upon git describe --tags on startup and also in AboutActivity. 2019-09-02 16:05:24 +02:00
Dennis Guse 72b1ca82ed Moved debug warnings into Startup. 2019-09-02 15:50:35 +02:00
Dennis Guse 1ef81c45db Build: do not minify code for release.
The amount of space saved is too small to be of a real benefit while stack traces become unreadable.
2019-09-02 15:50:35 +02:00
Dennis Guse 08e6ae76f2 Fix TravisCI for Android-29 (#34) 2019-09-02 15:44:21 +02:00
Dennis Guse 6e410e049d Bugfix: creating waypoint from camera works now.
Introduced in commit 677b691ef1
2019-08-26 22:57:00 +02:00
Dennis Guse 22b7962940 Upgraded to Gradle 5.6 and AndroidStudio 3.5.0. 2019-08-26 21:55:40 +02:00
Dennis Guse 5788f99b20 Catch ActivityNotFound exception for starting geo-data intents. 2019-08-25 22:20:08 +02:00
Dennis Guse 8685e43dc2 Fixed ShareContentProvider and KMZ mime type. 2019-08-25 21:37:47 +02:00
Dennis Guse b8bea27155 Added AndroidStudio coding style configuration. 2019-08-25 16:21:20 +02:00
Dennis Guse c9226d3b79 Fixed CustomContentProvider tests. 2019-08-25 16:20:27 +02:00
Dennis Guse 9701feff04 Removed duplicate registration of images at MediaScanner. 2019-08-25 08:42:36 +02:00
Dennis Guse b8b40a88ef Show Marker menu entry at defined position. 2019-08-25 08:41:11 +02:00
Dennis Guse adcf7b388b Finish MarkerDetailActivity after storing marker. 2019-08-25 00:26:04 +02:00
Dennis Guse 3a7562d96c Modified "show on map" functionality to only share locations and no other track data. 2019-08-25 00:21:36 +02:00
Dennis Guse 2753cf7d0b Removed search suggestion configuration. 2019-08-24 18:48:50 +02:00
Dennis Guse 5841415a0b Refactored TrackWriter instantiation. 2019-08-24 18:14:32 +02:00
Dennis Guse d79fa07834 Share multiple tracks from TrackListActivity. 2019-08-24 17:51:57 +02:00
Dennis Guse ff6caf706e Added show on map to TrackDetailActivity. 2019-08-24 17:51:57 +02:00
Dennis Guse c953fbc67a Implemented "Share" from TrackListActivity's context menu. 2019-08-24 09:37:38 +02:00
Dennis Guse 55870cca85 Bugfix: import/export all now react to back button. 2019-08-24 08:49:59 +02:00
Dennis Guse 0123242be6 Bugfix: use correct entries/values for track name. 2019-08-24 08:44:43 +02:00
Dennis Guse 81a6d7b53a Added release recipe for FDroid. 2019-08-24 08:39:13 +02:00
Dennis Guse f423b07cee Added Changelogs for v3.0.0, v3.0.1, and the upcoming release. 2019-08-24 08:27:31 +02:00
Dennis Guse 33231bdbb1 Renaming to AbstractListActivity. 2019-08-23 07:37:03 +02:00
Dennis Guse 677b691ef1 Moved special intent creation to IntentUtils. 2019-08-23 07:33:58 +02:00
Dennis Guse 5e74e80a9a Use correct mime type for KML and KMZ. 2019-08-23 07:19:40 +02:00
Dennis Guse 70b5d799a2 Share marker works everywhere and share track from TrackDetailActivity. 2019-08-23 07:01:14 +02:00
Dennis Guse 16f18fe16f Removed search suggestion provider as it only provided recent searches. 2019-08-21 22:25:16 +02:00
Dennis Guse 8538c79dd8 Extracted content uri definition. 2019-08-21 22:20:18 +02:00
Dennis Guse f8e3b28e51 Removed landscape mode for TrackDetailActivity as it was broken and portrait mode needs heavy refactoring first. 2019-08-21 07:18:57 +02:00
Dennis Guse 9f665e07fd Show on map: everything is handled by one content provider alone.
To avoid issues forwarding a requests from (temporarily) exported content provider to internal content provider only one content provider is used.
2019-08-20 22:55:32 +02:00
Dennis Guse b7c010c086 Show on map: draft via virtual file (with two content providers).
Exported content provider that temporary needs to grant access to the internal content provider.
2019-08-20 22:49:54 +02:00
Dennis Guse 8296c0194d Show on map: draft via sharing temporary file. 2019-08-20 22:48:05 +02:00
Dennis Guse f537cc7321 Cleanup: automatic code inspection via Android Studio. 2019-08-18 14:46:43 +02:00
Dennis Guse 4fa2401b27 Removed CustomContentProvider.openFile() as it was used for Google Earth integration. 2019-08-18 13:22:41 +02:00
Dennis Guse f30e3cd5cd Removed contentDescription for logo (was still MyTracks). 2019-08-18 13:13:11 +02:00
Dennis Guse b73353120f PreferenceCategory import/export all: now uses Tracks as title. 2019-08-18 11:00:04 +02:00
Dennis Guse 7fa9df1f99 Cleanup. 2019-08-17 00:58:52 +02:00
Dennis Guse ea3de8a987 Renamed SensorManager to RemoteSensorManager. 2019-08-16 21:20:00 +02:00
Dennis Guse c159e1eba4 Version v3.0.1 2019-08-16 19:53:38 +02:00
Dennis Guse 420d538a73 Preferences: Import all/Export all has now transparent background and back button works as desired. 2019-08-16 18:53:26 +02:00
Dennis Guse 36672d6857 Fixed ChartFragmentTest. 2019-08-16 07:49:47 +02:00
Dennis Guse 51dc6f8e8d Tests: accept permission automatically. 2019-08-16 07:13:58 +02:00
Dennis Guse 9a6c9eeace Set android:allowBackup=false to prevent backups as this also includes cloud-based storages (e.g., Google Drive). 2019-08-15 23:14:52 +02:00
Dennis Guse bfb7f781ec Enabled TravisCI (just building, no tests). 2019-08-15 23:03:30 +02:00
Dennis Guse 8ca24b877c Moved Import all/Export all functionality to Settings. 2019-08-15 22:38:51 +02:00
Dennis Guse 01ef5cfa6f All test are now executable (but all are not working). 2019-08-14 18:03:48 +02:00
Dennis Guse 08825fcf04 Cleanup: typo. 2019-08-14 08:05:37 +02:00
Dennis Guse 4f5e87f87d Enabled some TrackDataHub tests. 2019-08-14 08:05:00 +02:00
Dennis Guse 45b1fafdd5 Fixed test and implementation of DescriptionGenerator. 2019-08-13 23:04:09 +02:00
Dennis Guse e89258c173 Fixed some tests by using the application context.
Fixed some tests by using the application context.

Fixed some tests by using the application context.

Fixed some tests by using the application context.
2019-08-13 22:49:41 +02:00
Dennis Guse d47d37afd2 Import/Exporter tests now compile (do not work). 2019-08-13 22:09:56 +02:00
Dennis Guse d9effc80a7 Enabled TrackRecordingServiceTest (mainly failing). 2019-08-13 20:59:29 +02:00
Dennis Guse 03d2b05ffd Allows SharedPreferences to contain int as well as String. 2019-08-13 20:56:07 +02:00
Dennis Guse e2cac6ee60 More tests executable (but not passing). 2019-08-12 18:47:48 +02:00
Dennis Guse f749ddb5ae Cleanup. 2019-08-12 18:47:48 +02:00
DanielJorn 6bb409fed0 Fixed typo in Readme.md 2019-08-11 09:25:53 +02:00
Dennis Guse 6da2d9d104 Export to KML and KMZ as requested by user. 2019-08-11 09:07:31 +02:00
Dennis Guse cd267123e7 KML export: write close tag for last segment.
Closes #21.
2019-08-10 14:00:38 +02:00
Dennis Guse 617ee719c8 Add heart rate sensor UI for landscape (BUG: still not visible). 2019-08-07 07:26:39 +02:00
Dennis Guse 4edad610ee Moved SensorSettingsActivity into SettingsActivity. 2019-08-06 20:07:22 +02:00
Dennis Guse fda95bbec9 Integrated ChartSettingsActivity in SettingsActivity. 2019-08-06 19:52:57 +02:00
Dennis Guse 1028e09888 Removed actionbar back button.
It was complicating the code base too much.
2019-08-06 19:46:01 +02:00
Dennis Guse 4abd07694c Use a custom dialog to reset preferences. 2019-08-06 19:08:11 +02:00
Dennis Guse a53b3288f7 BREAKING CHANGE: Moved to getDefaultSharedPreferences(). 2019-08-06 18:34:07 +02:00
Dennis Guse 7ba771c417 Moved sharedPreferences accessor to PreferenceUtils. 2019-08-06 18:18:25 +02:00
Dennis Guse 2a145b4791 Cleanup list preferences. 2019-08-06 07:47:37 +02:00
Dennis Guse cf90462772 PreferenceList: let summary be set by Android.
Some preferences used customized summaries - these were also removed.
2019-08-06 07:41:17 +02:00
Dennis Guse d9a6701260 Initialize preferences with default values. 2019-08-06 07:41:17 +02:00
Dennis Guse 1dc9558541 Use Androidx to get version code from manifest. 2019-08-05 22:21:44 +02:00
Dennis Guse 972de5f92e Photo size for KMZ exports is not configurable anymore. 2019-08-05 22:12:40 +02:00
Dennis Guse 28270b202e Hide activityTypeLabel in statistic summary. 2019-08-05 22:05:49 +02:00
200 changed files with 2961 additions and 3810 deletions
+116
View File
@@ -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>
+12
View File
@@ -0,0 +1,12 @@
language: android
dist: trusty
android:
components:
- build-tools-28.0.3
- android-29
script:
- ./gradlew build
#TODO: When all tests are passing, CI should also execute them.
#- ./gradlew connectedCheck
+12
View File
@@ -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_
8. Push the tagged to public repository
+2 -2
View File
@@ -16,7 +16,7 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
* __No advertising__
* __Privacy:__
* _contains in-app analytics_
* _does not Google Play Services_
* _does not have Google Play Services_
* _has no cloud integration_
__Only required permissions:__
@@ -28,7 +28,7 @@ To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://pl
## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/).
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
The [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
+25 -12
View File
@@ -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,21 +68,33 @@ android {
}
release {
minifyEnabled true
minifyEnabled false
}
}
applicationVariants.all { variant ->
variant.resValue "string", "applicationId", variant.applicationId
variant.resValue "string", "applicationId", variant.applicationId
}
//TODO: Should be removed (when LINT does not throw any errors).
lintOptions {
abortOnError false
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
testImplementation 'androidx.test:core:1.2.0'
}
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'org.mockito:mockito-android:3.0.0'
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.mockito:mockito-core:3.0.0'
}
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
#Wed Jun 19 22:47:37 CEST 2019
#Mon Aug 26 18:59:08 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
Vendored
+56 -44
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -6,42 +6,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 +24,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"'
# 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 +89,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
@@ -150,11 +154,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" "$@"
Vendored
+4 -10
View File
@@ -8,14 +8,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"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,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 +59,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
+15
View File
@@ -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)
+17
View File
@@ -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,12 @@
(tbd): OpenTracks
Changes:
- Added show track in map apps (tested with OsmAnd and Maps.ME)
- Removed landscape mode for track detail view (was buggy)
- Removed search suggestions history
Bugfix:
-
Developer:
-
+1 -1
View File
@@ -1 +1 @@
A sport tracker that completely respects your privacy
A sport tracker that completely respects your privacy.
@@ -24,14 +24,14 @@ import android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import de.dennisguse.opentracks.services.TrackRecordingService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.services.TrackRecordingService;
/**
* Tests for the BootReceiver.
*
@@ -41,22 +41,24 @@ import java.util.List;
public class BootReceiverTest {
private static final String SERVICE_NAME = "de.dennisguse.opentracks.services.TrackRecordingService";
private Context context = InstrumentationRegistry.getInstrumentation().getContext();
/**
* Tests the behavior when receive notification which is the phone boot.
*/
@Test
public void testOnReceive_startService() {
// Make sure no TrackRecordingService
Intent stopIntent = new Intent(InstrumentationRegistry.getInstrumentation().getContext(), TrackRecordingService.class);
InstrumentationRegistry.getInstrumentation().getContext().stopService(stopIntent);
Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME));
Intent stopIntent = new Intent(context, TrackRecordingService.class);
context.stopService(stopIntent);
Assert.assertFalse(isServiceExisted(context));
BootReceiver bootReceiver = new BootReceiver();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_BOOT_COMPLETED);
bootReceiver.onReceive(InstrumentationRegistry.getInstrumentation().getContext(), intent);
bootReceiver.onReceive(context, intent);
// Check if the service is started
Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME));
Assert.assertFalse(isServiceExisted(context));
}
/**
@@ -65,33 +67,32 @@ public class BootReceiverTest {
@Test
public void testOnReceive_noStartService() {
// Make sure no TrackRecordingService
Intent stopIntent = new Intent(InstrumentationRegistry.getInstrumentation().getContext(), TrackRecordingService.class);
InstrumentationRegistry.getInstrumentation().getContext().stopService(stopIntent);
Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME));
Intent stopIntent = new Intent(context, TrackRecordingService.class);
context.stopService(stopIntent);
Assert.assertFalse(isServiceExisted(context));
BootReceiver bootReceiver = new BootReceiver();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_BUG_REPORT);
bootReceiver.onReceive(InstrumentationRegistry.getInstrumentation().getContext(), intent);
bootReceiver.onReceive(context, intent);
// Check if the service is not started
Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME));
Assert.assertFalse(isServiceExisted(context));
}
/**
* Checks if a service is started in a context.
*
* @param context the context for checking a service
* @param serviceName the service name to find if existed
* @param context the context for checking a service
*/
private boolean isServiceExisted(Context context, String serviceName) {
private boolean isServiceExisted(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
for (int i = 0; i < serviceList.size(); i++) {
RunningServiceInfo serviceInfo = serviceList.get(i);
ComponentName componentName = serviceInfo.service;
if (componentName.getClassName().equals(serviceName)) {
if (componentName.getClassName().equals(SERVICE_NAME)) {
return true;
}
}
@@ -15,13 +15,12 @@
*/
package de.dennisguse.opentracks;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import de.dennisguse.opentracks.R;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
@@ -34,9 +33,9 @@ public class ChartValueSeriesTest {
private ChartValueSeries series;
@Before
protected void setUp() {
public void setUp() {
series = new ChartValueSeries(
InstrumentationRegistry.getInstrumentation().getContext(),
ApplicationProvider.getApplicationContext(),
Integer.MIN_VALUE,
Integer.MAX_VALUE,
new int[]{100, 1000},
@@ -46,6 +45,7 @@ public class ChartValueSeriesTest {
R.color.chart_elevation_border);
}
@Test
public void testInitialConditions() {
Assert.assertEquals(1, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue());
@@ -53,11 +53,13 @@ public class ChartValueSeriesTest {
Assert.assertTrue(series.isEnabled());
}
@Test
public void testEnabled() {
series.setEnabled(false);
Assert.assertFalse(series.isEnabled());
}
@Test
public void testSmallUpdates() {
series.update(0);
series.update(10);
@@ -67,6 +69,7 @@ public class ChartValueSeriesTest {
Assert.assertEquals(500, series.getMaxMarkerValue());
}
@Test
public void testBigUpdates() {
series.update(0);
series.update(901);
@@ -76,6 +79,7 @@ public class ChartValueSeriesTest {
Assert.assertEquals(5000, series.getMaxMarkerValue());
}
@Test
public void testNotZeroBasedUpdates() {
series.update(220);
series.update(250);
@@ -32,14 +32,14 @@ public class TrackStubUtils {
public static final double INITIAL_ALTITUDE = 22;
public static final long INITIAL_TIME = 1000L;
static final String LOCATION_PROVIDER = "gps";
static final double INITIAL_LATITUDE = 22;
static final double INITIAL_LONGITUDE = 22;
static final float INITIAL_ACCURACY = 5;
static final float INITIAL_SPEED = 10;
static final float INITIAL_BEARING = 3.0f;
private static final String LOCATION_PROVIDER = "gps";
private static final double INITIAL_LATITUDE = 22;
private static final double INITIAL_LONGITUDE = 22;
private static final float INITIAL_ACCURACY = 5;
private static final float INITIAL_SPEED = 10;
private static final float INITIAL_BEARING = 3.0f;
// Used to change the value of latitude, longitude, and altitude.
static final double DIFFERENCE = 0.01;
private static final double DIFFERENCE = 0.01;
/**
* Gets a a {@link Track} stub with specified number of locations.
@@ -71,8 +71,7 @@ public class TrackStubUtils {
*
* @return a SensorDataSetLocation stub.
*/
public static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude,
double altitude) {
public static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, double altitude) {
// Initial Location
Location loc = new Location(LOCATION_PROVIDER);
loc.setLatitude(latitude);
@@ -82,7 +81,7 @@ public class TrackStubUtils {
loc.setSpeed(INITIAL_SPEED);
loc.setTime(INITIAL_TIME);
loc.setBearing(INITIAL_BEARING);
SensorDataSet sd = new SensorDataSet(100, 100);
SensorDataSet sd = new SensorDataSet(Float.NaN, Float.NaN);
return new SensorDataSetLocation(loc, sd);
}
@@ -16,41 +16,43 @@
package de.dennisguse.opentracks.content;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import de.dennisguse.opentracks.content.CustomContentProvider.DatabaseHelper;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.junit.Test;
import de.dennisguse.opentracks.content.CustomContentProvider.DatabaseHelper;
/**
* Tests {@link CustomContentProvider}.
*
* @author Youtao Liu
*/
@RunWith(AndroidJUnit4.class)
public class CustomContentProviderTest {
private static final String DATABASE_NAME = "test.db";
private SQLiteDatabase db;
private CustomContentProvider customContentProvider;
private Context context = ApplicationProvider.getApplicationContext();
@Before
protected void setUp() {
InstrumentationRegistry.getInstrumentation().getContext().deleteDatabase(DATABASE_NAME);
db = (new DatabaseHelper(InstrumentationRegistry.getInstrumentation().getContext(), DATABASE_NAME)).getWritableDatabase();
customContentProvider = new CustomContentProvider();
public void setUp() {
context.deleteDatabase(DATABASE_NAME);
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
customContentProvider = new CustomContentProvider() {
};
}
/**
* Tests {@link CustomContentProvider.DatabaseHelper#onCreate(SQLiteDatabase)}.
*/
@Test
public void testDatabaseHelper_OnCreate() {
Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME));
Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME));
@@ -60,19 +62,19 @@ public class CustomContentProviderTest {
/**
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
*/
@Test
public void testOnCreate() {
Assert.assertTrue(customContentProvider.onCreate(InstrumentationRegistry.getInstrumentation().getContext()));
Assert.assertTrue(customContentProvider.onCreate(context));
}
/**
* Tests {@link CustomContentProvider#getType(Uri)}.
*/
@Test
public void testGetType() {
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
Assert.assertEquals(
TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI));
Assert.assertEquals(
WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI));
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
}
/**
@@ -117,7 +119,7 @@ public class CustomContentProviderTest {
try {
db.execSQL("SElECT count(*) from " + table + " order by " + column);
} catch (Exception e) {
if (e.getMessage().indexOf("no such column") > -1) {
if (e.getMessage().contains("no such column")) {
return false;
}
}
@@ -137,7 +139,7 @@ public class CustomContentProviderTest {
createTable(TrackPointsColumns.TABLE_NAME);
createTable(WaypointsColumns.TABLE_NAME);
DatabaseHelper databaseHelper = new DatabaseHelper(InstrumentationRegistry.getInstrumentation().getContext());
DatabaseHelper databaseHelper = new DatabaseHelper(context);
databaseHelper.onUpgrade(db, oldVersion, CustomContentProvider.DATABASE_VERSION);
}
}
@@ -20,27 +20,27 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.services.TrackRecordingServiceTest.MockContext;
import de.dennisguse.opentracks.stats.TripStatistics;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* A unit test for {@link ContentProviderUtilsImpl}.
@@ -48,37 +48,36 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author Bartlomiej Niechwiej
* @author Youtao Liu
*/
@RunWith(AndroidJUnit4.class)
@RunWith(MockitoJUnitRunner.class)
public class CustomContentProviderUtilsImplTest {
private static final String NAME_PREFIX = "test name";
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
private static final String TEST_DESC = "Test Desc!";
private static final String TEST_DESC_NEW = "Test Desc new!";
private Context context;
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;
@Before
protected void setUp() {
MockContentResolver mockContentResolver = new MockContentResolver();
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
getContext(), getContext(), "test.");
context = new MockContext(mockContentResolver, targetContext);
CustomContentProvider provider = new CustomContentProvider();
provider.attachInfo(context, null);
mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, provider);
setContext(context);
@Mock
private Cursor cursorMock;
@Mock
private ContentResolver contentResolverMock;
@Before
public void setUp() {
providerUtils = ContentProviderUtils.Factory.get(context);
providerUtils.deleteAllTracks(context);
}
@Test
public void testLocationIterator_noPoints() {
testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_customFactory() {
final Location location = new Location("test_location");
final AtomicInteger counter = new AtomicInteger();
@@ -93,46 +92,50 @@ public class CustomContentProviderUtilsImplTest {
Assert.assertEquals(15, counter.get());
}
@Test
public void testLocationIterator_nullFactory() {
try {
testIterator(1, 15, 4, false, null);
fail("Expecting IllegalArgumentException");
Assert.fail("Expecting IllegalArgumentException");
} catch (IllegalArgumentException e) {
// Expected.
}
}
@Test
public void testLocationIterator_noBatchAscending() {
testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_noBatchDescending() {
testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_batchDescending() {
testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
}
@Test
public void testLocationIterator_largeTrack() {
testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
}
private List<Location> testIterator(long trackId, int numPoints, int batchSize,
boolean descending, LocationFactory locationFactory) {
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
long lastPointId = initializeTrack(trackId, numPoints);
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
List<Location> locations = new ArrayList<Location>(numPoints);
LocationIterator it = providerUtils.getTrackPointLocationIterator(
trackId, -1L, descending, locationFactory);
LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory);
try {
while (it.hasNext()) {
Location loc = it.next();
@@ -172,8 +175,7 @@ public class CustomContentProviderUtilsImplTest {
// Load all inserted locations.
long lastPointId = -1;
int counter = 0;
LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false,
ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
try {
while (it.hasNext()) {
it.next();
@@ -194,37 +196,35 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}.
*/
@UsesMocks(Cursor.class)
@Test
public void testCreateTrack() {
Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class);
int startColumnIndex = 1;
int columnIndex = startColumnIndex;
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TracksColumns._ID))
.andReturn(columnIndex++);
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).andReturn(
columnIndex++);
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex++);
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex++);
columnIndex = startColumnIndex;
// Id
AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false);
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
// Name
AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false);
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
long trackId = System.currentTimeMillis();
columnIndex = startColumnIndex;
// Id
AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(trackId);
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
// Name
String name = NAME_PREFIX + trackId;
AndroidMock.expect(cursorMock.getString(columnIndex++)).andReturn(name);
AndroidMock.replay(cursorMock);
when(cursorMock.getString(columnIndex++)).thenReturn(name);
Track track = providerUtils.createTrack(cursorMock);
Assert.assertEquals(trackId, track.getId());
Assert.assertEquals(name, track.getName());
AndroidMock.verify(cursorMock);
verify(cursorMock);
}
/**
* Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)}
*/
@Test
public void testDeleteAllTracks() {
// Insert track, points and waypoint at first.
long trackId = System.currentTimeMillis();
@@ -233,32 +233,27 @@ public class CustomContentProviderUtilsImplTest {
Waypoint waypoint = new Waypoint();
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(1, 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(10, 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 all.
providerUtils.deleteAllTracks(context);
// Check whether all have 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(0, 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(0, 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());
}
/**
* Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}.
*/
@Test
public void testDeleteTrack() {
// Insert three tracks, points of two tracks and way point of one track.
long trackId = System.currentTimeMillis();
@@ -299,6 +294,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getAllTracks()}
*/
@Test
public void testGetAllTracks() {
int initialTrackNumber = providerUtils.getAllTracks().size();
long trackId = System.currentTimeMillis();
@@ -311,6 +307,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getLastTrack()}
*/
@Test
public void testGetLastTrack() {
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
@@ -320,6 +317,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getTrack(long)}
*/
@Test
public void testGetTrack() {
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
@@ -329,6 +327,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)}
*/
@Test
public void testUpdateTrack() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 0);
@@ -345,6 +344,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}.
*/
@Test
public void testCreateContentValues_waypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -380,8 +380,7 @@ public class CustomContentProviderUtilsImplTest {
waypoint.setLocation(loc);
providerUtils.insertWaypoint(waypoint);
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(
new MockContentResolver());
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
long waypointId = System.currentTimeMillis();
waypoint.setId(waypointId);
@@ -396,40 +395,36 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}.
*/
@UsesMocks(Cursor.class)
@Test
public void testCreateWaypoint() {
Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class);
int startColumnIndex = 1;
int columnIndex = startColumnIndex;
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns._ID))
.andReturn(columnIndex++);
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns.NAME)).andReturn(
columnIndex++);
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns.TRACKID)).andReturn(
columnIndex++);
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns._ID)).thenReturn(columnIndex++);
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns.NAME)).thenReturn(columnIndex++);
when(cursorMock.getColumnIndexOrThrow(WaypointsColumns.TRACKID)).thenReturn(columnIndex++);
columnIndex = startColumnIndex;
// Id
AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false);
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
// Name
AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false);
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
// trackIdIndex
AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false);
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
long id = System.currentTimeMillis();
columnIndex = startColumnIndex;
// Id
AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(id);
when(cursorMock.getLong(columnIndex++)).thenReturn(id);
// Name
String name = NAME_PREFIX + id;
AndroidMock.expect(cursorMock.getString(columnIndex++)).andReturn(name);
when(cursorMock.getString(columnIndex++)).thenReturn(name);
// trackIdIndex
long trackId = 11L;
AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(trackId);
AndroidMock.replay(cursorMock);
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
Waypoint waypoint = providerUtils.createWaypoint(cursorMock);
Assert.assertEquals(id, waypoint.getId());
Assert.assertEquals(name, waypoint.getName());
Assert.assertEquals(trackId, waypoint.getTrackId());
AndroidMock.verify(cursorMock);
verify(cursorMock);
}
/**
@@ -437,6 +432,7 @@ public class CustomContentProviderUtilsImplTest {
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
* when there is only one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_onlyOneWayPoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -458,8 +454,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;
}
};
@@ -473,6 +468,7 @@ public class CustomContentProviderUtilsImplTest {
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
* when there is more than one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_hasNextWayPoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -513,8 +509,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;
}
};
@@ -528,6 +523,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}.
*/
@Test
public void testGetFirstWaypointId() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -547,6 +543,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}.
*/
@Test
public void testGetNextWaypointNumber() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -577,6 +574,7 @@ public class CustomContentProviderUtilsImplTest {
* Tests the method
* {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}.
*/
@Test
public void testGetLastStatisticsWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -605,6 +603,7 @@ public class CustomContentProviderUtilsImplTest {
* Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and
* {@link ContentProviderUtilsImpl#getWaypoint(long)}.
*/
@Test
public void testInsertAndGetWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -621,6 +620,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}.
*/
@Test
public void testUpdateWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
@@ -639,9 +639,9 @@ public class CustomContentProviderUtilsImplTest {
}
/**
* Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[],
* int, long)}.
* Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], int, long)}.
*/
@Test
public void testBulkInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
@@ -657,74 +657,68 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}.
*/
@UsesMocks(Cursor.class)
@Test
public void testCreateTrackPoint() {
Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class);
// Set index.
int index = 1;
// Id
AndroidMock.expect(cursorMock.getColumnIndex(TrackPointsColumns._ID)).andReturn(index++);
when(cursorMock.getColumnIndex(TrackPointsColumns._ID)).thenReturn(index++);
// Longitude
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).andReturn(
index++);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).thenReturn(index++);
// Latitude
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).andReturn(
index++);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).thenReturn(index++);
// Time
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME))
.andReturn(index++);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME))
.thenReturn(index++);
// Speed
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).andReturn(
index++);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).thenReturn(index++);
// Sensor
AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).andReturn(
index++);
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).thenReturn(index++);
// Set return value of isNull().
index = 2;
// Longitude
AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false);
when(cursorMock.isNull(index++)).thenReturn(false);
// Latitude
AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false);
when(cursorMock.isNull(index++)).thenReturn(false);
// Time
AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false);
when(cursorMock.isNull(index++)).thenReturn(false);
// Speed
AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false);
when(cursorMock.isNull(index++)).thenReturn(false);
// Sensor
AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false);
when(cursorMock.isNull(index++)).thenReturn(false);
// Set return value of isNull().
index = 2;
// Longitude
int longitude = 11;
AndroidMock.expect(cursorMock.getInt(index++)).andReturn(longitude * 1000000);
when(cursorMock.getInt(index++)).thenReturn(longitude * 1000000);
// Latitude.
int latitude = 22;
AndroidMock.expect(cursorMock.getInt(index++)).andReturn(latitude * 1000000);
when(cursorMock.getInt(index++)).thenReturn(latitude * 1000000);
// Time
long time = System.currentTimeMillis();
AndroidMock.expect(cursorMock.getLong(index++)).andReturn(time);
when(cursorMock.getLong(index++)).thenReturn(time);
// Speed
float speed = 2.2f;
AndroidMock.expect(cursorMock.getFloat(index++)).andReturn(speed);
when(cursorMock.getFloat(index++)).thenReturn(speed);
// Sensor
byte[] sensor = "Sensor state".getBytes();
AndroidMock.expect(cursorMock.getBlob(index++)).andReturn(sensor);
when(cursorMock.getBlob(index++)).thenReturn(sensor);
AndroidMock.replay(cursorMock);
Location location = providerUtils.createTrackPoint(cursorMock);
Assert.assertEquals((double) longitude, location.getLongitude());
Assert.assertEquals((double) latitude, location.getLatitude());
Assert.assertEquals(time, location.getTime());
Assert.assertEquals(speed, location.getSpeed());
AndroidMock.verify(cursorMock);
Assert.assertEquals((double) longitude, location.getLongitude(), 0.01);
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
Assert.assertEquals(time, location.getTime(), 0.01);
Assert.assertEquals(speed, location.getSpeed(), 0.01);
verify(cursorMock);
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}.
*/
@Test
public void testInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
@@ -738,6 +732,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}.
*/
@Test
public void testGetFirstTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
@@ -750,6 +745,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}.
*/
@Test
public void testGetLastTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
@@ -762,6 +758,7 @@ public class CustomContentProviderUtilsImplTest {
/**
* Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}.
*/
@Test
public void testGetLastValidTrackPoint() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
@@ -777,6 +774,7 @@ public class CustomContentProviderUtilsImplTest {
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
* in descending.
*/
@Test
public void testGetTrackPointCursor_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
@@ -792,6 +790,7 @@ public class CustomContentProviderUtilsImplTest {
* {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)}
* in ascending.
*/
@Test
public void testGetTrackPointCursor_asc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
@@ -803,10 +802,9 @@ public class CustomContentProviderUtilsImplTest {
}
/**
* Tests the method
* {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)}
* in descending.
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
*/
@Test
public void testGetTrackPointLocationIterator_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
@@ -815,8 +813,7 @@ public class CustomContentProviderUtilsImplTest {
long startTrackPointId = 2L;
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId,
startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
for (int i = 1; i >= 0; i--) {
Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next();
@@ -831,6 +828,7 @@ public class CustomContentProviderUtilsImplTest {
* {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)}
* in ascending.
*/
@Test
public void testGetTrackPointLocationIterator_asc() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
@@ -839,8 +837,7 @@ public class CustomContentProviderUtilsImplTest {
long startTrackPointId = 2L;
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId,
startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
for (int i = 1; i < 10; i++) {
Assert.assertTrue(locationIterator.hasNext());
@@ -876,7 +873,6 @@ public class CustomContentProviderUtilsImplTest {
* @return created location
*/
private Location createLocation(int i) {
Location loc = new Location("test");
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
@@ -893,10 +889,10 @@ public class CustomContentProviderUtilsImplTest {
* @param location the location to be checked
*/
private void checkLocation(int i, Location location) {
Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude());
Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude());
Assert.assertEquals((float) i / 100.0f, location.getAccuracy());
Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude());
Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
Assert.assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
}
/**
@@ -906,7 +902,6 @@ public class CustomContentProviderUtilsImplTest {
*/
private void insertTrackWithLocations(Track track) {
providerUtils.insertTrack(track);
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track
.getLocations().size(), track.getId());
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
}
}
@@ -16,17 +16,20 @@
package de.dennisguse.opentracks.content;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import android.content.Context;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.R;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
/**
* Tests for {@link DescriptionGeneratorImpl}.
*
@@ -38,15 +41,17 @@ public class DescriptionGeneratorImplTest {
private static final long START_TIME = 1288721514000L;
private DescriptionGeneratorImpl descriptionGenerator;
private Context context = ApplicationProvider.getApplicationContext();
@Before
protected void setUp() throws Exception {
descriptionGenerator = new DescriptionGeneratorImpl(InstrumentationRegistry.getInstrumentation().getContext());
public void setUp() {
descriptionGenerator = new DescriptionGeneratorImpl(ApplicationProvider.getApplicationContext());
}
/**
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track,
* java.util.Vector, java.util.Vector, boolean)}.
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, java.util.Vector, java.util.Vector, boolean)}.
*/
@Test
public void testGenerateTrackDescription() {
Track track = new Track();
TripStatistics stats = new TripStatistics();
@@ -62,8 +67,8 @@ public class DescriptionGeneratorImplTest {
stats.setStartTime(START_TIME);
track.setTripStatistics(stats);
track.setCategory("hiking");
String expected = "Created by"
+ " <a href='TODO'>TODO</a> on Android<p>"
String expected = //"Created by"
"<a href='https://github.com/dennisguse/opentracks'>OpenTracks</a><p>"
+ "Name: -<br>"
+ "Activity type: hiking<br>"
+ "Description: -<br>"
@@ -81,13 +86,15 @@ public class DescriptionGeneratorImplTest {
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Max grade: 42 %<br>"
+ "Min grade: 11 %<br>"
+ "Recorded: " + StringUtils.formatDateTime(InstrumentationRegistry.getInstrumentation().getContext(), START_TIME) + "<br>";
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, null, null, true));
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
/**
* Tests {@link DescriptionGeneratorImpl#generateWaypointDescription(TripStatistics)}.
*/
@Test
public void testGenerateWaypointDescription() {
Waypoint waypoint = new Waypoint();
TripStatistics stats = new TripStatistics();
@@ -116,15 +123,15 @@ public class DescriptionGeneratorImplTest {
+ "Elevation gain: 6000 m (19685 ft)\n"
+ "Max grade: 42 %\n"
+ "Min grade: 11 %\n"
+ "Recorded: " + StringUtils.formatDateTime(InstrumentationRegistry.getInstrumentation().getContext(), START_TIME) + "\n";
Assert.assertEquals(
expected, descriptionGenerator.generateWaypointDescription(waypoint.getTripStatistics()));
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "\n";
Assert.assertEquals(expected, descriptionGenerator.generateWaypointDescription(waypoint.getTripStatistics()));
}
/**
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder,
* int, String)}.
*/
@Test
public void testWriteDistance() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeDistance(1100, builder, R.string.description_total_distance, "<br>");
@@ -135,6 +142,7 @@ public class DescriptionGeneratorImplTest {
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int,
* String)}.
*/
@Test
public void testWriteTime() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeTime(1000, builder, R.string.description_total_time, "<br>");
@@ -145,6 +153,7 @@ public class DescriptionGeneratorImplTest {
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder,
* int, String)}.
*/
@Test
public void testWriteSpeed() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeSpeed(1.1, builder, R.string.description_average_speed, "\n");
@@ -155,6 +164,7 @@ public class DescriptionGeneratorImplTest {
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder,
* int, String)}.
*/
@Test
public void testWriteElevation() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeElevation(4.2, builder, R.string.description_min_elevation, "<br>");
@@ -165,6 +175,7 @@ public class DescriptionGeneratorImplTest {
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int,
* String)}.
*/
@Test
public void testWritePace() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n");
@@ -175,6 +186,7 @@ public class DescriptionGeneratorImplTest {
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
* int, String)}.
*/
@Test
public void testWriteGrade() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeGrade(.042, builder, R.string.description_max_grade, "<br>");
@@ -185,6 +197,7 @@ public class DescriptionGeneratorImplTest {
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
* int, String)} with a NaN.
*/
@Test
public void testWriteGrade_nan() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeGrade(Double.NaN, builder, R.string.description_max_grade, "<br>");
@@ -195,10 +208,10 @@ public class DescriptionGeneratorImplTest {
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder,
* int, String)} with an infinite number.
*/
@Test
public void testWriteGrade_infinite() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeGrade(
Double.POSITIVE_INFINITY, builder, R.string.description_max_grade, "<br>");
descriptionGenerator.writeGrade(Double.POSITIVE_INFINITY, builder, R.string.description_max_grade, "<br>");
Assert.assertEquals("Max grade: 0 %<br>", builder.toString());
}
}
@@ -17,26 +17,27 @@
package de.dennisguse.opentracks.content;
import android.content.ContentUris;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.services.TrackRecordingServiceTest.MockContext;
import de.dennisguse.opentracks.stats.TripStatistics;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* Tests for {@link SearchEngine}.
* These are not meant to be quality tests, but instead feature-by-feature tests
@@ -53,24 +54,18 @@ public class SearchEngineTest {
private ContentProviderUtils providerUtils;
private SearchEngine engine;
@Before
protected void setUp() {
MockContentResolver mockContentResolver = new MockContentResolver();
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
getContext(), getContext(), "test.");
MockContext context = new MockContext(mockContentResolver, targetContext);
CustomContentProvider provider = new CustomContentProvider();
provider.attachInfo(context, null);
mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, provider);
setContext(context);
private Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
providerUtils = ContentProviderUtils.Factory.get(context);
engine = new SearchEngine(providerUtils);
}
@After
protected void tearDown() throws Exception {
providerUtils.deleteAllTracks(getContext());
public void tearDown() throws Exception {
providerUtils.deleteAllTracks(context);
}
private long insertTrack(String title, String description, String category, double distance, long hoursAgo) {
@@ -141,6 +136,7 @@ public class SearchEngineTest {
return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId);
}
@Test
public void testSearchText() {
// Insert 7 tracks (purposefully out of result order):
// - one which won't match
@@ -162,11 +158,10 @@ public class SearchEngineTest {
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
// Title > Description > Category.
assertTrackResults(results,
allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId,
categoryMatchId);
assertTrackResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
}
@Test
public void testSearchWaypointText() {
// Insert 7 waypoints (purposefully out of result order):
// - one which won't match
@@ -188,11 +183,10 @@ public class SearchEngineTest {
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
// Title > Description > Category.
assertWaypointResults(results,
allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId,
categoryMatchId);
assertWaypointResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
}
@Test
public void testSearchMixedText() {
// Insert 5 entries (purposefully out of result order):
// - one waypoint which will match by description
@@ -210,12 +204,13 @@ public class SearchEngineTest {
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
// Title > Description > Category.
assertEquals(results.toString(), 3, results.size());
Assert.assertEquals(results.toString(), 3, results.size());
assertTrackResult(trackId, results.get(0));
assertWaypointResult(titleWaypointId, results.get(1));
assertWaypointResult(descriptionWaypointId, results.get(2));
}
@Test
public void testSearchTrackDistance() {
// All results match text, but they're at difference distances from the user.
long farFarAwayId = insertTrack("aa", 0.3);
@@ -229,6 +224,7 @@ public class SearchEngineTest {
assertTrackResults(results, nearId, farId, farFarAwayId);
}
@Test
public void testSearchWaypointDistance() {
// All results match text, but they're at difference distances from the user.
long farFarAwayId = insertWaypoint("aa", 0.3);
@@ -255,6 +251,7 @@ public class SearchEngineTest {
assertTrackResults(results, recentId, oldId, oldestId);
}
@Test
public void testSearchWaypointRecent() {
// All results match text, but they're were recorded at different times.
long oldestId = insertWaypoint("aa", 2);
@@ -268,6 +265,7 @@ public class SearchEngineTest {
assertWaypointResults(results, recentId, oldId, oldestId);
}
@Test
public void testSearchCurrentTrack() {
// All results match text, but one of them is the current track.
long currentId = insertTrack("ab", 1);
@@ -280,6 +278,7 @@ public class SearchEngineTest {
assertTrackResults(results, otherId, currentId);
}
@Test
public void testSearchCurrentTrackWaypoint() {
// All results match text, but one of them is in the current track.
long otherId = insertWaypoint("aa", 1, 456);
@@ -293,36 +292,36 @@ public class SearchEngineTest {
}
private void assertTrackResult(long trackId, ScoredResult result) {
assertNotNull("Not a track", result.track);
assertNull("Ambiguous result", result.waypoint);
assertEquals(trackId, result.track.getId());
Assert.assertNotNull("Not a track", result.track);
Assert.assertNull("Ambiguous result", result.waypoint);
Assert.assertEquals(trackId, result.track.getId());
}
private void assertTrackResults(List<ScoredResult> results, long... trackIds) {
String errMsg = "Expected IDs=" + Arrays.toString(trackIds) + "; results=" + results;
assertEquals(results.size(), trackIds.length);
Assert.assertEquals(results.size(), trackIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
assertNotNull(errMsg, result.track);
assertNull(errMsg, result.waypoint);
assertEquals(errMsg, trackIds[i], result.track.getId());
Assert.assertNotNull(errMsg, result.track);
Assert.assertNull(errMsg, result.waypoint);
Assert.assertEquals(errMsg, trackIds[i], result.track.getId());
}
}
private void assertWaypointResult(long waypointId, ScoredResult result) {
assertNotNull("Not a waypoint", result.waypoint);
assertNull("Ambiguous result", result.track);
assertEquals(waypointId, result.waypoint.getId());
Assert.assertNotNull("Not a waypoint", result.waypoint);
Assert.assertNull("Ambiguous result", result.track);
Assert.assertEquals(waypointId, result.waypoint.getId());
}
private void assertWaypointResults(List<ScoredResult> results, long... waypointIds) {
String errMsg = "Expected IDs=" + Arrays.toString(waypointIds) + "; results=" + results;
assertEquals(results.size(), waypointIds.length);
Assert.assertEquals(results.size(), waypointIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
assertNotNull(errMsg, result.waypoint);
assertNull(errMsg, result.track);
assertEquals(errMsg, waypointIds[i], result.waypoint.getId());
Assert.assertNotNull(errMsg, result.waypoint);
Assert.assertNull(errMsg, result.track);
Assert.assertEquals(errMsg, waypointIds[i], result.waypoint.getId());
}
}
}
@@ -19,134 +19,117 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.location.Location;
import android.provider.BaseColumns;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.core.app.ApplicationProvider;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.TrackStubUtils;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
import de.dennisguse.opentracks.services.TrackRecordingServiceTest.MockContext;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import org.easymock.Capture;
import org.easymock.IAnswer;
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 static com.google.android.testing.mocking.AndroidMock.capture;
import static com.google.android.testing.mocking.AndroidMock.eq;
import static com.google.android.testing.mocking.AndroidMock.expect;
import static com.google.android.testing.mocking.AndroidMock.isA;
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(AndroidJUnit4.class)
@RunWith(MockitoJUnitRunner.class)
public class TrackDataHubTest {
private static final long TRACK_ID = 42L;
private static final int TARGET_POINTS = 50;
private MockContext context;
private Context context = ApplicationProvider.getApplicationContext();
private SharedPreferences sharedPreferences;
@Mock
private ContentProviderUtils contentProviderUtils;
@Mock
private DataSource dataSource;
private TrackDataManager trackDataManager;
private TrackDataHub trackDataHub;
@Mock
private TrackDataListener trackDataListener1;
@Mock
private TrackDataListener trackDataListener2;
private Capture<OnSharedPreferenceChangeListener> preferenceChangeListenerCapture = new Capture<
SharedPreferences.OnSharedPreferenceChangeListener>();
private ArgumentCaptor<OnSharedPreferenceChangeListener> preferenceChangeListenerCapture = ArgumentCaptor.forClass(OnSharedPreferenceChangeListener.class);
@Before
@UsesMocks({ContentProviderUtils.class, DataSource.class, TrackDataListener.class})
@Override
protected void setUp() {
context = new MockContext(new MockContentResolver(), new RenamingDelegatingContext(
getContext(), getContext(), "test."));
sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
contentProviderUtils = AndroidMock.createMock(ContentProviderUtils.class);
dataSource = AndroidMock.createMock(DataSource.class, context);
trackDataManager = new TrackDataManager();
trackDataHub = new TrackDataHub(
context, trackDataManager, contentProviderUtils, TARGET_POINTS) {
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 runInHanderThread(Runnable runnable) {
protected void runInHandlerThread(Runnable runnable) {
// Run everything in the same thread
runnable.run();
}
};
trackDataListener1 = AndroidMock.createStrictMock(
"trackDataListener1", TrackDataListener.class);
trackDataListener2 = AndroidMock.createStrictMock(
"trackDataListener2", TrackDataListener.class);
PreferencesUtils.setLong(context, R.string.recording_track_id_key, TRACK_ID);
}
@Override
protected void tearDown() throws Exception {
AndroidMock.reset(dataSource);
@After
public void tearDown() {
reset(dataSource);
// Expect everything to be unregistered.
dataSource.unregisterContentObserver(isA(ContentObserver.class));
AndroidMock.expectLastCall().times(3);
dataSource.unregisterOnSharedPreferenceChangeListener(
isA(OnSharedPreferenceChangeListener.class));
AndroidMock.replay(dataSource);
//TODO
// AndroidMock.expectLastCall().times(3);
dataSource.unregisterOnSharedPreferenceChangeListener(isA(OnSharedPreferenceChangeListener.class));
verify(dataSource);
trackDataHub.stop();
trackDataHub = null;
super.tearDown();
}
/**
* Tests registering for tracks table update.
*/
@Test
public void testTracksTableUpdate() {
// Register two listeners
Capture<ContentObserver> contentObserverCapture = new Capture<ContentObserver>();
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
Track track = new Track();
expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track);
dataSource.registerContentObserver(
eq(TracksColumns.CONTENT_URI), capture(contentObserverCapture));
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));
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();
expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track);
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
trackDataListener1.onTrackUpdated(track);
trackDataListener2.onTrackUpdated(track);
replay();
@@ -155,7 +138,7 @@ public class TrackDataHubTest {
verifyAndReset();
// Unregister one listener
expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track);
when(contentProviderUtils.getTrack(TRACK_ID)).thenReturn(track);
trackDataListener2.onTrackUpdated(track);
replay();
@@ -172,111 +155,117 @@ public class TrackDataHubTest {
verifyAndReset();
}
//TODO
/**
* Tests registering for waypoints table update.
*/
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
Capture<ContentObserver> contentObserverCapture = new Capture<ContentObserver>();
expect(contentProviderUtils.getWaypointCursor(
eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
.andStubAnswer(new FixedSizeCursorAnswer(2));
expect(contentProviderUtils.createWaypoint(isA(Cursor.class)))
.andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint1).andReturn(waypoint2);
dataSource.registerContentObserver(
eq(WaypointsColumns.CONTENT_URI), capture(contentObserverCapture));
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();
expect(contentProviderUtils.getWaypointCursor(
eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
.andStubAnswer(new FixedSizeCursorAnswer(3));
expect(contentProviderUtils.createWaypoint(isA(Cursor.class)))
.andReturn(waypoint1).andReturn(waypoint2).andReturn(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
expect(contentProviderUtils.getWaypointCursor(
eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS)))
.andStubAnswer(new FixedSizeCursorAnswer(4));
expect(contentProviderUtils.createWaypoint(isA(Cursor.class)))
.andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint3).andReturn(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();
}
// @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_BASE_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
Capture<ContentObserver> contentObserverCapture = new Capture<ContentObserver>();
dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture));
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L);
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();
@@ -284,30 +273,26 @@ public class TrackDataHubTest {
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(
trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Register a second listener
locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L);
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));
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);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(11L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(20L);
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();
@@ -319,10 +304,9 @@ public class TrackDataHubTest {
// Unregister one listener and change track
locationIterator = new FixedSizeLocationIterator(101, 10);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
.andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).andReturn(110L);
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();
@@ -336,17 +320,16 @@ public class TrackDataHubTest {
/**
* Tests track points table update with registering the same listener.
*/
@Test
public void testTrackPointsTableUpdate_reRegister() {
// Register one listener
Capture<ContentObserver> contentObserverCapture = new Capture<ContentObserver>();
dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture));
ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L);
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);
@@ -355,8 +338,7 @@ public class TrackDataHubTest {
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(
trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Unregister the listener
@@ -368,36 +350,32 @@ public class TrackDataHubTest {
verifyAndReset();
// Register again
dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture));
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), contentObserverCapture.capture());
locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L);
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));
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
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L);
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();
@@ -405,8 +383,7 @@ public class TrackDataHubTest {
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(
trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
// Unregister the listener
@@ -418,73 +395,65 @@ public class TrackDataHubTest {
verifyAndReset();
// Register the listener after a new track
dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_URI), observerCapture.capture());
locationIterator = new FixedSizeLocationIterator(1, 10);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
.andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).andReturn(10L);
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));
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
verifyAndReset();
}
//TODO
/**
* Tests track points table update with large track sampling.
*/
public void testTrackPointsTableUpdate_largeTrackSampling() {
Capture<ContentObserver> contentObserverCapture = new Capture<ContentObserver>();
dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture));
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(200L);
AndroidMock.expectLastCall().anyTimes();
FixedSizeLocationIterator locationIterator1 = new FixedSizeLocationIterator(
1, 200, 4, 25, 71, 120);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator1);
FixedSizeLocationIterator locationIterator2 = new FixedSizeLocationIterator(
1, 200, 4, 25, 71, 120);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(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();
}
// @Test
// public void testTrackPointsTableUpdate_largeTrackSampling() {
// ArgumentCaptor<ContentObserver> contentObserverCapture = ArgumentCaptor.forClass(ContentObserver.class);
// dataSource.registerContentObserver(eq(TrackPointsColumns.CONTENT_BASE_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() {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
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);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(30L);
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);
@@ -493,16 +462,14 @@ public class TrackDataHubTest {
trackDataHub.start();
trackDataHub.loadTrack(TRACK_ID);
trackDataHub.registerTrackDataListener(
trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE));
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);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(31L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(60L);
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();
@@ -512,9 +479,9 @@ public class TrackDataHubTest {
// Now another 30 (triggers resampling)
locationIterator = new FixedSizeLocationIterator(1, 90);
expect(contentProviderUtils.getTrackPointLocationIterator(
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(90L);
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();
@@ -527,66 +494,49 @@ public class TrackDataHubTest {
/**
* 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);
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(capture(preferenceChangeListenerCapture));
expect(trackDataListener1.onMetricUnitsChanged(true)).andReturn(false);
expect(trackDataListener1.onReportSpeedChanged(true)).andReturn(false);
expect(
trackDataListener1.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT))
.andReturn(false);
expect(trackDataListener1.onRecordingDistanceIntervalChanged(
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).andReturn(false);
expect(trackDataListener1.onMapTypeChanged(PreferencesUtils.MAP_TYPE_DEFAULT)).andReturn(false);
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);
expect(trackDataListener2.onMetricUnitsChanged(true)).andReturn(false);
expect(trackDataListener2.onReportSpeedChanged(true)).andReturn(false);
expect(
trackDataListener2.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT))
.andReturn(false);
expect(trackDataListener2.onRecordingDistanceIntervalChanged(
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).andReturn(false);
expect(trackDataListener2.onMapTypeChanged(PreferencesUtils.MAP_TYPE_DEFAULT)).andReturn(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));
trackDataHub.registerTrackDataListener(trackDataListener1, EnumSet.of(TrackDataType.PREFERENCE));
trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of(TrackDataType.PREFERENCE));
verifyAndReset();
// Change report speed to false
expect(trackDataListener1.onReportSpeedChanged(false)).andReturn(false);
expect(trackDataListener2.onReportSpeedChanged(false)).andReturn(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));
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));
listener.onSharedPreferenceChanged(sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_rate_key));
verifyAndReset();
// Change metric units to false
expect(trackDataListener1.onMetricUnitsChanged(false)).andReturn(false);
expect(trackDataListener2.onMetricUnitsChanged(false)).andReturn(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));
listener.onSharedPreferenceChanged(sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_units_key));
verifyAndReset();
}
@@ -594,125 +544,128 @@ public class TrackDataHubTest {
* Replays mocks.
*/
private void replay() {
AndroidMock.replay(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
// AndroidMock.replay(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
}
/**
* Verifies and resets mocks.
*/
private void verifyAndReset() {
AndroidMock.verify(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
AndroidMock.reset(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
verify(contentProviderUtils);
verify(dataSource);
verify(trackDataListener1);
verify(trackDataListener2);
reset(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2);
}
//TODO
/**
* Tests the method {@link TrackDataHub#start()}.
*/
public void testRegisterTracksTableListener() {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), capture(observerCapture));
Track track = TrackStubUtils.createTrack(1);
expect(contentProviderUtils.getTrack(capture(new Capture<Long>()))).andReturn(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();
}
// @Test
// public void testRegisterTracksTableListener() {
// ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
// dataSource.registerContentObserver(eq(TracksColumns.CONTENT_BASE_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()}.
*/
public void testRegisterWaypointsTableListener() {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), capture(observerCapture));
expect(contentProviderUtils.getWaypointCursor(
capture(new Capture<Long>()), capture(new Capture<Long>()),
capture(new Capture<Integer>()))).andReturn(null);
trackDataListener1.clearWaypoints();
trackDataListener1.onNewWaypointsDone();
replay();
trackDataHub.start();
trackDataHub.registerTrackDataListener(
trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
verifyAndReset();
}
// @Test
// public void testRegisterWaypointsTableListener() {
// ArgumentCaptor<ContentObserver> observerCapture = ArgumentCaptor.forClass(ContentObserver.class);
// dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_BASE_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));
assertEquals(value, trackDataHub.getRecordingGpsAccuracy());
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));
assertEquals(value + 1, trackDataHub.getRecordingGpsAccuracy());
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));
assertEquals(false, trackDataHub.isMetricUnits());
PreferencesUtils.setString(
context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
trackDataHub.notifyPreferenceChanged(
PreferencesUtils.getKey(context, R.string.stats_units_key));
assertEquals(true, trackDataHub.isMetricUnits());
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));
assertEquals(false, trackDataHub.isReportSpeed());
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));
assertEquals(true, trackDataHub.isReportSpeed());
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 implements IAnswer<Cursor> {
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;
}
//
// @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;
// }
}
/**
@@ -19,11 +19,12 @@ import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
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}
@@ -33,6 +34,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class WaypointCreationRequestTest {
@Test
public void testTypeParceling() {
WaypointCreationRequest original = WaypointCreationRequest.DEFAULT_WAYPOINT;
Parcel p = Parcel.obtain();
@@ -46,9 +48,9 @@ public class WaypointCreationRequestTest {
Assert.assertNull(copy.getIconUrl());
}
@Test
public void testAllAttributesParceling() {
WaypointCreationRequest original = new WaypointCreationRequest(
WaypointType.WAYPOINT, false, "name", "category", "description", "img.png", null);
WaypointCreationRequest original = new WaypointCreationRequest(WaypointType.WAYPOINT, false, "name", "category", "description", "img.png", null);
Parcel p = Parcel.obtain();
original.writeToParcel(p, 0);
p.setDataPosition(0);
@@ -18,20 +18,20 @@ package de.dennisguse.opentracks.fragments;
import android.location.Location;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.ChartView;
import de.dennisguse.opentracks.TrackStubUtils;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.sensor.Sensor;
import de.dennisguse.opentracks.content.sensor.Sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.UnitConversions;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
/**
* Tests {@link ChartFragment}.
*
@@ -45,94 +45,76 @@ public class ChartFragmentTest {
private ChartFragment chartFragment;
@Before
protected void setUp() {
public void setUp() {
chartFragment = new ChartFragment();
chartFragment.setChartView(new ChartView(InstrumentationRegistry.getInstrumentation().getContext()));
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext()));
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
}
/**
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location,
* double[])}
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}
*/
@Test
public void testFillDataPoint_sensorIncorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
// Input incorrect state.
// Creates SensorData.
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
.setValue(100).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(101).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(102).setState(Sensor.SensorState.NONE);
// Creates SensorDataSet.
SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet();
sensorDataSet = sensorDataSet.toBuilder()
.setHeartRate(heartRateData).setCadence(cadenceData).setPower(powerData).build();
SensorDataSet sensorDataSet = new SensorDataSet(SensorDataSet.DATA_UNAVAILABLE, SensorDataSet.DATA_UNAVAILABLE);
sensorDataSetLocation.setSensorDataSet(sensorDataSet);
// Test.
point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
}
/**
* Tests the logic to get the correct values of sensor in {@link ChartFragment#fillDataPoint(Location,
* double[])}.
* Tests the logic to get the correct values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
@Test
public void testFillDataPoint_sensorCorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
// Creates SensorData.
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
.setValue(100).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(101).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(102).setState(Sensor.SensorState.SENDING);
SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102);
// Creates SensorDataSet.
SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet();
sensorDataSet = sensorDataSet.toBuilder()
.setHeartRate(heartRateData).setCadence(cadenceData).setPower(powerData).build();
sensorDataSetLocation.setSensorDataSet(sensorDataSet);
// Test.
point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1]);
Assert.assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1]);
Assert.assertEquals(102.0, point[ChartView.POWER_SERIES + 1]);
Assert.assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
Assert.assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1], 0.01);
Assert.assertEquals(102.0, point[ChartView.POWER_SERIES + 1], 0.01);
}
/**
* Tests the logic to get the value of metric Distance in {@link ChartFragment#fillDataPoint(Location,
* double[])}.
* Tests the logic to get the value of metric Distance in {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
@Test
public void testFillDataPoint_distanceMetric() {
// By distance.
chartFragment.setChartByDistance(true);
// Resets last location and writes first location.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0]);
Assert.assertEquals(0.0, point[0], 0.01);
// The second is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(0.0, point[0]);
Assert.assertEquals(0.0, point[0], 0.01);
// The third location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
@@ -144,7 +126,7 @@ public class ChartFragmentTest {
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(),
sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals(distance1, point[0]);
Assert.assertEquals(distance1, point[0], 0.01);
// The fourth location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
@@ -155,13 +137,13 @@ public class ChartFragmentTest {
Location.distanceBetween(sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(),
sensorDataSetLocation4.getLatitude(), sensorDataSetLocation4.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals((distance1 + distance2), point[0]);
Assert.assertEquals((distance1 + distance2), point[0], 0.01);
}
/**
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#fillDataPoint(Location,
* double[])}.
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
@Test
public void testFillDataPoint_distanceImperial() {
// By distance.
chartFragment.setChartByDistance(true);
@@ -171,7 +153,7 @@ public class ChartFragmentTest {
// The first is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0]);
Assert.assertEquals(0.0, point[0], 0.01);
// The second location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
@@ -186,7 +168,7 @@ public class ChartFragmentTest {
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]);
Assert.assertEquals(distance1, point[0], 0.01);
// The third location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
@@ -200,30 +182,30 @@ public class ChartFragmentTest {
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]);
Assert.assertEquals(distance1 + distance2, point[0], 0.01);
}
/**
* Tests the logic to get the values of time in {@link ChartFragment#fillDataPoint(Location,
* double[])}.
* Tests the logic to get the values of time in {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
@Test
public void testFillDataPoint_time() {
// By time
chartFragment.setChartByDistance(false);
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0]);
Assert.assertEquals(0.0, point[0], 0.01);
long timeSpan = 222;
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((double) timeSpan, point[0]);
Assert.assertEquals((double) timeSpan, point[0], 0.01);
}
/**
* Tests the logic to get the value of elevation in {@link ChartFragment#fillDataPoint(Location,
* double[])} by one and two points.
* Tests the logic to get the value of elevation in {@link ChartFragment#fillDataPoint(Location, double[])} by one and two points.
*/
@Test
public void testFillDataPoint_elevation() {
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
@@ -232,7 +214,7 @@ public class ChartFragmentTest {
* parameter. Then only one value INITIALLONGTITUDE in buffer.
*/
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1]);
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01);
/*
* Send another value to buffer, now there are two values, INITIALALTITUDE
@@ -242,15 +224,15 @@ public class ChartFragmentTest {
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
point[ChartView.ELEVATION_SERIES + 1]);
point[ChartView.ELEVATION_SERIES + 1], 0.01);
}
/**
* Tests the logic to get the value of speed in {@link ChartFragment#fillDataPoint(Location,
* double[])}. In this test, firstly remove all
* points in memory, and then fill in two points one by one. The speed values
* of these points are 129, 130.
* Tests the logic to get the value of speed in {@link ChartFragment#fillDataPoint(Location, double[])}.
* In this test, firstly remove all points in memory, and then fill in two points one by one.
* The speed values of these points are 129, 130.
*/
@Test
public void testFillDataPoint_speed() {
/*
* At first, clear old points of speed, so give true to the second
@@ -259,7 +241,7 @@ public class ChartFragmentTest {
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(128.5f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
/*
* Tests the logic when both metricUnits and reportSpeed are true.This
@@ -274,12 +256,13 @@ public class ChartFragmentTest {
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222);
sensorDataSetLocation2.setSpeed(130);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1]);
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1], 0.01);
}
/**
* Tests the logic to compute speed when use Imperial.
*/
@Test
public void testFillDataPoint_speedImperial() {
// Setups to use imperial.
chartFragment.setMetricUnits(false);
@@ -288,7 +271,7 @@ public class ChartFragmentTest {
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
@@ -300,12 +283,13 @@ public class ChartFragmentTest {
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
sensorDataSetLocation2.setSpeed(102);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[ChartView.SPEED_SERIES + 1]);
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[ChartView.SPEED_SERIES + 1], 0.01);
}
/**
* Tests the logic to get pace value when reportSpeed is false.
*/
@Test
public void testFillDataPoint_pace_nonZeroSpeed() {
// Setups reportSpeed to false.
chartFragment.setReportSpeed(false);
@@ -314,7 +298,7 @@ public class ChartFragmentTest {
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
@@ -326,20 +310,21 @@ public class ChartFragmentTest {
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
sensorDataSetLocation2.setSpeed(102);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1]);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1], 0.01);
}
/**
* Tests the logic to get pace value when reportSpeed is false and average
* speed is zero.
*/
@Test
public void testFillDataPoint_pace_zeroSpeed() {
// Setups reportSpeed to false.
chartFragment.setReportSpeed(false);
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation.setSpeed(0);
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1]);
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1], 0.01);
}
/**
@@ -353,5 +338,4 @@ public class ChartFragmentTest {
chartFragment.fillDataPoint(location, point);
return point;
}
}
@@ -17,36 +17,37 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.ContentUris;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.test.AndroidTestCase;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.ContentProviderUtils.Factory;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.WaypointsColumns;
import de.dennisguse.opentracks.testing.TestingProviderUtilsFactory;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import androidx.test.core.app.ApplicationProvider;
import org.easymock.Capture;
import org.junit.Assert;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.SimpleTimeZone;
import static com.google.android.testing.mocking.AndroidMock.eq;
import static com.google.android.testing.mocking.AndroidMock.expect;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.WaypointsColumns;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
/**
* Abstract class for testing file track importers.
*
* @author Jimmy Shih.
*/
public class AbstractTestFileTrackImporter extends AndroidTestCase {
public abstract class AbstractTestFileTrackImporter {
protected static final String TRACK_NAME_0 = "blablub";
protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
@@ -63,22 +64,19 @@ public class AbstractTestFileTrackImporter extends AndroidTestCase {
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 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();
protected static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
protected static final Uri TRACK_ID_1_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build();
protected static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
protected final Context context = ApplicationProvider.getApplicationContext();
static {
/*
@@ -90,24 +88,8 @@ public class AbstractTestFileTrackImporter extends AndroidTestCase {
DATE_FORMAT_1.setTimeZone(utc);
}
protected ContentProviderUtils contentProviderUtils;
private Factory oldMyTracksProviderUtilsFactory;
@UsesMocks(ContentProviderUtils.class)
@Override
protected void setUp() throws Exception {
super.setUp();
contentProviderUtils = AndroidMock.createMock(ContentProviderUtils.class);
oldMyTracksProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(
contentProviderUtils);
}
@Override
protected void tearDown() throws Exception {
TestingProviderUtilsFactory.restoreOldFactory(oldMyTracksProviderUtilsFactory);
super.tearDown();
}
@Mock
public ContentProviderUtils contentProviderUtils;
protected Location createLocation(int index, long time) {
Location location = new Location(LocationManager.GPS_PROVIDER);
@@ -126,37 +108,35 @@ public class AbstractTestFileTrackImporter extends AndroidTestCase {
* @param trackPointId the track point id
*/
protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) {
expect(contentProviderUtils.bulkInsertTrackPoint(
location != null ? LocationsMatcher.eqLoc(location) : (Location[]) AndroidMock.anyObject(),
eq(1), eq(trackId))).andReturn(1);
expect(contentProviderUtils.getFirstTrackPointId(trackId)).andReturn(trackPointId);
expect(contentProviderUtils.getLastTrackPointId(trackId)).andReturn(trackPointId);
when(contentProviderUtils.bulkInsertTrackPoint(location != null ? (Location[]) any() : (Location[]) any(), eq(1), eq(trackId))).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(trackId)).thenReturn(trackPointId);
when(contentProviderUtils.getLastTrackPointId(trackId)).thenReturn(trackPointId);
}
/**
* Expects the track to be updated.
*
* @param track the track
* @param lastTrack true if it is the last track in the gpx
* @param trackId the track id
* @param trackCaptor the track
* @param lastTrack true if it is the last track in the gpx
* @param trackId the track id
*/
protected void expectUpdateTrack(Capture<Track> track, boolean lastTrack, long trackId) {
contentProviderUtils.updateTrack(AndroidMock.capture(track));
expect(contentProviderUtils.insertWaypoint((Waypoint) AndroidMock.anyObject()))
.andReturn(WAYPOINT_ID_O_URI);
protected void expectUpdateTrack(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
contentProviderUtils.updateTrack(trackCaptor.capture());
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
if (lastTrack) {
// Return null to not add waypoints
expect(contentProviderUtils.getTrack(trackId)).andReturn(null);
when(contentProviderUtils.getTrack(trackId)).thenReturn(null);
}
}
protected void verifyTrack(Track track, String name, String description, long time) {
assertEquals(name, track.getName());
assertEquals(description, track.getDescription());
Assert.assertEquals(name, track.getName());
Assert.assertEquals(description, track.getDescription());
if (time != -1L) {
assertEquals(time, track.getTripStatistics().getStartTime());
Assert.assertEquals(time, track.getTripStatistics().getStartTime());
}
assertNotSame(-1, track.getStartId());
assertNotSame(-1, track.getStopId());
Assert.assertNotSame(-1, track.getStartId());
Assert.assertNotSame(-1, track.getStopId());
}
}
@@ -18,24 +18,33 @@ package de.dennisguse.opentracks.io.file.importer;
import android.location.Location;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import com.google.android.testing.mocking.AndroidMock;
import org.easymock.Capture;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import static com.google.android.testing.mocking.AndroidMock.eq;
import static com.google.android.testing.mocking.AndroidMock.expect;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link GpxFileTrackImporter}.
*
* @author Steffen Horlacher
*/
@RunWith(MockitoJUnitRunner.class)
public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<gpx><trk>"
@@ -49,18 +58,12 @@ 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");
private static final String INVALID_ALTITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(
Double.toString(TRACK_ELEVATION), "invalid");
private static final String INVALID_LATITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(
Double.toString(TRACK_LATITUDE), "invalid");
private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(
Double.toString(TRACK_LONGITUDE), "invalid");
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");
private static final String INVALID_ALTITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_ELEVATION), "invalid");
private static final String INVALID_LATITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "invalid");
private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LONGITUDE), "invalid");
private static String getNameAndDescription(String name, String description) {
return "<name><![CDATA[" + name + "]]></name>" + "<desc><![CDATA[" + description + "]]></desc>";
@@ -70,9 +73,8 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
String latitude = Double.toString(TRACK_LATITUDE + index);
String longitude = Double.toString(TRACK_LONGITUDE + index);
String elevation = Double.toString(TRACK_ELEVATION + index);
StringBuffer buffer = new StringBuffer();
buffer.append(
"<trkpt lat=\"" + latitude + "\" lon=\"" + longitude + "\"><ele>" + elevation + "</ele>");
StringBuilder buffer = new StringBuilder();
buffer.append("<trkpt lat=\"" + latitude + "\" lon=\"" + longitude + "\"><ele>" + elevation + "</ele>");
if (time != null) {
buffer.append("<time>" + time + "</time>");
}
@@ -83,174 +85,155 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
/**
* Tests one track with one segment.
*/
@Test
public void testOneTrackOneSegment() throws Exception {
Capture<Track> track = new Capture<Track>();
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
.andReturn(TRACK_ID_0_URI);
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
expect(contentProviderUtils.bulkInsertTrackPoint(
LocationsMatcher.eqLoc(location1), eq(1), eq(TRACK_ID_0))).andReturn(1);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_1);
expect(
contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(),
R.string.recording_track_id_key))).andStubReturn(null);
expectUpdateTrack(track, true, TRACK_ID_0);
AndroidMock.replay(contentProviderUtils);
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);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), contentProviderUtils);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
assertEquals(TRACK_ID_0, trackId);
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();
assertEquals(time1 - time0, track.getValue().getTripStatistics().getTotalTime());
AndroidMock.verify(contentProviderUtils);
verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
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 {
Capture<Track> track = new Capture<Track>();
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
.andReturn(TRACK_ID_0_URI);
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
expect(contentProviderUtils.bulkInsertTrackPoint(
(Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3);
expect(
contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(),
R.string.recording_track_id_key))).andStubReturn(null);
expectUpdateTrack(track, true, TRACK_ID_0);
AndroidMock.replay(contentProviderUtils);
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);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), contentProviderUtils);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
assertEquals(TRACK_ID_0, trackId);
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();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
assertEquals(
time1 - time0 + time3 - time2, track.getValue().getTripStatistics().getTotalTime());
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
AndroidMock.verify(contentProviderUtils);
verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0,
DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
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.
*/
public void testOneTrackTwoSegmentsNoTime() throws Exception {
Capture<Track> track = new Capture<Track>();
@Test
public void testOneTrackTwoSegmentsNoTime() {
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
.andReturn(TRACK_ID_0_URI);
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
expect(contentProviderUtils.bulkInsertTrackPoint(
(Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3);
expect(
contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(),
R.string.recording_track_id_key))).andStubReturn(null);
expectUpdateTrack(track, true, TRACK_ID_0);
AndroidMock.replay(contentProviderUtils);
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);
InputStream inputStream = new ByteArrayInputStream(
VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), contentProviderUtils);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
assertEquals(TRACK_ID_0, trackId);
assertEquals(0, track.getValue().getTripStatistics().getTotalTime());
Assert.assertEquals(TRACK_ID_0, trackId);
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
AndroidMock.verify(contentProviderUtils);
verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
verify(contentProviderUtils);
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
}
/**
* Test an invalid xml input.
*/
public void testInvalidXml() throws Exception {
@Test
public void testInvalidXml() {
testInvalidGpx(INVALID_XML_GPX);
}
/**
* Test an invalid location.
*/
public void testInvalidLocation() throws Exception {
@Test
public void testInvalidLocation() {
testInvalidGpx(INVALID_LOCATION_GPX);
}
/**
* Test an invalid time.
*/
public void testInvalidTime() throws Exception {
@Test
public void testInvalidTime() {
testInvalidGpx(INVALID_TIME_GPX);
}
/**
* Test an invalid altitude.
*/
public void testInvalidAltitude() throws Exception {
@Test
public void testInvalidAltitude() {
testInvalidGpx(INVALID_ALTITUDE_GPX);
}
/**
* Test an invalid latitude.
*/
public void testInvalidLatitude() throws Exception {
@Test
public void testInvalidLatitude() {
testInvalidGpx(INVALID_LATITUDE_GPX);
}
/**
* Test an invalid longitude.
*/
public void testInvalidLongitude() throws Exception {
@Test
public void testInvalidLongitude() {
testInvalidGpx(INVALID_LONGITUDE_GPX);
}
private void testInvalidGpx(String xml) throws Exception {
expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
.andReturn(TRACK_ID_0_URI);
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.
expect(contentProviderUtils.bulkInsertTrackPoint(
(Location[]) AndroidMock.anyObject(), AndroidMock.anyInt(), AndroidMock.anyLong()))
.andStubReturn(1);
expect(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).andStubReturn(TRACK_POINT_ID_0);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andStubReturn(TRACK_POINT_ID_0);
expect(
contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(),
R.string.recording_track_id_key))).andStubReturn(null);
contentProviderUtils.deleteTrack(getContext(), TRACK_ID_0);
AndroidMock.replay(contentProviderUtils);
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(
getContext(), contentProviderUtils);
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
assertEquals(-1L, trackId);
AndroidMock.verify(contentProviderUtils);
Assert.assertEquals(-1L, trackId);
verify(contentProviderUtils);
}
}
@@ -18,24 +18,31 @@ package de.dennisguse.opentracks.io.file.importer;
import android.location.Location;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import com.google.android.testing.mocking.AndroidMock;
import org.easymock.Capture;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import static com.google.android.testing.mocking.AndroidMock.eq;
import static com.google.android.testing.mocking.AndroidMock.expect;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
@RunWith(MockitoJUnitRunner.class)
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX =
@@ -59,83 +66,72 @@ 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);
StringBuffer buffer = new StringBuffer();
buffer.append("<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " "
+ altitude + "</gx:coord>");
StringBuilder buffer = new StringBuilder();
buffer.append("<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>");
return buffer.toString();
}
/**
* Tests one track with one segment.
*/
@Test
public void testOneTrackOneSegment() throws Exception {
Capture<Track> track = new Capture<Track>();
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
contentProviderUtils.clearTrack(getContext(), TRACK_ID_0);
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
expect(contentProviderUtils.bulkInsertTrackPoint(
LocationsMatcher.eqLoc(location1), eq(1), eq(TRACK_ID_0))).andReturn(1);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_1);
expect(
contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(),
R.string.recording_track_id_key))).andStubReturn(null);
expectUpdateTrack(track, true, TRACK_ID_0);
AndroidMock.replay(contentProviderUtils);
when(contentProviderUtils.bulkInsertTrackPoint(new Location[]{location1}, 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);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(
getContext(), TRACK_ID_0, contentProviderUtils);
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
assertEquals(TRACK_ID_0, trackId);
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();
assertEquals(time1 - time0, track.getValue().getTripStatistics().getTotalTime());
AndroidMock.verify(contentProviderUtils);
verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
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 {
Capture<Track> track = new Capture<Track>();
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
contentProviderUtils.clearTrack(getContext(), TRACK_ID_0);
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
expect(contentProviderUtils.bulkInsertTrackPoint(
(Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5);
expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3);
expect(
contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(),
R.string.recording_track_id_key))).andStubReturn(null);
expectUpdateTrack(track, true, TRACK_ID_0);
AndroidMock.replay(contentProviderUtils);
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);
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(
getContext(), TRACK_ID_0, contentProviderUtils);
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
assertEquals(TRACK_ID_0, trackId);
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();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
assertEquals(
time1 - time0 + time3 - time2, track.getValue().getTripStatistics().getTotalTime());
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
AndroidMock.verify(contentProviderUtils);
verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0,
DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
verify(contentProviderUtils);
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
}
@@ -1,81 +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.io.file.importer;
import android.location.Location;
import com.google.android.testing.mocking.AndroidMock;
import org.easymock.IArgumentMatcher;
import java.util.Arrays;
/**
* Locations matcher. Workaround because of capture bug 2617107 in easymock:
* http://sourceforge.net/tracker/?func=detail&aid=2617107&group_id=82958&atid=567837
*
* @author Jimmy Shih
*/
public class LocationsMatcher implements IArgumentMatcher {
private final Location[] expectedLocations;
private LocationsMatcher(Location[] expected) {
this.expectedLocations = expected;
}
public static Location[] eqLoc(Location expected) {
return eqLoc(new Location[]{expected});
}
public static Location[] eqLoc(Location[] expected) {
IArgumentMatcher matcher = new LocationsMatcher(expected);
AndroidMock.reportMatcher(matcher);
return null;
}
@Override
public void appendTo(StringBuffer buf) {
buf.append("eqLoc(").append(Arrays.toString(expectedLocations)).append(")");
}
@Override
public boolean matches(Object obj) {
if (!(obj instanceof Location[])) {
return false;
}
Location[] locations = (Location[]) obj;
if (locations.length < expectedLocations.length) {
return false;
}
// Only check the first elements (those that will be taken into account)
for (int i = 0; i < expectedLocations.length; i++) {
if (!matchLocation(locations[i], expectedLocations[i])) {
return false;
}
}
return true;
}
private boolean matchLocation(Location location1, Location location2) {
return (location1.getTime() == location2.getTime())
&& (location1.getLatitude() == location2.getLatitude())
&& (location1.getLongitude() == location2.getLongitude())
&& (location1.getAltitude() == location2.getAltitude());
}
}
@@ -17,55 +17,58 @@ package de.dennisguse.opentracks.services;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.test.ServiceTestCase;
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 com.google.android.testing.mocking.UsesMocks;
import org.easymock.EasyMock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests {@link ControlRecordingService}.
*
* @author Youtao Liu
*/
public class ControlRecordingServiceTest extends ServiceTestCase<ControlRecordingService> {
public class ControlRecordingServiceTest {
private Context context;
private ControlRecordingService controlRecordingService;
@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
public ControlRecordingServiceTest() {
super(ControlRecordingService.class);
}
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@Override
protected void setUp() throws Exception {
super.setUp();
context = getContext();
}
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.
*/
@UsesMocks(ITrackRecordingService.class)
public void testStartRecording() {
assertNull(controlRecordingService);
@Test
public void testStartRecording() throws TimeoutException {
Assert.assertNull(subject);
Intent intent = startControlRecordingService(context.getString(R.string.track_action_start));
assertNotNull(controlRecordingService);
Assert.assertNotNull(subject);
ITrackRecordingService iTrackRecordingServiceMock = EasyMock
.createStrictMock(ITrackRecordingService.class);
try {
EasyMock.expect(iTrackRecordingServiceMock.startNewTrack()).andReturn(1L);
EasyMock.replay(iTrackRecordingServiceMock);
controlRecordingService.onHandleIntent(intent, iTrackRecordingServiceMock);
EasyMock.verify(iTrackRecordingServiceMock);
} catch (RemoteException e) {
fail();
}
when(iTrackRecordingServiceMock.startNewTrack()).thenReturn(1L);
subject.onHandleIntent(intent, iTrackRecordingServiceMock);
verify(iTrackRecordingServiceMock);
}
/**
@@ -73,20 +76,13 @@ public class ControlRecordingServiceTest extends ServiceTestCase<ControlRecordin
* {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)}
* to stop a track recording.
*/
@UsesMocks(ITrackRecordingService.class)
public void testStopRecording() {
@Test
public void testStopRecording() throws TimeoutException {
Intent intent = startControlRecordingService(context.getString(R.string.track_action_end));
iTrackRecordingServiceMock.endCurrentTrack();
ITrackRecordingService iTrackRecordingServiceMock = EasyMock
.createStrictMock(ITrackRecordingService.class);
try {
iTrackRecordingServiceMock.endCurrentTrack();
EasyMock.replay(iTrackRecordingServiceMock);
controlRecordingService.onHandleIntent(intent, iTrackRecordingServiceMock);
EasyMock.verify(iTrackRecordingServiceMock);
} catch (RemoteException e) {
fail();
}
subject.onHandleIntent(intent, iTrackRecordingServiceMock);
verify(iTrackRecordingServiceMock);
}
/**
@@ -94,12 +90,11 @@ public class ControlRecordingServiceTest extends ServiceTestCase<ControlRecordin
*
* @param action the action string in the start intent
*/
private Intent startControlRecordingService(String action) {
private Intent startControlRecordingService(String action) throws TimeoutException {
Intent intent = new Intent(context, ControlRecordingService.class);
intent.setAction(action);
startService(intent);
controlRecordingService = getService();
mServiceRule.startService(intent);
subject = ((ControlRecordingService) mServiceRule.bindService(intent));
return intent;
}
}
@@ -16,28 +16,32 @@
package de.dennisguse.opentracks.services;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentProvider;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import android.test.RenamingDelegatingContext;
import android.test.ServiceTestCase;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import de.dennisguse.opentracks.Constants;
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.ServiceTestRule;
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.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;
@@ -46,175 +50,100 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
import java.util.List;
/**
* Tests for the track recording service.
* <p>
* TODO: The original class, ServiceTestCase, has a few limitations, e.g. it's
* not possible to properly shutdown the service, unless tearDown() is called,
* which prevents from testing multiple scenarios in a single test (see
* runFunctionTest for more details).
*
* @author Bartlomiej Niechwiej
*/
public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingService> {
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTest {
private Context context;
@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
private ContentProviderUtils providerUtils;
private Context context = ApplicationProvider.getApplicationContext();
/*
* In order to support starting and binding to the service in the same unit
* test, we provide a workaround, as the original class doesn't allow to bind
* after the service has been previously started.
*/
private boolean bound;
private Intent serviceIntent;
public TrackRecordingServiceTest() {
super(TestRecordingService.class);
}
@Override
protected IBinder bindService(Intent intent) {
if (getService() != null) {
if (bound) {
throw new IllegalStateException("Service: " + getService() + " is already bound");
}
bound = true;
serviceIntent = intent.cloneFilter();
return getService().onBind(intent);
} else {
return super.bindService(intent);
}
}
@Override
protected void shutdownService() {
if (bound) {
assertNotNull(getService());
getService().onUnbind(serviceIntent);
bound = false;
}
super.shutdownService();
}
@Override
protected void setUp() throws Exception {
super.setUp();
/*
* Create a mock context that uses a mock content resolver and a renaming
* delegating context.
*/
MockContentResolver mockContentResolver = new MockContentResolver();
RenamingDelegatingContext renamingDelegatingContext = new RenamingDelegatingContext(
getContext(), getContext(), "test.");
context = new MockContext(mockContentResolver, renamingDelegatingContext);
@Before
public void setUp() throws Exception {
// Set up the mock content resolver
CustomContentProvider customContentProvider = new CustomContentProvider();
customContentProvider.attachInfo(context, null);
mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, customContentProvider);
MockContentProvider settingsProvider = new MockContentProvider(context) {
@Override
public Bundle call(String method, String arg, Bundle extras) {
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return null;
}
ContentProvider customContentProvider = new CustomContentProvider() {
};
mockContentResolver.addProvider(Settings.AUTHORITY, settingsProvider);
// Set the context
setContext(context);
customContentProvider.attachInfo(context, null);
providerUtils = ContentProviderUtils.Factory.get(context);
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
// Let's use default values.
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);
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
}
@SmallTest
public void testStartable() {
startService(createStartIntent());
assertNotNull(getService());
@Test
public void testStartable() throws TimeoutException {
Intent intent = new Intent(context, TrackRecordingService.class);
mServiceRule.startService(createStartIntent());
Assert.assertNotNull(mServiceRule.bindService(createStartIntent()));
}
@MediumTest
public void testBindable() {
IBinder service = bindService(createStartIntent());
assertNotNull(service);
@Test
public void testBindable() throws TimeoutException {
IBinder service = mServiceRule.bindService(createStartIntent());
Assert.assertNotNull(service);
}
@MediumTest
@Test
public void testResumeAfterReboot_shouldResume() throws Exception {
// Insert a dummy track and mark it as recording track.
createDummyTrack(123L, 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(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();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
startService(startIntent);
assertNotNull(getService());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We expect to resume the previous track.
assertTrue(getService().isRecording());
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertEquals(123L, service.getRecordingTrackId());
Assert.assertTrue(service.isRecording());
Assert.assertEquals(123L, service.getRecordingTrackId());
}
/*
* TODO: shutdownService() has a bug and doesn't set mServiceCreated to false,
* thus preventing from a second call to onCreate(). Report the bug to Android
* team. Until then, the following tests and checks must be commented out.
*
* TODO: If fixed, remove "disabled" prefix from the test name.
*/
@MediumTest
public void disabledTestResumeAfterReboot_simulateReboot() throws Exception {
updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT,
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
@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()));
Assert.assertFalse(service.isRecording());
// Simulate recording a track.
long id = service.startNewTrack();
assertTrue(service.isRecording());
assertEquals(id, service.getRecordingTrackId());
shutdownService();
assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
Assert.assertTrue(service.isRecording());
Assert.assertEquals(id, service.getRecordingTrackId());
mServiceRule.unbindService();
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
startService(startIntent);
assertNotNull(getService());
mServiceRule.startService(startIntent);
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertNotNull(service);
assertTrue(getService().isRecording());
Assert.assertTrue(service.isRecording());
}
@MediumTest
@Test
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
// Insert a dummy track and mark it as recording track.
createDummyTrack(123L, System.currentTimeMillis(), false);
@@ -226,16 +155,16 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
startService(startIntent);
assertNotNull(getService());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because it was stopped.
assertFalse(getService().isRecording());
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertEquals(-1L, service.getRecordingTrackId());
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_expiredTrack() throws Exception {
// Insert a dummy track last updated 20 min ago.
createDummyTrack(123L, System.currentTimeMillis() - 20 * 60 * 1000, true);
@@ -247,16 +176,16 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
startService(startIntent);
assertNotNull(getService());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because it has expired.
assertFalse(getService().isRecording());
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertEquals(-1L, service.getRecordingTrackId());
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
// Insert a dummy track.
createDummyTrack(123L, System.currentTimeMillis(), true);
@@ -268,49 +197,52 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent();
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
startService(startIntent);
assertNotNull(getService());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because there were already
// too many attempts.
assertFalse(getService().isRecording());
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertEquals(-1L, service.getRecordingTrackId());
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_noTracks() throws Exception {
List<Track> tracks = providerUtils.getAllTracks();
assertTrue(tracks.isEmpty());
Assert.assertTrue(tracks.isEmpty());
ITrackRecordingService service = bindAndGetService(createStartIntent());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
// Test if we start in no-recording mode by default.
assertFalse(service.isRecording());
assertEquals(-1L, service.getRecordingTrackId());
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_oldTracks() throws Exception {
createDummyTrack(123L, -1L, false);
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
assertEquals(-1L, service.getRecordingTrackId());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testRecording_orphanedRecordingTrack() throws Exception {
// 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.
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
assertEquals(-1L, service.getRecordingTrackId());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
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).
@@ -320,51 +252,50 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
context.registerReceiver(startReceiver, new IntentFilter(startAction));
List<Track> tracks = providerUtils.getAllTracks();
assertTrue(tracks.isEmpty());
Assert.assertTrue(tracks.isEmpty());
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
long id = service.startNewTrack();
assertTrue(id >= 0);
assertTrue(service.isRecording());
Assert.assertTrue(id >= 0);
Assert.assertTrue(service.isRecording());
Track track = providerUtils.getTrack(id);
assertNotNull(track);
assertEquals(id, track.getId());
assertEquals(PreferencesUtils.getString(
context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT),
track.getCategory());
assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
assertEquals(id, service.getRecordingTrackId());
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.
assertTrue(startReceiver.waitUntilReceived(1));
Assert.assertTrue(startReceiver.waitUntilReceived(1));
List<Intent> receivedIntents = startReceiver.getReceivedIntents();
assertEquals(1, receivedIntents.size());
Assert.assertEquals(1, receivedIntents.size());
Intent broadcastIntent = receivedIntents.get(0);
assertEquals(startAction, broadcastIntent.getAction());
assertEquals(id,
broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
Assert.assertEquals(startAction, broadcastIntent.getAction());
Assert.assertEquals(id, broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
context.unregisterReceiver(startReceiver);
}
@MediumTest
@Test
public void testStartNewTrack_alreadyRecording() throws Exception {
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertTrue(service.isRecording());
// Starting a new track when there is a recording should just return -1L.
long newTrack = service.startNewTrack();
assertEquals(-1L, newTrack);
Assert.assertEquals(-1L, newTrack);
assertEquals(123L, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
assertEquals(123L, service.getRecordingTrackId());
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();
@@ -373,32 +304,31 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertTrue(service.isRecording());
// End the current track.
service.endCurrentTrack();
assertFalse(service.isRecording());
assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT,
PreferencesUtils.getLong(context, R.string.recording_track_id_key));
assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
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.
assertTrue(stopReceiver.waitUntilReceived(1));
Assert.assertTrue(stopReceiver.waitUntilReceived(1));
List<Intent> receivedIntents = stopReceiver.getReceivedIntents();
assertEquals(1, receivedIntents.size());
Assert.assertEquals(1, receivedIntents.size());
Intent broadcastIntent = receivedIntents.get(0);
assertEquals(stopAction, broadcastIntent.getAction());
assertEquals(123L,
broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
Assert.assertEquals(stopAction, broadcastIntent.getAction());
Assert.assertEquals(123L, broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L));
context.unregisterReceiver(stopReceiver);
}
@MediumTest
@Test
public void testEndCurrentTrack_noRecording() throws Exception {
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
/*
* Ending the current track when there is no recording should not result in
@@ -406,204 +336,196 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
*/
service.endCurrentTrack();
assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT,
PreferencesUtils.getLong(context, R.string.recording_track_id_key));
assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
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();
assertTrue(tracks.isEmpty());
Assert.assertTrue(tracks.isEmpty());
fullRecordingSession();
}
@MediumTest
@Test
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
assertEquals(-1L, waypointId);
Assert.assertEquals(-1L, waypointId);
}
@MediumTest
@Test
public void testInsertStatisticsMarker_validLocation() throws Exception {
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
assertFalse(service.isPaused());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertTrue(service.isRecording());
Assert.assertFalse(service.isPaused());
insertLocation(service);
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
Assert.assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
Waypoint wpt = providerUtils.getWaypoint(2);
assertEquals(getContext().getString(R.string.marker_statistics_icon_url), wpt.getIcon());
assertEquals(getContext().getString(R.string.marker_split_name_format, 1), wpt.getName());
assertEquals(WaypointType.STATISTICS, wpt.getType());
assertEquals(123L, wpt.getTrackId());
assertEquals(0.0, wpt.getLength());
assertNotNull(wpt.getLocation());
assertNotNull(wpt.getTripStatistics());
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(WaypointType.STATISTICS, wpt.getType());
Assert.assertEquals(123L, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
Assert.assertNotNull(wpt.getTripStatistics());
// TODO check the rest of the params.
// TODO: Check waypoint 2.
}
@MediumTest
@Test
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
assertEquals(-1L, waypointId);
Assert.assertEquals(-1L, waypointId);
}
@MediumTest
@Test
public void testInsertWaypointMarker_validWaypoint() throws Exception {
createDummyTrack(123L, -1L, true);
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertTrue(service.isRecording());
insertLocation(service);
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT));
Assert.assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT));
Waypoint wpt = providerUtils.getWaypoint(1);
assertEquals(getContext().getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
assertEquals(getContext().getString(R.string.marker_name_format, 1), wpt.getName());
assertEquals(WaypointType.WAYPOINT, wpt.getType());
assertEquals(123L, wpt.getTrackId());
assertEquals(0.0, wpt.getLength());
assertNotNull(wpt.getLocation());
assertNull(wpt.getTripStatistics());
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);
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);
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);
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);
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);
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));
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);
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);
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();
}
@MediumTest
public void testWithProperties_sensorTypeDefault() throws Exception {
PreferencesUtils.setString(
context, R.string.sensor_type_key, PreferencesUtils.SENSOR_TYPE_DEFAULT);
fullRecordingSession();
}
@MediumTest
public void testWithProperties_sensorTypeZephyr() throws Exception {
PreferencesUtils.setString(
context, R.string.sensor_type_key, context.getString(R.string.sensor_type_value_zephyr));
fullRecordingSession();
}
private ITrackRecordingService bindAndGetService(Intent intent) {
ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(bindService(intent));
assertNotNull(service);
return service;
}
private Track createDummyTrack(long id, long stopTime, boolean isRecording) {
Track dummyTrack = new Track();
dummyTrack.setId(id);
@@ -627,27 +549,26 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
}
private void addTrack(Track track, boolean isRecording) {
assertTrue(track.getId() >= 0);
Assert.assertTrue(track.getId() >= 0);
providerUtils.insertTrack(track);
assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key,
isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
Assert.assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
}
private void fullRecordingSession() throws Exception {
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent()));
Assert.assertFalse(service.isRecording());
// Start a track.
long id = service.startNewTrack();
assertTrue(id >= 0);
assertTrue(service.isRecording());
Assert.assertTrue(id >= 0);
Assert.assertTrue(service.isRecording());
Track track = providerUtils.getTrack(id);
assertNotNull(track);
assertEquals(id, track.getId());
assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
assertEquals(id, service.getRecordingTrackId());
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();
@@ -670,15 +591,15 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
assertFalse(service.isRecording());
assertEquals(-1L, service.getRecordingTrackId());
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
track = providerUtils.getTrack(id);
assertNotNull(track);
assertEquals(id, track.getId());
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
TripStatistics tripStatistics = track.getTripStatistics();
assertNotNull(tripStatistics);
assertTrue(tripStatistics.getStartTime() > 0);
assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
Assert.assertNotNull(tripStatistics);
Assert.assertTrue(tripStatistics.getStartTime() > 0);
Assert.assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
}
/**
@@ -686,8 +607,7 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
*
* @param trackRecordingService the track recording service
*/
private void insertLocation(ITrackRecordingService trackRecordingService)
throws RemoteException, InterruptedException {
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
Location location = new Location("gps");
location.setLongitude(35.0f);
location.setLatitude(45.0f);
@@ -699,30 +619,12 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
Thread.sleep(200);
}
/**
* A context wrapper with the user provided {@link ContentResolver}. TODO:
* Move to test utils package.
*/
public static class MockContext extends ContextWrapper {
private final ContentResolver contentResolver;
public MockContext(ContentResolver contentResolver, Context base) {
super(base);
this.contentResolver = contentResolver;
}
@Override
public ContentResolver getContentResolver() {
return contentResolver;
}
}
/**
* Synchronous/waitable broadcast receiver to be used in testing.
*/
private class BlockingBroadcastReceiver extends BroadcastReceiver {
private static final long MAX_WAIT_TIME_MS = 10000;
private List<Intent> receivedIntents = new ArrayList<Intent>();
private final List<Intent> receivedIntents = new ArrayList<>();
public List<Intent> getReceivedIntents() {
return receivedIntents;
@@ -16,10 +16,13 @@
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
@@ -30,9 +33,13 @@ import org.junit.runner.RunWith;
*/
@RunWith(AndroidJUnit4.class)
public class AnnouncementPeriodicTaskFactoryTest {
private Context context = ApplicationProvider.getApplicationContext();
@Test
public void testCreate() {
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
PeriodicTask task = factory.create(InstrumentationRegistry.getInstrumentation().getContext());
PeriodicTask task = factory.create(context);
Assert.assertTrue(task instanceof AnnouncementPeriodicTask);
}
}
@@ -21,24 +21,31 @@ import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import androidx.test.core.app.ApplicationProvider;
import org.easymock.Capture;
import org.easymock.EasyMock;
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 static com.google.android.testing.mocking.AndroidMock.capture;
import static com.google.android.testing.mocking.AndroidMock.eq;
import static com.google.android.testing.mocking.AndroidMock.expect;
import static com.google.android.testing.mocking.AndroidMock.same;
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}.
@@ -47,28 +54,31 @@ import static com.google.android.testing.mocking.AndroidMock.same;
*
* @author Rodrigo Damazio
*/
public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
@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 Capture<OnInitListener> initListenerCapture;
private Capture<PhoneStateListener> phoneListenerCapture;
private ArgumentCaptor<OnInitListener> initListenerCapture = ArgumentCaptor.forClass(OnInitListener.class);
private ArgumentCaptor<PhoneStateListener> phoneListenerCapture = ArgumentCaptor.forClass(PhoneStateListener.class);
private TextToSpeechDelegate ttsDelegate;
@Mock
private TextToSpeechInterface tts;
@UsesMocks({
AnnouncementPeriodicTask.class,
StringUtils.class,
})
@Override
protected void setUp() throws Exception {
super.setUp();
// @UsesMocks({
// StringUtils.class,
// })
@Before
public void setUp() throws Exception {
oldDefaultLocale = Locale.getDefault();
Locale.setDefault(DEFAULT_LOCALE);
@@ -84,7 +94,7 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
}
};
ttsDelegate = new TextToSpeechDelegate(getContext(), blockingListener);
ttsDelegate = new TextToSpeechDelegate(context, blockingListener);
// Wait for all async operations done in the constructor to finish.
synchronized (blockingListener) {
@@ -94,18 +104,10 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
}
}
// Phew, done, now we can start forwarding calls
tts = AndroidMock.createMock(TextToSpeechInterface.class);
initListenerCapture = new Capture<OnInitListener>();
phoneListenerCapture = new Capture<PhoneStateListener>();
// Create a partial forwarding mock
mockTask = AndroidMock.createMock(AnnouncementPeriodicTask.class, getContext());
task = new AnnouncementPeriodicTask(getContext()) {
task = new AnnouncementPeriodicTask(context) {
@Override
protected TextToSpeech newTextToSpeech(Context ctx,
OnInitListener onInitListener) {
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
return mockTask.newTextToSpeech(ctx, onInitListener);
}
@@ -115,116 +117,109 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
}
@Override
protected void listenToPhoneState(
PhoneStateListener listener, int events) {
protected void listenToPhoneState(PhoneStateListener listener, int events) {
mockTask.listenToPhoneState(listener, events);
}
};
}
@Override
protected void tearDown() {
@After
public void tearDown() {
Locale.setDefault(oldDefaultLocale);
}
@Test
public void testStart() {
doStart();
OnInitListener ttsInitListener = initListenerCapture.getValue();
assertNotNull(ttsInitListener);
AndroidMock.replay(tts);
Assert.assertNotNull(ttsInitListener);
ttsInitListener.onInit(TextToSpeech.SUCCESS);
AndroidMock.verify(mockTask, tts);
//TODO
//verify(mockTask, tts);
}
@Test
public void testStart_notReady() {
doStart();
OnInitListener ttsInitListener = initListenerCapture.getValue();
assertNotNull(ttsInitListener);
AndroidMock.replay(tts);
Assert.assertNotNull(ttsInitListener);
ttsInitListener.onInit(TextToSpeech.ERROR);
AndroidMock.verify(mockTask, tts);
//TODO
//AndroidMock.verify(mockTask, tts);
}
@Test
public void testShutdown() {
// First, start
doStart();
AndroidMock.verify(mockTask);
AndroidMock.reset(mockTask);
verify(mockTask);
reset(mockTask);
// Then, shut down
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
mockTask.listenToPhoneState(
same(phoneListener), eq(PhoneStateListener.LISTEN_NONE));
tts.shutdown();
AndroidMock.replay(mockTask, tts);
task.shutdown();
AndroidMock.verify(mockTask, tts);
//TODO
//AndroidMock.verify(mockTask, tts);
}
public void testRun() throws Exception {
// Expect service data calls
@Test
public void testRun() {
// when service data calls
TripStatistics stats = new TripStatistics();
// Expect announcement building call
expect(mockTask.getAnnouncement(same(stats))).andStubReturn(ANNOUNCEMENT);
// when announcement building call
when(mockTask.getAnnouncement(same(stats))).thenReturn(ANNOUNCEMENT);
// Put task in "ready" state
startTask(TextToSpeech.SUCCESS);
expect(tts.isLanguageAvailable(DEFAULT_LOCALE)).andStubReturn(TextToSpeech.LANG_AVAILABLE);
expect(tts.setLanguage(DEFAULT_LOCALE)).andReturn(TextToSpeech.LANG_AVAILABLE);
expect(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).andReturn(
TextToSpeech.SUCCESS);
expect(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) EasyMock.anyObject()))
.andReturn(0);
// Expect actual announcement call
expect(
tts.speak(eq(ANNOUNCEMENT), eq(TextToSpeech.QUEUE_FLUSH),
eq(AnnouncementPeriodicTask.SPEECH_PARAMS))).andReturn(0);
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
AndroidMock.replay(tts);
task.announce(stats);
AndroidMock.verify(mockTask, tts);
//TODO
//AndroidMock.verify(mockTask, tts);
}
public void testRun_notReady() throws Exception {
@Test
public void testRun_notReady() {
// Put task in "not ready" state
startTask(TextToSpeech.ERROR);
// Run the announcement
AndroidMock.replay(tts);
task.run(null);
AndroidMock.verify(mockTask, tts);
//TODO
//verify(mockTask, tts);
}
public void testRun_duringCall() throws Exception {
@Test
public void testRun_duringCall() {
startTask(TextToSpeech.SUCCESS);
expect(tts.isSpeaking()).andStubReturn(false);
when(tts.isSpeaking()).thenReturn(false);
// Run the announcement
AndroidMock.replay(tts);
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
task.run(null);
AndroidMock.verify(mockTask, tts);
//TODO
//verify(mockTask, tts);
}
public void testRun_ringWhileSpeaking() throws Exception {
@Test
public void testRun_ringWhileSpeaking() {
startTask(TextToSpeech.SUCCESS);
expect(tts.isSpeaking()).andStubReturn(true);
expect(tts.stop()).andReturn(TextToSpeech.SUCCESS);
AndroidMock.replay(tts);
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();
@@ -232,111 +227,118 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
// Run the announcement - this should do nothing.
task.run(null);
AndroidMock.verify(mockTask, tts);
//TODO
//AndroidMock.verify(mockTask, tts);
}
public void testRun_whileRinging() throws Exception {
@Test
public void testRun_whileRinging() {
startTask(TextToSpeech.SUCCESS);
expect(tts.isSpeaking()).andStubReturn(false);
when(tts.isSpeaking()).thenReturn(false);
// Run the announcement
AndroidMock.replay(tts);
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
task.run(null);
AndroidMock.verify(mockTask, tts);
//TODO
//AndroidMock.verify(mockTask, tts);
}
public void testRun_noService() throws Exception {
@Test
public void testRun_noService() {
startTask(TextToSpeech.SUCCESS);
// Run the announcement
AndroidMock.replay(tts);
task.run(null);
AndroidMock.verify(mockTask, tts);
//TODO
//AndroidMock.verify(mockTask, tts);
}
public void testRun_noStats() throws Exception {
// Expect service data calls
@Test
public void testRun_noStats() {
// when service data calls
startTask(TextToSpeech.SUCCESS);
// Run the announcement
AndroidMock.replay(tts);
task.run(null);
AndroidMock.verify(mockTask, tts);
//TODO
//AndroidMock.verify(mockTask, tts);
}
/**
* Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero.
*/
@Test
public void testGetAnnounceTime_time_zero() {
long time = 0; // 0 seconds
assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time));
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
assertEquals("0 minutes 1 second", task.getAnnounceTime(time));
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
assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time));
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
assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time));
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
assertEquals("1 minute 1 second", task.getAnnounceTime(time));
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
assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time));
Assert.assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time));
}
private void startTask(int state) {
AndroidMock.resetToNice(tts);
AndroidMock.replay(tts);
reset(tts);
doStart();
OnInitListener ttsInitListener = initListenerCapture.getValue();
ttsInitListener.onInit(state);
AndroidMock.resetToDefault(tts);
//TODO
reset(tts);
//AndroidMock.resetToDefault(tts);
}
private void doStart() {
mockTask.listenToPhoneState(capture(phoneListenerCapture),
eq(PhoneStateListener.LISTEN_CALL_STATE));
expect(mockTask.newTextToSpeech(
same(getContext()), capture(initListenerCapture)))
.andStubReturn(ttsDelegate);
AndroidMock.replay(mockTask);
mockTask.listenToPhoneState(phoneListenerCapture.capture(), eq(PhoneStateListener.LISTEN_CALL_STATE));
when(mockTask.newTextToSpeech(same(context), initListenerCapture.capture())).thenReturn(ttsDelegate);
task.start();
}
@@ -401,8 +403,7 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
* 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 {
private class TextToSpeechDelegate extends TextToSpeech implements TextToSpeechInterface {
public TextToSpeechDelegate(Context context, OnInitListener listener) {
super(context, listener);
}
@@ -464,8 +465,7 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
}
@Override
public int playEarcon(String earcon, int queueMode,
HashMap<String, String> params) {
public int playEarcon(String earcon, int queueMode, HashMap<String, String> params) {
if (tts == null) {
return super.playEarcon(earcon, queueMode, params);
}
@@ -473,8 +473,7 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
}
@Override
public int playSilence(long durationInMs, int queueMode,
HashMap<String, String> params) {
public int playSilence(long durationInMs, int queueMode, HashMap<String, String> params) {
if (tts == null) {
return super.playSilence(durationInMs, queueMode, params);
}
@@ -490,8 +489,7 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
}
@Override
public int setOnUtteranceCompletedListener(
OnUtteranceCompletedListener listener) {
public int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener) {
if (tts == null) {
return super.setOnUtteranceCompletedListener(listener);
}
@@ -541,8 +539,7 @@ public class AnnouncementPeriodicTaskTest extends AndroidTestCase {
}
@Override
public int synthesizeToFile(String text, HashMap<String, String> params,
String filename) {
public int synthesizeToFile(String text, HashMap<String, String> params, String filename) {
if (tts == null) {
return super.synthesizeToFile(text, params, filename);
}
@@ -16,10 +16,13 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.GregorianCalendar;
@@ -33,9 +36,13 @@ import java.util.TimeZone;
@RunWith(AndroidJUnit4.class)
public class StringUtilsTest {
private Context context = ApplicationProvider.getApplicationContext();
/**
* Tests {@link StringUtils#formatDateTimeIso8601(long)}.
*/
@Test
public void testFormatDateTimeIso8601() {
Assert.assertEquals("1970-01-01T00:00:12.345Z", StringUtils.formatDateTimeIso8601(12345));
}
@@ -43,6 +50,7 @@ public class StringUtilsTest {
/**
* Tests {@link StringUtils#formatElapsedTime(long)}.
*/
@Test
public void testformatElapsedTime() {
// 1 second
Assert.assertEquals("00:01", StringUtils.formatElapsedTime(1000));
@@ -63,6 +71,7 @@ public class StringUtilsTest {
/**
* Tests {@link StringUtils#formatElapsedTimeWithHour(long)}.
*/
@Test
public void testformatElapsedTimeWithHour() {
// 1 second
Assert.assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(1000));
@@ -84,20 +93,22 @@ public class StringUtilsTest {
* Tests {@link StringUtils#formatDistance(android.content.Context, double,
* boolean)}.
*/
@Test
public void testFormatDistance() {
// A large number in metric
Assert.assertEquals("5.00 km", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 5000, true));
Assert.assertEquals("5.00 km", StringUtils.formatDistance(context, 5000, true));
// A large number in imperial
Assert.assertEquals("3.11 mi", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 5000, false));
Assert.assertEquals("3.11 mi", StringUtils.formatDistance(context, 5000, false));
// A small number in metric
Assert.assertEquals("100.00 m", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 100, true));
Assert.assertEquals("100.00 m", StringUtils.formatDistance(context, 100, true));
// A small number in imperial
Assert.assertEquals("328.08 ft", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 100, false));
Assert.assertEquals("328.08 ft", StringUtils.formatDistance(context, 100, false));
}
/**
* Tests {@link StringUtils#formatCData(String)}.
*/
@Test
public void testFormatCData() {
Assert.assertEquals("<![CDATA[hello]]>", StringUtils.formatCData("hello"));
Assert.assertEquals("<![CDATA[hello]]]]><![CDATA[>there]]>", StringUtils.formatCData("hello]]>there"));
@@ -106,6 +117,7 @@ public class StringUtilsTest {
/**
* Tests {@link StringUtils#getTime(String)}.
*/
@Test
public void testGetTime() {
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
@@ -114,6 +126,7 @@ public class StringUtilsTest {
/**
* Tests {@link StringUtils#getTime(String)} with fractional seconds.
*/
@Test
public void testGetTime_fractional() {
assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300);
assertGetTime("2010-05-04T03:02:01.35", 2010, 5, 4, 3, 2, 1, 350);
@@ -124,6 +137,7 @@ public class StringUtilsTest {
/**
* Tests {@link StringUtils#getTime(String)} with time zone.
*/
@Test
public void testGetTime_timezone() {
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0);
@@ -138,6 +152,7 @@ public class StringUtilsTest {
* Tests {@link StringUtils#getTime(String)} with fractional seconds and time
* zone.
*/
@Test
public void testGetTime_fractionalAndTimezone() {
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
assertGetTime("2010-05-04T03:02:01.47+00:00", 2010, 5, 4, 3, 2, 1, 470);
@@ -169,6 +184,7 @@ public class StringUtilsTest {
/**
* Tests {@link StringUtils#getTimeParts(long)} with a positive number.
*/
@Test
public void testGetTimeParts_postive() {
int[] parts = StringUtils.getTimeParts(61000);
Assert.assertEquals(1, parts[0]);
@@ -179,6 +195,7 @@ public class StringUtilsTest {
/**
* Tests {@link StringUtils#getTimeParts(long)} with a negative number.
*/
@Test
public void testGetTimeParts_negative() {
int[] parts = StringUtils.getTimeParts(-61000);
Assert.assertEquals(-1, parts[0]);
@@ -18,18 +18,19 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import de.dennisguse.opentracks.R;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import de.dennisguse.opentracks.R;
/**
* Tests {@link TrackNameUtils}.
*
@@ -41,37 +42,36 @@ public class TrackNameUtilsTest {
private static final long TRACK_ID = 1L;
private static final long START_TIME = 1288213406000L;
private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext();
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
/**
* Tests when the track_name_key is
* settings_recording_track_name_date_local_value.
*/
@Test
public void testTrackName_date_local() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
Assert.assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME),
TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null));
Assert.assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
* Tests when the track_name_key is
* settings_recording_track_name_date_iso_8601_value.
*/
@Test
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
TrackNameUtils.ISO_8601_FORMAT, Locale.US);
Assert.assertEquals(simpleDateFormat.format(new Date(START_TIME)),
TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
Assert.assertEquals(simpleDateFormat.format(new Date(START_TIME)), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
* Tests when the track_name_key is
* settings_recording_track_name_number_value.
*/
@Test
public void testTrackName_number() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
Assert.assertEquals(
"Track " + TRACK_ID, TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null));
Assert.assertEquals("Track " + TRACK_ID, TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
}
+13 -21
View File
@@ -17,8 +17,8 @@ limitations under the License.
xmlns:tools="http://schemas.android.com/tools"
package="de.dennisguse.opentracks"
android:installLocation="auto"
android:versionCode="2916"
android:versionName="v3.0.0"> <!-- For FDroid. -->
android:versionCode="2991"
android:versionName="v3.1.0"> <!-- For FDroid. -->
<!-- Permission to initialize services -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -70,7 +70,8 @@ limitations under the License.
android:smallScreens="true"
android:xlargeScreens="true" />
<application
android:allowBackup="true"
android:name=".Startup"
android:allowBackup="false"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_logo"
android:label="@string/app_name"
@@ -82,7 +83,9 @@ limitations under the License.
android:name=".AggregatedStatsActivity"
android:label="@string/menu_aggregated_statistics" />
<activity android:name=".DeleteActivity" />
<activity android:name=".io.file.importer.ImportActivity">
<activity
android:name=".io.file.importer.ImportActivity"
android:theme="@style/ThemeCustomTransparent">
<intent-filter>
<action android:name="android.intent.action.ATTACH_DATA" />
<action android:name="android.intent.action.VIEW" />
@@ -118,11 +121,7 @@ limitations under the License.
</activity>
<activity
android:name=".TrackDetailActivity"
android:parentActivityName=".TrackListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".TrackListActivity" />
</activity>
android:screenOrientation="userPortrait" />
<activity android:name=".TrackEditActivity" />
<activity android:name=".TrackListActivity">
<intent-filter>
@@ -133,11 +132,10 @@ limitations under the License.
android:name="android.app.default_searchable"
android:value=".SearchListActivity" />
</activity>
<activity android:name=".io.file.exporter.SaveActivity" />
<activity android:name=".settings.AdvancedSettingsActivity" />
<activity android:name=".settings.ChartSettingsActivity" />
<activity
android:name=".io.file.exporter.ExportActivity"
android:theme="@style/ThemeCustomTransparent" />
<activity android:name=".settings.RecordingSettingsActivity" />
<activity android:name=".settings.SensorSettingsActivity" />
<activity android:name=".settings.SettingsActivity" />
<activity android:name=".settings.StatsSettingsActivity" />
<activity
@@ -178,16 +176,10 @@ limitations under the License.
</receiver>
<!-- Providers -->
<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"
android:authorities="${applicationId}.fileprovider"
@@ -16,11 +16,9 @@
package de.dennisguse.opentracks;
import android.app.ActionBar;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.view.Menu;
import android.view.MenuItem;
import androidx.fragment.app.FragmentActivity;
@@ -39,15 +37,6 @@ public abstract class AbstractActivity extends FragmentActivity {
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
setContentView(getLayoutResId());
// Configure action bar must be after setContentView
if (configureActionBarHomeAsUp()) {
ActionBar actionBar = this.getActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
}
/**
@@ -55,33 +44,9 @@ public abstract class AbstractActivity extends FragmentActivity {
*/
protected abstract int getLayoutResId();
/**
* Returns true to configure the action bar home button as the up button (go to previous activity).
*/
protected boolean configureActionBarHomeAsUp() {
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
TrackIconUtils.setMenuIconColor(menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handles actionBar's home menu item (arrow top left corner) and triggers, up navigation to previous activity.
if (item.getItemId() == android.R.id.home) {
onHomeSelected();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Callback when the home menu item is selected. E.g., setup the back stack when home is selected.
*/
protected void onHomeSelected() {
finish();
}
}
@@ -29,15 +29,14 @@ import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.SearchView;
import java.util.ArrayList;
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;
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
/**
* An abstract class for the following common tasks across
@@ -49,7 +48,7 @@ import java.util.ArrayList;
*
* @author Jimmy Shih
*/
public abstract class AbstractTrackActivity extends AbstractActivity implements ConfirmDeleteCaller {
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
protected static final int GPS_REQUEST_CODE = 6;
private static final int DELETE_REQUEST_CODE = 3;
@@ -27,10 +27,6 @@ public class Constants {
* Maximum number of waypoints that will be loaded at one time.
*/
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
/**
* The settings file name.
*/
public static final String SETTINGS_NAME = "SettingsActivity";
private Constants() {
}
@@ -31,7 +31,6 @@ 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.
@@ -121,6 +120,7 @@ public class MarkerEditActivity extends AbstractActivity {
} else {
saveMarker();
}
finish();
}
});
@@ -168,8 +168,7 @@ public class MarkerEditActivity extends AbstractActivity {
waypointDescription.getText().toString(),
null,
null);
TrackRecordingServiceConnectionUtils.addMarker(
this, trackRecordingServiceConnection, waypointCreationRequest);
TrackRecordingServiceConnectionUtils.addMarker(this, trackRecordingServiceConnection, waypointCreationRequest);
}
/**
@@ -49,7 +49,6 @@ import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarke
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.
@@ -129,7 +128,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
super.onCreate(savedInstanceState);
contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null;
@@ -173,7 +172,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
}
};
listView.setAdapter(resourceCursorAdapter);
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
AbstractListActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
LoaderManager.getInstance(this).initLoader(0, null, new LoaderCallbacks<Cursor>() {
@NonNull
@@ -232,7 +231,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);
AbstractListActivity.configureSearchWidget(this, searchMenuItem, null);
return super.onCreateOptionsMenu(menu);
}
@@ -284,10 +283,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:
@@ -17,12 +17,10 @@
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;
@@ -38,11 +36,17 @@ import android.widget.ListView;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
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.SearchEngineProvider;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
@@ -57,14 +61,6 @@ 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;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
/**
* An activity to display a list of search results.
@@ -73,7 +69,7 @@ import java.util.SortedSet;
* <p>
* TODO: allow to refine search (present search 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();
@@ -98,17 +94,11 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
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;
// 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;
@@ -136,6 +126,11 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
// 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
@@ -168,10 +163,9 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
contentProviderUtils = ContentProviderUtils.Factory.get(this);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
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
@@ -224,7 +218,7 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
startActivity(intent);
}
});
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
AbstractListActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
handleIntent(getIntent());
}
@@ -322,8 +316,7 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
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) + "");
startActivity(intent);
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_edit:
if (markerId != null) {
@@ -386,9 +379,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);
}
/**
@@ -420,10 +410,7 @@ public class SearchListActivity extends AbstractTrackActivity implements DeleteM
*/
@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.
*/
//TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the search.
String trackName = null;
long trackId = waypoint.getTrackId();
if (trackId != -1L) {
@@ -0,0 +1,37 @@
package de.dennisguse.opentracks;
import android.app.Application;
import android.os.StrictMode;
import android.util.Log;
import de.dennisguse.opentracks.util.PreferencesUtils;
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());
}
}
}
@@ -17,19 +17,20 @@
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.view.WindowManager;
import android.widget.ShareActionProvider;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
@@ -37,8 +38,6 @@ 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 de.dennisguse.opentracks.content.ContentProviderUtils;
@@ -53,7 +52,6 @@ import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment.Choos
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;
@@ -63,17 +61,13 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
import de.dennisguse.opentracks.util.TrackUtils;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* An activity to show the track detail.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class TrackDetailActivity extends AbstractTrackActivity implements ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
public class TrackDetailActivity extends AbstractListActivity implements ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
@@ -81,7 +75,6 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
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,6 +96,8 @@ 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() {
@@ -117,6 +112,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString());
long markerId = TrackRecordingServiceConnectionUtils.addMarker(TrackDetailActivity.this, trackRecordingServiceConnection, waypointCreationRequest);
if (markerId != -1L) {
//TODO: Make configurable.
FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri);
}
}
@@ -140,6 +136,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() {
@@ -193,7 +194,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
contentProviderUtils = ContentProviderUtils.Factory.get(this);
handleIntent(getIntent());
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
trackDataHub = TrackDataHub.newInstance(this);
@@ -222,6 +223,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();
@@ -275,14 +292,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);
}
@@ -292,20 +301,6 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
return R.layout.track_detail;
}
@Override
protected void onHomeSelected() {
/*
* TODO: Investigate
* According to
* http://developer.android.com/training/implementing-navigation
* /ancestral.html, we should use NavUtils.shouldUpRecreateTask instead of
* always creating a new back stack. However, NavUtils.shouldUpRecreateTask
* seems to always return false.
*/
TaskStackBuilder.create(this).addParentStack(TrackDetailActivity.class).startActivities();
finish();
}
@Override
public void onNewIntent(Intent intent) {
setIntent(intent);
@@ -322,8 +317,7 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
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));
shareActionProvider.setShareIntent(IntentUtils.newShareFileIntent(this, new long[]{trackId}));
voiceFrequencyMenuItem = menu.findItem(R.id.track_detail_voice_frequency);
splitFrequencyMenuItem = menu.findItem(R.id.track_detail_split_frequency);
@@ -353,7 +347,10 @@ public class TrackDetailActivity extends AbstractTrackActivity implements Choose
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)
@@ -385,29 +382,13 @@ 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));
if (file != null) {
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;
}
@@ -496,6 +477,12 @@ 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);
}
@@ -26,8 +26,6 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.StrictMode;
import android.provider.Settings;
import android.util.Log;
import android.view.ContextMenu;
@@ -43,6 +41,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.ShareActionProvider;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -58,11 +57,6 @@ import java.util.Locale;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment.FileTypeCaller;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.SaveActivity;
import de.dennisguse.opentracks.io.file.importer.ImportActivity;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
@@ -79,7 +73,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
*
* @author Leif Hendrik Wilden
*/
public class TrackListActivity extends AbstractTrackActivity implements FileTypeCaller, 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,
@@ -113,16 +107,16 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
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;
MenuItem shareMenuItem = menu.findItem(R.id.list_context_menu_share);
ShareActionProvider shareActionProvider = (ShareActionProvider) shareMenuItem.getActionProvider();
shareActionProvider.setShareIntent(trackIds.length == 0 ? null : IntentUtils.newShareFileIntent(TrackListActivity.this, trackIds));
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);
}
@@ -134,7 +128,7 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
};
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
@@ -175,8 +169,6 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
private MenuItem searchMenuItem;
private MenuItem startGpsMenuItem;
private MenuItem aggregatedStatisticsMenuItem;
private MenuItem exportAllMenuItem;
private MenuItem importAllMenuItem;
private MenuItem deleteAllMenuItem;
private final OnClickListener stopListener = new OnClickListener() {
@Override
@@ -256,20 +248,8 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
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 = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener);
@@ -324,7 +304,7 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
}
};
listView.setAdapter(resourceCursorAdapter);
AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
AbstractListActivity.configureListViewContextualMenu(listView, contextualActionModeCallback);
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
showStartupDialogs();
@@ -382,22 +362,15 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
return R.layout.track_list;
}
@Override
protected boolean configureActionBarHomeAsUp() {
return false;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_list, menu);
searchMenuItem = menu.findItem(R.id.track_list_search);
AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, trackController);
AbstractListActivity.configureSearchWidget(this, searchMenuItem, trackController);
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
aggregatedStatisticsMenuItem = menu.findItem(R.id.track_list_aggregated_statistics);
exportAllMenuItem = menu.findItem(R.id.track_list_export_all);
importAllMenuItem = menu.findItem(R.id.track_list_import_all);
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
return super.onCreateOptionsMenu(menu);
@@ -457,16 +430,6 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
intent = IntentUtils.newIntent(this, AggregatedStatsActivity.class);
startActivity(intent);
return true;
case R.id.track_list_export_all:
FileTypeDialogFragment.newInstance(R.id.track_list_export_all,
R.string.export_all_title, R.string.export_all_option)
.show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG);
return true;
case R.id.track_list_import_all:
FileTypeDialogFragment.newInstance(R.id.track_list_import_all,
R.string.import_selection_title, R.string.import_selection_option)
.show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG);
return true;
case R.id.track_list_delete_all:
deleteTracks(new long[]{-1L});
return true;
@@ -484,8 +447,7 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
getMenuInflater().inflate(R.menu.list_context_menu, menu);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
contextualActionModeCallback.onPrepare(
menu, new int[]{info.position}, new long[]{info.id}, false);
contextualActionModeCallback.onPrepare(menu, new int[]{info.position}, new long[]{info.id}, false);
}
@Override
@@ -522,26 +484,6 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
// Do nothing
}
@Override
public void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat) {
Intent intent;
switch (menuId) {
case R.id.track_list_export_all:
intent = IntentUtils.newIntent(this, SaveActivity.class)
.putExtra(SaveActivity.EXTRA_TRACK_IDS, new long[]{-1L})
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
startActivity(intent);
break;
case R.id.track_list_import_all:
intent = IntentUtils.newIntent(this, ImportActivity.class)
.putExtra(ImportActivity.EXTRA_IMPORT_ALL, true)
.putExtra(ImportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
startActivity(intent);
break;
default:
}
}
/**
* Shows start up dialogs.
*/
@@ -580,12 +522,6 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
if (aggregatedStatisticsMenuItem != null) {
aggregatedStatisticsMenuItem.setVisible(hasTrack);
}
if (exportAllMenuItem != null) {
exportAllMenuItem.setVisible(hasTrack && !isRecording);
}
if (importAllMenuItem != null) {
importAllMenuItem.setVisible(!isRecording);
}
if (deleteAllMenuItem != null) {
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
}
@@ -616,9 +552,8 @@ public class TrackListActivity extends AbstractTrackActivity implements FileType
*/
private boolean handleContextItem(int itemId, long[] trackIds) {
switch (itemId) {
case R.id.list_context_menu_share:
//TODO
Log.e(TAG, "Not implemented");
case R.id.list_context_menu_show_on_map:
IntentUtils.showTrackOnMap(this, trackIds);
return true;
case R.id.list_context_menu_edit:
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
@@ -0,0 +1,40 @@
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;
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);
}
@@ -22,12 +22,12 @@ import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import java.util.Iterator;
import java.util.List;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
/**
* Utilities to access data from the app's content provider.
*
@@ -36,10 +36,15 @@ import java.util.List;
public interface ContentProviderUtils {
/**
* The authority (the first part of the URI) for the app's content
* provider.
* The authority (the first part of the URI) for the app's content provider.
*/
String AUTHORITY = BuildConfig.APPLICATION_ID;
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.
*/
@@ -354,8 +359,7 @@ public interface ContentProviderUtils {
}
/**
* A factory which can produce instances of {@link ContentProviderUtils}, and
* can be overridden for testing.
* A factory which can produce instances of {@link ContentProviderUtils}, and can be overridden for testing.
*/
class Factory {
@@ -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
@@ -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,25 +28,19 @@ 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.
*
* @author Leif Hendrik Wilden
*/
public class CustomContentProvider extends ContentProvider {
public abstract class CustomContentProvider extends ContentProvider {
@VisibleForTesting
static final int DATABASE_VERSION = 23;
@@ -59,12 +52,12 @@ public class CustomContentProvider extends ContentProvider {
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 +270,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,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.net.Uri;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Data source on the phone.
@@ -37,7 +37,7 @@ public class DataSource {
public DataSource(Context context) {
contentResolver = context.getContentResolver();
sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
}
/**
@@ -17,8 +17,6 @@ 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.
@@ -31,12 +29,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.
@@ -20,12 +20,10 @@ import android.content.Context;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
import java.util.Vector;
/**
* @author Jimmy Shih
@@ -44,14 +42,14 @@ 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();
// Created by
String creator = html
? "<a href='http://" + context.getString(R.string.app_web_url) + "'>" + context.getString(R.string.app_name) + "</a>"
? "<a href='" + context.getString(R.string.app_web_url) + "'>" + context.getString(R.string.app_name) + "</a>"
: context.getString(R.string.app_name);
builder.append(creator);
@@ -20,10 +20,6 @@ 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;
@@ -31,6 +27,10 @@ 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.
*
@@ -381,6 +381,7 @@ public class SearchEngine {
public final Location currentLocation;
public final long currentTrackId;
public final long currentTimestamp;
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId,
long currentTimestamp) {
this.textQuery = textQuery.toLowerCase(Locale.getDefault());
@@ -397,11 +398,13 @@ public class SearchEngine {
public final Track track;
public final Waypoint waypoint;
public 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,210 @@
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}.
* <p>
* Moreover, it manages access to OpenTrack's database via {@link CustomContentProvider}.
* <p>
* 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_SENSORDATA = 2;
private static final int URI_KMZ_ONLY_TRACK = 3;
private static final int URI_KMZ_WITH_SENSORDATA = 4;
private static final int URI_KMZ_WITH_SENSORDATA_AND_PICTURES = 5;
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.KML_ONLY_TRACK.getName() + "/*", URI_KML_ONLY);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_SENSORDATA.getName() + "/*", URI_KML_WITH_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_SENSORDATA.getName() + "/*", URI_KMZ_WITH_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_SENSORDATA_AND_PICTURES.getName() + "/*", URI_KMZ_WITH_SENSORDATA_AND_PICTURES);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*", URI_GPX);
}
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_SENSORDATA:
return TrackFileFormat.KML_WITH_SENSORDATA;
case URI_KMZ_ONLY_TRACK:
return TrackFileFormat.KMZ_ONLY_TRACK;
case URI_KMZ_WITH_SENSORDATA:
return TrackFileFormat.KMZ_WITH_SENSORDATA;
case URI_KMZ_WITH_SENSORDATA_AND_PICTURES:
return TrackFileFormat.KMZ_WITH_SENSORDATA_AND_PICTURES;
default: //URI_KML_ONLY
return TrackFileFormat.KMZ_ONLY_TRACK;
}
}
@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(fileOutputStream);
} catch (IOException e) {
Log.w(TAG, "there occurred an error while sharing a file: " + e);
}
}
};
return openPipeHelper(uri, getType(uri), null, null, pipeDataWriter);
}
}
@@ -25,15 +25,15 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
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.
@@ -120,7 +120,7 @@ public class TrackDataHub implements DataSourceListener {
dataSourceManager = new DataSourceManager(dataSource, this);
notifyPreferenceChanged(null);
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
if (dataSourceManager != null) {
@@ -157,7 +157,7 @@ public class TrackDataHub implements DataSourceListener {
* @param trackId the track id
*/
public void loadTrack(final long trackId) {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
if (trackId == selectedTrackId) {
@@ -177,7 +177,7 @@ public class TrackDataHub implements DataSourceListener {
* @param trackDataTypes the track data types
*/
public void registerTrackDataListener(final TrackDataListener trackDataListener, final EnumSet<TrackDataType> trackDataTypes) {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
trackDataManager.registerListener(trackDataListener, trackDataTypes);
@@ -195,7 +195,7 @@ public class TrackDataHub implements DataSourceListener {
* @param trackDataListener the track data listener
*/
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
trackDataManager.unregisterListener(trackDataListener);
@@ -210,7 +210,7 @@ public class TrackDataHub implements DataSourceListener {
* Reloads data for a {@link TrackDataListener}.
*/
public void reloadDataForListener(final TrackDataListener trackDataListener) {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
loadDataForListener(trackDataListener);
@@ -235,7 +235,7 @@ public class TrackDataHub implements DataSourceListener {
@Override
public void notifyTracksTableUpdated() {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE));
@@ -245,7 +245,7 @@ public class TrackDataHub implements DataSourceListener {
@Override
public void notifyWaypointsTableUpdated() {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE));
@@ -255,7 +255,7 @@ public class TrackDataHub implements DataSourceListener {
@Override
public void notifyTrackPointsTableUpdated() {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
notifyTrackPointsTableUpdate(
@@ -267,7 +267,7 @@ public class TrackDataHub implements DataSourceListener {
@Override
public void notifyPreferenceChanged(final String key) {
runInHanderThread(new Runnable() {
runInHandlerThread(new Runnable() {
@Override
public void run() {
if (key == null
@@ -565,7 +565,7 @@ public class TrackDataHub implements DataSourceListener {
* @param runnable the runnable
*/
@VisibleForTesting
protected void runInHanderThread(Runnable runnable) {
protected 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());
@@ -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.
*
@@ -33,7 +31,7 @@ public interface TracksColumns extends BaseColumns {
/**
* Tracks provider uri.
*/
Uri CONTENT_URI = Uri.parse("content://" + BuildConfig.APPLICATION_ID + "/tracks");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/tracks");
/**
* Track content type.
@@ -19,8 +19,6 @@ package de.dennisguse.opentracks.content;
import android.net.Uri;
import android.provider.BaseColumns;
import de.dennisguse.opentracks.BuildConfig;
/**
* Constants for waypoints table.
*
@@ -29,7 +27,7 @@ import de.dennisguse.opentracks.BuildConfig;
public interface WaypointsColumns extends BaseColumns {
String TABLE_NAME = "waypoints";
Uri CONTENT_URI = Uri.parse("content://" + BuildConfig.APPLICATION_ID + "/waypoints");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/waypoints");
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
String DEFAULT_SORT_ORDER = "_id";
@@ -32,13 +32,13 @@ import android.widget.GridView;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
/**
* A DialogFragment to choose an activity type.
*
@@ -119,11 +119,6 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment {
return -1;
}
private static void updateWeightContainer(View weightContainer, int position) {
boolean showWeight = position == 0 || position == 1 || position == 2;
weightContainer.setVisibility(showWeight ? View.VISIBLE : View.GONE);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -23,10 +23,10 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import de.dennisguse.opentracks.R;
import java.util.List;
import de.dennisguse.opentracks.R;
/**
* Image adapter for choosing an activity type.
*
@@ -41,8 +41,7 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
private final int padding;
private int selected = -1;
public ChooseActivityTypeImageAdapter(
Context context, List<Integer> imageIds, int width, int height, int padding) {
public ChooseActivityTypeImageAdapter(Context context, List<Integer> imageIds, int width, int height, int padding) {
this.context = context;
this.imageIds = imageIds;
this.width = width;
@@ -23,9 +23,9 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.R;
/**
* A DialogFragment to select a file type, gpx, kml, etc.
@@ -35,14 +35,12 @@ import de.dennisguse.opentracks.R;
public class FileTypeDialogFragment extends AbstractDialogFragment {
public static final String FILE_TYPE_DIALOG_TAG = "fileType";
private static final String KEY_MENU_ID = "menuId";
private static final String KEY_TITLE_ID = "titleId";
private static final String KEY_OPTION_ID = "optionId";
private FileTypeCaller caller;
public static FileTypeDialogFragment newInstance(int menuId, int titleId, int optionId) {
public static FileTypeDialogFragment newInstance(int titleId, int optionId) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_MENU_ID, menuId);
bundle.putInt(KEY_TITLE_ID, titleId);
bundle.putInt(KEY_OPTION_ID, optionId);
@@ -65,22 +63,39 @@ public class FileTypeDialogFragment extends AbstractDialogFragment {
protected Dialog createDialog() {
int optionId = getArguments().getInt(KEY_OPTION_ID);
final int titleId = getArguments().getInt(KEY_TITLE_ID);
final int menuId = getArguments().getInt(KEY_MENU_ID);
TrackFileFormat[] trackFileFormats = TrackFileFormat.values();
final TrackFileFormat[] trackFileFormats = {TrackFileFormat.KML_WITH_SENSORDATA, TrackFileFormat.KMZ_WITH_SENSORDATA_AND_PICTURES, TrackFileFormat.GPX};
String[] choices = new String[trackFileFormats.length];
for (int i = 0; i < choices.length; i++) {
TrackFileFormat trackFileFormat = trackFileFormats[i];
choices[i] = getString(optionId, trackFileFormat.name(),
FileUtils.getPathDisplayName(trackFileFormat.getExtension()));
choices[i] = getString(optionId, trackFileFormat.getExtension().toUpperCase(), FileUtils.getPathDisplayName(trackFileFormat.getExtension()));
}
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
return new AlertDialog.Builder(getActivity())
.setNegativeButton(R.string.generic_cancel, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onDismissed();
}
})
.setPositiveButton(R.string.generic_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
caller.onFileTypeDone(menuId, TrackFileFormat.values()[position]);
caller.onFileTypeDone(trackFileFormats[position]);
}
}).setSingleChoiceItems(choices, 0, null).setTitle(titleId).create();
})
.setSingleChoiceItems(choices, 0, null)
.setTitle(titleId)
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
onDismissed();
}
protected void onDismissed() {
dismiss();
caller.onDismissed();
}
/**
@@ -93,6 +108,8 @@ public class FileTypeDialogFragment extends AbstractDialogFragment {
/**
* Called when file type selection is done.
*/
void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat);
void onFileTypeDone(TrackFileFormat trackFileFormat);
void onDismissed();
}
}
@@ -39,6 +39,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.MarkerEditActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
@@ -48,7 +49,6 @@ import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.R;
/**
* A fragment to show marker details.
@@ -190,8 +190,7 @@ public class MarkerDetailFragment extends Fragment {
Intent intent;
switch (item.getItemId()) {
case R.id.marker_detail_show_on_map:
intent = IntentUtils.newShowOnMapIntent(waypoint);
startActivity(intent);
IntentUtils.showCoordinateOnMap(getContext(), waypoint);
return true;
case R.id.marker_detail_edit:
intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
@@ -31,6 +31,9 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import java.util.EnumSet;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub;
@@ -40,6 +43,7 @@ import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.sensors.RemoteSensorManager;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -47,9 +51,6 @@ import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
import java.util.EnumSet;
/**
* A fragment to display track statistics to the user.
@@ -297,7 +298,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
}
/**
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.SensorManager}.
* Tries to fetch most recent {@link SensorDataSet} {@link RemoteSensorManager}.
*/
private void updateSensorDataUI() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
@@ -4,30 +4,124 @@ import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.io.file.exporter.TrackExporterListener;
import de.dennisguse.opentracks.io.file.exporter.TrackWriter;
import de.dennisguse.opentracks.R;
import java.util.Locale;
/**
* Definition of all possible track formats.
*/
public enum TrackFileFormat implements Parcelable {
KML {
KML_ONLY_TRACK {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) {
return new KmlTrackWriter(context, multiple, playTrack);
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, false, false, false);
}
@Override
public String getMimeType() {
return MIME_KML;
}
public String getExtension() {
return "kml";
}
},
KML_WITH_SENSORDATA {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, false);
}
@Override
public String getMimeType() {
return MIME_KML;
}
public String getExtension() {
return "kml";
}
},
KMZ_ONLY_TRACK {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, false, false, false);
}
@Override
public String getMimeType() {
return MIME_KMZ;
}
public String getExtension() {
return "kmz";
}
},
KMZ_WITH_SENSORDATA {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, false);
}
@Override
public String getMimeType() {
return MIME_KMZ;
}
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener);
}
public String getExtension() {
return "kmz";
}
},
KMZ_WITH_SENSORDATA_AND_PICTURES {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, true);
}
@Override
public String getMimeType() {
return MIME_KMZ;
}
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener);
}
public String getExtension() {
return "kmz";
}
},
GPX {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) {
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new GpxTrackWriter(context.getString(R.string.app_name));
}
@Override
public String getMimeType() {
return "application/gpx+xml";
}
public String getExtension() {
return "gpx";
}
};
@Deprecated
public static final Creator<TrackFileFormat> CREATOR = new Creator<TrackFileFormat>() {
@Override
public TrackFileFormat createFromParcel(final Parcel source) {
@@ -50,26 +144,46 @@ public enum TrackFileFormat implements Parcelable {
dest.writeInt(ordinal());
}
/**
* Creates a new track writer for the format.
*
* @param context the context
* @param multiple true for writing multiple tracks
* @param playTrack true to play track
*/
public abstract TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack);
private static final String MIME_KMZ = "application/vnd.google-earth.kmz";
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1);
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
}
/**
* Returns the mime type for each format.
*/
public String getMimeType() {
return "application/" + getExtension() + "+xml";
public abstract String getMimeType();
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener) {
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks);
}
/**
* Creates a new track writer for the format.
*
* @param context the context
* @param multiple true for writing multiple tracks
*/
public abstract TrackWriter newTrackWriter(Context context, boolean multiple);
/**
* Returns the file extension for each format.
*/
public String getExtension() {
public abstract String getExtension();
/**
* Returns the name of for each format.
*/
public String getName() {
return this.name().toLowerCase(Locale.US);
}
}
@@ -16,22 +16,26 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.app.Activity;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentActivity;
import java.io.File;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.R;
import java.io.File;
/**
* An activity for saving tracks to the external storage. If saving a specific
@@ -39,23 +43,16 @@ import java.io.File;
*
* @author Rodrigo Damazio
*/
public class SaveActivity extends Activity {
public class ExportActivity extends FragmentActivity implements FileTypeDialogFragment.FileTypeCaller {
public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format";
public static final String EXTRA_TRACK_IDS = "track_ids";
@Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed.
public static final String EXTRA_PLAY_TRACK = "play_track";
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_RESULT_ID = 1;
private TrackFileFormat trackFileFormat;
private long[] trackIds;
private boolean playTrack;
private String directoryDisplayName;
private SaveAsyncTask saveAsyncTask;
private ExportAsyncTask exportAsyncTask;
private ProgressDialog progressDialog;
// the number of tracks successfully saved
@@ -64,55 +61,67 @@ public class SaveActivity extends Activity {
// the number of tracks to save
private int totalCount;
// the last successfully saved path
private String savedPath;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS);
playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false);
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);
} else {
fileTypeDialogStart();
}
}
@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 {
fileTypeDialogStart();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void fileTypeDialogStart() {
FileTypeDialogFragment
.newInstance(R.string.export_all_title, R.string.export_all_option)
.show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG);
}
@Override
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
if (!FileUtils.isExternalStorageWriteable()) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
finish();
return;
}
File directory = playTrack ? new File(getCacheDir(), FileUtils.PLAY_TRACKS_DIR)
: new File(FileUtils.getPath(trackFileFormat.getExtension()));
File directory = new File(FileUtils.getPath(trackFileFormat.getExtension()));
if (!FileUtils.ensureDirectoryExists(directory)) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
finish();
return;
}
if (playTrack) {
for (File file : directory.listFiles()) {
file.delete();
}
}
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
directoryDisplayName = playTrack ? directory.getName()
: FileUtils.getPathDisplayName(trackFileFormat.getExtension());
Object retained = getLastNonConfigurationInstance();
if (retained instanceof SaveAsyncTask) {
saveAsyncTask = (SaveAsyncTask) retained;
saveAsyncTask.setActivity(this);
} else {
saveAsyncTask = new SaveAsyncTask(this, trackIds, trackFileFormat, playTrack, directory);
saveAsyncTask.execute();
}
//TODO (still needed?): getLastNonConfiguration instance returned ExportAsyncTask before
// Object retained = getLastNonConfigurationInstance();
// if (retained instanceof ExportAsyncTask) {
// exportAsyncTask = (ExportAsyncTask) retained;
// exportAsyncTask.setActivity(this);
// } else {
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, directory);
exportAsyncTask.execute();
// }
}
@Override
public Object onRetainNonConfigurationInstance() {
saveAsyncTask.setActivity(null);
return saveAsyncTask;
public void onDismissed() {
finish();
}
@Override
@@ -124,8 +133,9 @@ public class SaveActivity extends Activity {
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
saveAsyncTask.cancel(true);
exportAsyncTask.cancel(true);
dialog.dismiss();
onDismissed();
}
}, directoryDisplayName);
return progressDialog;
@@ -152,35 +162,21 @@ public class SaveActivity extends Activity {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
finish();
onDismissed();
}
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
dialog.dismiss();
finish();
onDismissed();
}
}).setTitle(titleId);
if (!playTrack && trackIds.length == 1 && trackIds[0] != -1L && successCount == totalCount
&& savedPath != null) {
builder.setNegativeButton(
R.string.share_track_share_file, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = IntentUtils.newShareFileIntent(
SaveActivity.this, trackIds[0], savedPath, trackFileFormat);
startActivity(
Intent.createChooser(intent, getString(R.string.share_track_picker_title)));
finish();
}
});
}
final Dialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
DialogUtils.setDialogTitleDivider(SaveActivity.this, dialog);
DialogUtils.setDialogTitleDivider(ExportActivity.this, dialog);
}
});
return dialog;
@@ -194,12 +190,10 @@ public class SaveActivity extends Activity {
*
* @param aSuccessCount the number of tracks successfully saved
* @param aTotalCount the number of tracks to save
* @param aSavedPath the last successfully saved path
*/
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, String aSavedPath) {
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) {
successCount = aSuccessCount;
totalCount = aTotalCount;
savedPath = aSavedPath;
removeDialog(DIALOG_PROGRESS_ID);
showDialog(DIALOG_RESULT_ID);
}
@@ -22,6 +22,12 @@ import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
@@ -29,29 +35,21 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
import de.dennisguse.opentracks.R;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Async Task to save tracks to the external storage.
*
* @author Jimmy Shih
*/
public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
//TODO Make independent from ExportActivity?
public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = SaveAsyncTask.class.getSimpleName();
private final long[] trackIds;
private static final String TAG = ExportAsyncTask.class.getSimpleName();
private final TrackFileFormat trackFileFormat;
@Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed.
private final boolean playTrack;
private final File directory;
private final Context context;
private final ContentProviderUtils contentProviderUtils;
private SaveActivity saveActivity;
private ExportActivity exportActivity;
private WakeLock wakeLock;
// true if the AsyncTask has completed
@@ -69,20 +67,15 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
/**
* Creates an AsyncTask.
*
* @param saveActivity the activity currently associated with this task
* @param trackIds the track ids to save. To save all, set to size 1 with
* trackIds[0] == -1L
* @param exportActivity the activity currently associated with this task
* @param trackFileFormat the track file format
* @param playTrack true to play track
* @param directory the directory to write the file
*/
public SaveAsyncTask(SaveActivity saveActivity, long[] trackIds, TrackFileFormat trackFileFormat, boolean playTrack, File directory) {
this.saveActivity = saveActivity;
this.trackIds = trackIds;
public ExportAsyncTask(ExportActivity exportActivity, TrackFileFormat trackFileFormat, File directory) {
this.exportActivity = exportActivity;
this.trackFileFormat = trackFileFormat;
this.playTrack = playTrack;
this.directory = directory;
context = saveActivity.getApplicationContext();
context = exportActivity.getApplicationContext();
contentProviderUtils = ContentProviderUtils.Factory.get(context);
completed = false;
@@ -94,19 +87,19 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
/**
* Sets the current activity associated with this AsyncTask.
*
* @param saveActivity the current activity, can be null
* @param exportActivity the current activity, can be null
*/
public void setActivity(SaveActivity saveActivity) {
this.saveActivity = saveActivity;
if (completed && saveActivity != null) {
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
public void setActivity(ExportActivity exportActivity) {
this.exportActivity = exportActivity;
if (completed && exportActivity != null) {
exportActivity.onAsyncTaskCompleted(successCount, totalCount);
}
}
@Override
protected void onPreExecute() {
if (saveActivity != null) {
saveActivity.showProgressDialog();
if (exportActivity != null) {
exportActivity.showProgressDialog();
}
}
@@ -114,31 +107,13 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected Boolean doInBackground(Void... params) {
try {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
boolean isRecording = PreferencesUtils.getLong(saveActivity, R.string.recording_track_id_key) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
boolean isPaused = PreferencesUtils.getBoolean(saveActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
boolean isRecording = PreferencesUtils.getLong(exportActivity, R.string.recording_track_id_key) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
boolean isPaused = PreferencesUtils.getBoolean(exportActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
// Get the wake lock if not recording or paused
if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(saveActivity, wakeLock);
}
if (trackIds.length == 1 && trackIds[0] == -1L) {
return saveAllTracks();
} else {
totalCount = 1;
Track[] tracks = new Track[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
if (tracks[i] == null) {
Log.d(TAG, "No track for " + trackIds[i]);
return false;
}
}
if (saveTracks(tracks)) {
successCount = 1;
return true;
} else {
return false;
}
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
}
return saveAllTracks();
} finally {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
@@ -148,29 +123,29 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onProgressUpdate(Integer... values) {
if (saveActivity != null) {
saveActivity.setProgressDialogValue(values[0], values[1]);
if (exportActivity != null) {
exportActivity.setProgressDialogValue(values[0], values[1]);
}
}
@Override
protected void onPostExecute(Boolean result) {
completed = true;
if (saveActivity != null) {
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
if (exportActivity != null) {
exportActivity.onAsyncTaskCompleted(successCount, totalCount);
}
}
@Override
protected void onCancelled() {
completed = true;
if (saveActivity != null) {
saveActivity.onAsyncTaskCompleted(successCount, totalCount, null);
if (exportActivity != null) {
exportActivity.onAsyncTaskCompleted(successCount, totalCount);
}
}
/**
* Saves tracks to one file.
* Saves tracks to one file (uses first track to determine the filename).
*
* @param tracks the tracks
*/
@@ -179,32 +154,23 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
return false;
}
TrackExporterListener trackExporterListener = new TrackExporterListener() {
@Override
public void onProgressUpdate(int number, int max) {
//Update the progress dialog once every 500 points.
if (number % 500 == 0) {
publishProgress(number, max);
}
}
};
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, tracks, trackExporterListener);
Track track = tracks[0];
boolean useKmz = trackFileFormat == TrackFileFormat.KML && !playTrack;
String extension = useKmz ? KmzTrackExporter.KMZ_EXTENSION : trackFileFormat.getExtension();
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, tracks,
trackFileFormat.newTrackWriter(context, tracks.length > 1, playTrack),
new TrackExporterListener() {
@Override
public void onProgressUpdate(int number, int max) {
/*
* If only saving one track, update the progress dialog once every
* 500 points
*/
if (trackIds.length == 1 && trackIds[0] != -1L && number % 500 == 0) {
publishProgress(number, max);
}
}
});
TrackExporter trackExporter = useKmz ? new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, context) : fileTrackExporter;
String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), extension);
String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension());
File file = new File(directory, fileName);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
if (trackExporter.writeTrack(fileOutputStream)) {
savedPath = file.getAbsolutePath();
return true;
@@ -218,14 +184,9 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
} catch (FileNotFoundException e) {
Log.e(TAG, "Unable to open file " + file.getName(), e);
return false;
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close file output stream", e);
}
}
} catch (IOException e) {
Log.e(TAG, "Unable to close file output stream", e);
return false;
}
}
@@ -20,6 +20,8 @@ import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.io.OutputStream;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
@@ -28,8 +30,6 @@ import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.util.LocationUtils;
import java.io.OutputStream;
/**
* Track Writer for writing tracks to an {@link OutputStream}.
*
@@ -49,12 +49,11 @@ public class FileTrackExporter implements TrackExporter {
* Constructor.
*
* @param contentProviderUtils the content provider utils
* @param tracks the tracks
* @param trackWriter the track writer
* @param tracks the tracks
* @param trackExporterListener the track export listener
*/
public FileTrackExporter(ContentProviderUtils contentProviderUtils, Track[] tracks,
TrackWriter trackWriter, TrackExporterListener trackExporterListener) {
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener) {
this.contentProviderUtils = contentProviderUtils;
this.tracks = tracks;
this.trackWriter = trackWriter;
@@ -95,8 +94,7 @@ public class FileTrackExporter implements TrackExporter {
* load them into objects all at the same time.
*/
boolean hasWaypoints = false;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(
track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS)) {
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as the
@@ -132,8 +130,7 @@ public class FileTrackExporter implements TrackExporter {
LocationIterator locationIterator = null;
try {
locationIterator = contentProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, locationFactory);
while (locationIterator.hasNext()) {
if (Thread.interrupted()) {
@@ -177,6 +174,11 @@ public class FileTrackExporter implements TrackExporter {
isLastLocationValid = isLocationValid;
}
if (wroteSegment) {
//Close the last segment
trackWriter.writeCloseSegment();
}
if (wroteTrack) {
Location lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
setLocationTime(lastValidTrackPoint, offset);
@@ -18,16 +18,15 @@ package de.dennisguse.opentracks.io.file.exporter;
import android.location.Location;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.StringUtils;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Locale;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.util.StringUtils;
/**
* Write track as GPX to a file.
*
@@ -59,11 +58,6 @@ public class GpxTrackWriter implements TrackWriter {
this.creator = creator;
}
@Override
public String getExtension() {
return TrackFileFormat.GPX.getExtension();
}
@Override
public void prepare(OutputStream outputStream) {
this.printWriter = new PrintWriter(outputStream);
@@ -83,11 +77,9 @@ public class GpxTrackWriter implements TrackWriter {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
printWriter.println("<gpx");
printWriter.println("version=\"1.1\"");
printWriter.println(
"creator=\"" + creator + "\"");
printWriter.println("creator=\"" + creator + "\"");
printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\"");
printWriter.println(
"xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1"
+ " http://www.topografix.com/GPX/1/1/gpx.xsd"
@@ -20,8 +20,11 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import androidx.annotation.VisibleForTesting;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
@@ -30,14 +33,7 @@ import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.R;
import java.io.File;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* Write track as KML to a file.
@@ -56,20 +52,17 @@ public class KmlTrackWriter implements TrackWriter {
private static final String HEART_RATE = "heart_rate";
private static final String POWER = "power";
private static final String
WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
private static final String
STATISTICS_ICON = "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";
private static final String
START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
private static final String
END_ICON = "http://maps.google.com/mapfiles/kml/paddle/red-circle.png";
private static final String
TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private static final String WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
private static final String STATISTICS_ICON = "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
private static final String END_ICON = "http://maps.google.com/mapfiles/kml/paddle/red-circle.png";
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private final Context context;
private final boolean multiple;
private final boolean playTrack;
private final boolean hasMultipleTracks;
private final boolean exportPictures;
private final boolean exportTrackDetails;
private final boolean exportSensorData;
private final DescriptionGenerator descriptionGenerator;
private final ContentProviderUtils contentProviderUtils;
@@ -81,24 +74,25 @@ public class KmlTrackWriter implements TrackWriter {
private boolean hasCadence;
private boolean hasHeartRate;
public KmlTrackWriter(Context context, boolean multiple, boolean playTrack) {
this(context, multiple, playTrack, new DescriptionGeneratorImpl(context));
}
private Location startLocation;
@VisibleForTesting
KmlTrackWriter(Context context, boolean multiple, boolean playTrack, DescriptionGenerator descriptionGenerator) {
/**
* @param context the context
* @param hasMultipleTracks should encode multiple tracks into one file?
* @param exportPictures should pictures be exported (if true: exports to KMZ)?
* @param exportTrackDetails should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
* @param exportSensorData should {@link SensorDataSet} be exported?
*/
public KmlTrackWriter(Context context, boolean hasMultipleTracks, boolean exportTrackDetails, boolean exportSensorData, boolean exportPictures) {
this.context = context;
this.multiple = multiple;
this.playTrack = playTrack;
this.descriptionGenerator = descriptionGenerator;
this.hasMultipleTracks = hasMultipleTracks;
this.exportTrackDetails = exportTrackDetails;
this.exportSensorData = exportSensorData;
this.exportPictures = exportPictures;
this.descriptionGenerator = new DescriptionGeneratorImpl(context);
this.contentProviderUtils = ContentProviderUtils.Factory.get(context);
}
@Override
public String getExtension() {
return TrackFileFormat.KML.getExtension();
}
@Override
public void prepare(OutputStream outputStream) {
this.printWriter = new PrintWriter(outputStream);
@@ -122,20 +116,25 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<Document>");
printWriter.println("<open>1</open>");
printWriter.println("<visibility>1</visibility>");
Track track = tracks[0];
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<atom:author><atom:name>"
+ StringUtils.formatCData(context.getString(R.string.app_name))
+ "</atom:name></atom:author>");
if (exportTrackDetails) {
Track track = tracks[0];
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
}
printWriter.println("<atom:author><atom:name>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:name></atom:author>");
writeTrackStyle();
writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1);
writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1);
writePlacemarkerStyle(STATISTICS_STYLE, STATISTICS_ICON, 20, 2);
writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2);
printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">");
writeSensorStyle(POWER, context.getString(R.string.description_sensor_power));
writeSensorStyle(CADENCE, context.getString(R.string.description_sensor_cadence));
writeSensorStyle(HEART_RATE, context.getString(R.string.description_sensor_heart_rate));
if (exportSensorData) {
writeSensorStyle(POWER, context.getString(R.string.description_sensor_power));
writeSensorStyle(CADENCE, context.getString(R.string.description_sensor_cadence));
writeSensorStyle(HEART_RATE, context.getString(R.string.description_sensor_heart_rate));
}
printWriter.println("</Schema>");
}
}
@@ -151,9 +150,10 @@ public class KmlTrackWriter implements TrackWriter {
@Override
public void writeBeginWaypoints(Track track) {
if (printWriter != null) {
printWriter.println("<Folder><name>"
+ StringUtils.formatCData(context.getString(R.string.track_markers, track.getName()))
+ "</name>");
printWriter.println("<Folder>");
if (exportTrackDetails) {
printWriter.println("<name>" + StringUtils.formatCData(context.getString(R.string.track_markers, track.getName())) + "</name>");
}
printWriter.println("<open>1</open>");
}
}
@@ -167,7 +167,7 @@ public class KmlTrackWriter implements TrackWriter {
@Override
public void writeWaypoint(Waypoint waypoint) {
if (printWriter != null) {
if (printWriter != null && exportTrackDetails) {
String styleName = waypoint.getType() == WaypointType.STATISTICS ? STATISTICS_STYLE : WAYPOINT_STYLE;
if (waypoint.hasPhoto()) {
float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation());
@@ -180,7 +180,7 @@ public class KmlTrackWriter implements TrackWriter {
@Override
public void writeBeginTracks() {
if (printWriter != null && multiple) {
if (printWriter != null && hasMultipleTracks) {
printWriter.println("<Folder id=tour>");
printWriter.println("<name>" + context.getString(R.string.generic_tracks) + "</name>");
printWriter.println("<open>1</open>");
@@ -189,20 +189,20 @@ public class KmlTrackWriter implements TrackWriter {
@Override
public void writeEndTracks() {
if (printWriter != null && multiple) {
if (printWriter != null && hasMultipleTracks) {
printWriter.println("</Folder>");
}
}
@Override
public void writeBeginTrack(Track track, Location startLocation) {
this.startLocation = startLocation;
if (printWriter != null) {
String name = context.getString(R.string.marker_label_start, track.getName());
writePlacemark(name, "", "", START_STYLE, startLocation);
printWriter.println("<Placemark>");
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println(
"<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
printWriter.println("<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
writeCategory(track.getCategory());
printWriter.println("<gx:MultiTrack>");
@@ -217,7 +217,7 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("</gx:MultiTrack>");
printWriter.println("</Placemark>");
String name = context.getString(R.string.marker_label_end, track.getName());
String description = descriptionGenerator.generateTrackDescription(track, null, null, false);
String description = descriptionGenerator.generateTrackDescription(track, false);
writePlacemark(name, "", description, END_STYLE, endLocation);
}
}
@@ -240,14 +240,16 @@ public class KmlTrackWriter implements TrackWriter {
if (printWriter != null) {
printWriter.println("<ExtendedData>");
printWriter.println("<SchemaData schemaUrl=\"#" + SCHEMA_ID + "\">");
if (hasPower) {
writeSensorData(powerList, POWER);
}
if (hasCadence) {
writeSensorData(cadenceList, CADENCE);
}
if (hasHeartRate) {
writeSensorData(heartRateList, HEART_RATE);
if (exportSensorData) {
if (hasPower) {
writeSensorData(powerList, POWER);
}
if (hasCadence) {
writeSensorData(cadenceList, CADENCE);
}
if (hasHeartRate) {
writeSensorData(heartRateList, HEART_RATE);
}
}
printWriter.println("</SchemaData>");
printWriter.println("</ExtendedData>");
@@ -258,10 +260,13 @@ public class KmlTrackWriter implements TrackWriter {
@Override
public void writeLocation(Location location) {
if (printWriter != null) {
printWriter.println(
"<when>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</when>");
if (exportTrackDetails) {
printWriter.println("<when>" + getTime(location) + "</when>");
}
printWriter.println("<gx:coord>" + getCoordinates(location, " ") + "</gx:coord>");
if (location instanceof SensorDataSetLocation) {
if (exportSensorData && location instanceof SensorDataSetLocation) {
SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet();
if (sensorDataSet != null) {
if (sensorDataSet.hasHeartRate()) {
@@ -301,15 +306,12 @@ public class KmlTrackWriter implements TrackWriter {
* @param styleName the style name
* @param location the location
*/
private void writePlacemark(
String name, String category, String description, String styleName, Location location) {
if (location != null) {
private void writePlacemark(String name, String category, String description, String styleName, Location location) {
if (location != null && exportTrackDetails) {
printWriter.println("<Placemark>");
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
printWriter.println(
"<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.println("<TimeStamp><when>"
+ StringUtils.formatDateTimeIso8601(location.getTime()) + "</when></TimeStamp>");
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.println("<TimeStamp><when>" + getTime(location) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
writeCategory(category);
printWriter.println("<Point>");
@@ -330,13 +332,11 @@ public class KmlTrackWriter implements TrackWriter {
* @param photoUrl the photo url
* @param heading the heading
*/
private void writePhotoOverlay(String name, String category, String description, String styleName,
Location location, String photoUrl, float heading) {
if (location != null) {
private void writePhotoOverlay(String name, String category, String description, String styleName, Location location, String photoUrl, float heading) {
if (location != null && exportTrackDetails) {
printWriter.println("<PhotoOverlay>");
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
printWriter.println(
"<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.print("<Camera>");
printWriter.print("<longitude>" + location.getLongitude() + "</longitude>");
printWriter.print("<latitude>" + location.getLatitude() + "</latitude>");
@@ -344,16 +344,12 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.print("<heading>" + heading + "</heading>");
printWriter.print("<tilt>90</tilt>");
printWriter.println("</Camera>");
printWriter.println("<TimeStamp><when>"
+ StringUtils.formatDateTimeIso8601(location.getTime()) + "</when></TimeStamp>");
printWriter.println("<TimeStamp><when>" + getTime(location) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
writeCategory(category);
if (playTrack) {
if (exportPictures) {
printWriter.println("<Icon><href>" + Uri.decode(photoUrl) + "</href></Icon>");
} else {
Uri uri = Uri.parse(photoUrl);
printWriter.println("<Icon><href>" + KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar
+ uri.getLastPathSegment() + "</href></Icon>");
}
printWriter.print("<ViewVolume>");
@@ -370,6 +366,19 @@ public class KmlTrackWriter implements TrackWriter {
}
}
/**
* Returns the formatted time of the location; either absolute or relative depending exportTrackDetails.
*
* @param location the location
*/
private String getTime(Location location) {
if (exportTrackDetails) {
return StringUtils.formatDateTimeIso8601(location.getTime());
} else {
return StringUtils.formatDateTimeIso8601(location.getTime() - startLocation.getTime());
}
}
/**
* Gets the heading to a location.
*
@@ -411,8 +420,7 @@ public class KmlTrackWriter implements TrackWriter {
return;
}
printWriter.println("<ExtendedData>");
printWriter.println(
"<Data name=\"type\"><value>" + StringUtils.formatCData(category) + "</value></Data>");
printWriter.println("<Data name=\"type\"><value>" + StringUtils.formatCData(category) + "</value></Data>");
printWriter.println("</ExtendedData>");
}
@@ -441,8 +449,7 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<Style id=\"" + name + "\"><IconStyle>");
printWriter.println("<scale>1.3</scale>");
printWriter.println("<Icon><href>" + url + "</href></Icon>");
printWriter.println(
"<hotSpot x=\"" + x + "\" y=\"" + y + "\" xunits=\"pixels\" yunits=\"pixels\"/>");
printWriter.println("<hotSpot x=\"" + x + "\" y=\"" + y + "\" xunits=\"pixels\" yunits=\"pixels\"/>");
printWriter.println("</IconStyle></Style>");
}
@@ -16,19 +16,10 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -36,14 +27,16 @@ import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
/**
* KMZ track exporter.
*
* @author Jimmy Shih
*/
public class KmzTrackExporter implements TrackExporter {
public static final String KMZ_EXTENSION = "kmz";
public static final String KMZ_IMAGES_DIR = "images";
public static final String KMZ_KML_FILE = "doc.kml";
@@ -53,7 +46,6 @@ public class KmzTrackExporter implements TrackExporter {
private final ContentProviderUtils contentProviderUtils;
private final FileTrackExporter fileTrackExporter;
private final Track[] tracks;
private final long photoSize;
/**
* Constructor.
@@ -61,15 +53,11 @@ public class KmzTrackExporter implements TrackExporter {
* @param contentProviderUtils the content provider utils
* @param fileTrackExporter the file track exporter
* @param tracks the tracks to export
* @param context the context
*/
public KmzTrackExporter(ContentProviderUtils contentProviderUtils,
FileTrackExporter fileTrackExporter, Track[] tracks, Context context) {
public KmzTrackExporter(ContentProviderUtils contentProviderUtils, FileTrackExporter fileTrackExporter, Track[] tracks) {
this.contentProviderUtils = contentProviderUtils;
this.fileTrackExporter = fileTrackExporter;
this.tracks = tracks;
this.photoSize = PreferencesUtils.getInt(
context, R.string.photo_size_key, PreferencesUtils.PHOTO_SIZE_DEFAULT);
}
@Override
@@ -139,40 +127,13 @@ public class KmzTrackExporter implements TrackExporter {
return;
}
ZipEntry zipEntry = new ZipEntry(
KMZ_IMAGES_DIR + File.separatorChar + uri.getLastPathSegment());
ZipEntry zipEntry = new ZipEntry(KMZ_IMAGES_DIR + File.separatorChar + uri.getLastPathSegment());
zipOutputStream.putNextEntry(zipEntry);
int sampleSize;
if (photoSize == -1) {
sampleSize = 1;
} else {
long size = file.length();
// Convert from kilobytes to bytes.
long limit = photoSize * 1024;
sampleSize = size > limit ? (int) Math.ceil(size / limit) : 1;
}
if (sampleSize == 1) {
readFromFile(zipOutputStream, uri);
} else {
readFromScaledBitmap(zipOutputStream, uri, sampleSize);
}
readFromFile(zipOutputStream, uri);
zipOutputStream.closeEntry();
}
private void readFromScaledBitmap(ZipOutputStream zipOutputStream, Uri uri, int sampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(uri.getPath(), options);
if (bitmap == null) {
return;
}
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, zipOutputStream);
bitmap.recycle();
}
private void readFromFile(ZipOutputStream zipOutputStream, Uri uri) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(new File(uri.getPath()))) {
byte[] buffer = new byte[BUFFER_SIZE];
@@ -17,11 +17,11 @@ package de.dennisguse.opentracks.io.file.exporter;
import android.location.Location;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import java.io.OutputStream;
/**
* Interface for writing tracks to a file. The expected sequence of calls is:
*
@@ -51,11 +51,6 @@ import java.io.OutputStream;
*/
public interface TrackWriter {
/**
* Gets the file extension (e.g, gpx, kml, ...).
*/
String getExtension();
/**
* Prepares the output stream.
*
@@ -16,27 +16,26 @@
package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.TaskStackBuilder;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.TrackDetailActivity;
import java.io.File;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.R;
import java.io.File;
/**
* An activity to import files from the external storage. Optionally to import
@@ -44,20 +43,16 @@ import java.io.File;
*
* @author Rodrigo Damazio
*/
public class ImportActivity extends Activity {
public class ImportActivity extends FragmentActivity implements FileTypeDialogFragment.FileTypeCaller {
public static final String EXTRA_IMPORT_ALL = "import_all";
public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format";
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
private static final String TAG = ImportActivity.class.getSimpleName();
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_RESULT_ID = 1;
private ImportAsyncTask importAsyncTask;
private ProgressDialog progressDialog;
private boolean importAll;
// the path on the external storage to import
private String directoryDisplayName;
@@ -67,68 +62,67 @@ public class ImportActivity extends Activity {
// the number of files to import
private int totalCount;
// the last successfully imported track id
private long trackId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false);
TrackFileFormat trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
if (trackFileFormat == null) {
trackFileFormat = TrackFileFormat.GPX;
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
} else {
fileTypeDialogStart();
}
}
@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.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
Toast.makeText(this, R.string.external_storage_not_readable, Toast.LENGTH_LONG).show();
} else {
fileTypeDialogStart();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void fileTypeDialogStart() {
FileTypeDialogFragment
.newInstance(R.string.import_selection_title, R.string.import_selection_option)
.show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG);
}
@Override
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
if (!FileUtils.isExternalStorageAvailable()) {
Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show();
finish();
return;
}
String directoryPath;
if (importAll) {
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
directoryPath = FileUtils.getPath(trackFileFormat.getExtension());
if (!FileUtils.isDirectory(new File(directoryPath))) {
Toast.makeText(
this, getString(R.string.import_no_directory, directoryDisplayName), Toast.LENGTH_LONG)
.show();
finish();
return;
}
} else {
String action = intent.getAction();
if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) {
Log.d(TAG, "Invalid action: " + intent);
finish();
return;
}
Uri data = intent.getData();
if ("file".equals(data.getScheme())) {
Log.d(TAG, "Invalid data: " + intent);
finish();
return;
}
directoryDisplayName = data.getPath();
directoryPath = data.getPath();
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
String directoryPath = FileUtils.getPath(trackFileFormat.getExtension());
if (!FileUtils.isDirectory(new File(directoryPath))) {
Toast.makeText(this, getString(R.string.import_no_directory, directoryDisplayName), Toast.LENGTH_LONG)
.show();
finish();
return;
}
Object retained = getLastNonConfigurationInstance();
if (retained instanceof ImportAsyncTask) {
importAsyncTask = (ImportAsyncTask) retained;
importAsyncTask.setActivity(this);
} else {
importAsyncTask = new ImportAsyncTask(this, importAll, trackFileFormat, directoryPath);
importAsyncTask.execute();
}
//TODO (still needed?): getLastNonConfiguration instance returned ExportAsyncTask before
// Object retained = getLastNonConfigurationInstance();
// if (retained instanceof ImportAsyncTask) {
// importAsyncTask = (ImportAsyncTask) retained;
// importAsyncTask.setActivity(this);
// } else {
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, directoryPath);
importAsyncTask.execute();
// }
}
@Override
public Object onRetainNonConfigurationInstance() {
importAsyncTask.setActivity(null);
return importAsyncTask;
public void onDismissed() {
finish();
}
@Override
@@ -141,6 +135,7 @@ public class ImportActivity extends Activity {
public void onCancel(DialogInterface dialog) {
importAsyncTask.cancel(true);
dialog.dismiss();
onDismissed();
}
}, directoryDisplayName);
return progressDialog;
@@ -171,21 +166,13 @@ public class ImportActivity extends Activity {
@Override
public void onCancel(DialogInterface dialogInterface) {
dialogInterface.dismiss();
finish();
onDismissed();
}
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
if (successCount == totalCount && !importAll && trackId != -1L) {
Intent intent = IntentUtils.newIntent(
ImportActivity.this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
TaskStackBuilder.create(ImportActivity.this)
.addParentStack(TrackDetailActivity.class).addNextIntent(intent)
.startActivities();
}
dialogInterface.dismiss();
finish();
onDismissed();
}
}).setTitle(titleId).create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@@ -206,12 +193,10 @@ public class ImportActivity extends Activity {
*
* @param aSuccessCount the number of files successfully imported
* @param aTotalCount the number of files to import
* @param aTrackId the last successfully imported track id
*/
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, long aTrackId) {
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) {
successCount = aSuccessCount;
totalCount = aTotalCount;
trackId = aTrackId;
removeDialog(DIALOG_PROGRESS_ID);
showDialog(DIALOG_RESULT_ID);
}
@@ -22,22 +22,20 @@ import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
import de.dennisguse.opentracks.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
/**
* AsyncTask to import files from the external storage.
*
@@ -46,7 +44,6 @@ import java.util.List;
public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = ImportAsyncTask.class.getSimpleName();
private final boolean importAll;
private final TrackFileFormat trackFileFormat;
private final String path;
private final Context context;
@@ -69,14 +66,11 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* Creates an AsyncTask.
*
* @param importActivity the activity currently associated with this AsyncTask
* @param importAll true to import all GPX files
* @param trackFileFormat the track file format
* @param path path to import GPX files
*/
public ImportAsyncTask(ImportActivity importActivity, boolean importAll,
TrackFileFormat trackFileFormat, String path) {
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, String path) {
this.importActivity = importActivity;
this.importAll = importAll;
this.trackFileFormat = trackFileFormat;
this.path = path;
context = importActivity.getApplicationContext();
@@ -95,7 +89,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
public void setActivity(ImportActivity importActivity) {
this.importActivity = importActivity;
if (completed && importActivity != null) {
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
importActivity.onAsyncTaskCompleted(successCount, totalCount);
}
}
@@ -111,11 +105,8 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
try {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
// Get the wake lock if not recording or paused
boolean isRecording =
PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key)
!= PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
boolean isPaused = PreferencesUtils.getBoolean(importActivity,
R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
boolean isRecording = PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
boolean isPaused = PreferencesUtils.getBoolean(importActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
}
@@ -155,7 +146,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected void onPostExecute(Boolean result) {
completed = true;
if (importActivity != null) {
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
importActivity.onAsyncTaskCompleted(successCount, totalCount);
}
}
@@ -163,7 +154,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected void onCancelled() {
completed = true;
if (importActivity != null) {
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
importActivity.onAsyncTaskCompleted(successCount, totalCount);
}
}
@@ -173,37 +164,28 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* @param file the file
*/
private boolean importFile(final File file) {
FileInputStream fileInputStream = null;
try {
TrackImporter trackImporter;
if (trackFileFormat == TrackFileFormat.KML) {
String extension = FileUtils.getExtension(file.getName());
if (TrackFileFormat.KML.getExtension().equals(extension)) {
trackImporter = new KmlFileTrackImporter(context, -1L);
} else {
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
Uri uri = contentProviderUtils.insertTrack(new Track());
long newId = Long.parseLong(uri.getLastPathSegment());
trackImporter = new KmzTrackImporter(context, newId);
}
TrackImporter trackImporter;
if (trackFileFormat == TrackFileFormat.GPX) {
trackImporter = new GpxFileTrackImporter(context);
} else { //KML or KMZ
String extension = FileUtils.getExtension(file.getName());
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
trackImporter = new KmlFileTrackImporter(context, -1L);
} else {
trackImporter = new GpxFileTrackImporter(context);
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
Uri uri = contentProviderUtils.insertTrack(new Track());
long newId = Long.parseLong(uri.getLastPathSegment());
trackImporter = new KmzTrackImporter(context, newId);
}
fileInputStream = new FileInputStream(file);
}
try (FileInputStream fileInputStream = new FileInputStream(file)) {
trackId = trackImporter.importFile(fileInputStream);
return trackId != -1L;
} catch (FileNotFoundException e) {
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return false;
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close file input stream", e);
}
}
}
}
@@ -215,26 +197,20 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private List<File> getFiles() {
List<File> files = new ArrayList<>();
File file = new File(path);
if (importAll) {
File[] candidates = file.listFiles();
if (candidates != null) {
for (File candidate : candidates) {
if (!FileUtils.isDirectory(candidate)) {
String extension = FileUtils.getExtension(candidate.getName());
if (trackFileFormat == TrackFileFormat.KML && (
TrackFileFormat.KML.getExtension().equals(extension)
|| KmzTrackExporter.KMZ_EXTENSION.equals(extension))) {
files.add(candidate);
} else if (trackFileFormat == TrackFileFormat.GPX
&& TrackFileFormat.GPX.getExtension().equals(extension)) {
files.add(candidate);
}
}
File[] candidates = file.listFiles();
if (candidates == null) {
return files;
}
for (File candidate : candidates) {
if (!FileUtils.isDirectory(candidate)) {
String extension = FileUtils.getExtension(candidate.getName());
if (trackFileFormat.getExtension().equals(extension)) {
files.add(candidate);
}
}
} else {
files.add(file);
}
return files;
}
}
@@ -41,7 +41,10 @@ import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
import androidx.core.app.TaskStackBuilder;
import de.dennisguse.opentracks.Constants;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.content.ContentProviderUtils;
@@ -55,8 +58,8 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorState;
import de.dennisguse.opentracks.services.sensors.SensorManager;
import de.dennisguse.opentracks.services.sensors.SensorManagerFactory;
import de.dennisguse.opentracks.services.sensors.RemoteSensorManager;
import de.dennisguse.opentracks.services.sensors.RemoteSensorManagerFactory;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory;
@@ -69,10 +72,6 @@ import de.dennisguse.opentracks.util.SystemUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackNameUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* A background service that registers a location listener and records track
@@ -182,7 +181,7 @@ public class TrackRecordingService extends Service {
};
private TripStatisticsUpdater markerTripStatisticsUpdater;
private WakeLock wakeLock;
private SensorManager sensorManager;
private RemoteSensorManager remoteSensorManager;
private Location lastLocation;
private boolean currentSegmentHasLocation;
private boolean isIdle; // true if idle
@@ -241,7 +240,7 @@ public class TrackRecordingService extends Service {
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory());
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
// onSharedPreferenceChanged might not set recordingTrackId.
@@ -281,9 +280,9 @@ public class TrackRecordingService extends Service {
@Override
public void onDestroy() {
if (sensorManager != null) {
SensorManagerFactory.releaseSystemSensorManager();
sensorManager = null;
if (remoteSensorManager != null) {
RemoteSensorManagerFactory.releaseSystemSensorManager();
remoteSensorManager = null;
}
// Reverse order from onCreate
@@ -618,7 +617,7 @@ public class TrackRecordingService extends Service {
*/
private void startRecording(boolean trackStarted) {
// Update instance variables
sensorManager = SensorManagerFactory.getSystemSensorManager(this);
remoteSensorManager = RemoteSensorManagerFactory.getSystemSensorManager(this);
lastLocation = null;
currentSegmentHasLocation = false;
isIdle = false;
@@ -716,9 +715,9 @@ public class TrackRecordingService extends Service {
splitExecutor.shutdown();
// Update instance variables
if (sensorManager != null) {
SensorManagerFactory.releaseSystemSensorManager();
sensorManager = null;
if (remoteSensorManager != null) {
RemoteSensorManagerFactory.releaseSystemSensorManager();
remoteSensorManager = null;
}
lastLocation = null;
@@ -932,10 +931,10 @@ public class TrackRecordingService extends Service {
}
private SensorDataSet getSensorDataSet() {
if (sensorManager == null || !sensorManager.isEnabled() || !sensorManager.isSensorDataSetValid()) {
if (remoteSensorManager == null || !remoteSensorManager.isEnabled() || !remoteSensorManager.isSensorDataSetValid()) {
return null;
}
return sensorManager.getSensorDataSet();
return remoteSensorManager.getSensorDataSet();
}
/**
@@ -1013,8 +1012,7 @@ public class TrackRecordingService extends Service {
* @param trackId the track id
*/
private void sendTrackBroadcast(int actionId, long trackId) {
Intent intent = new Intent().setAction(getString(actionId))
.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
Intent intent = new Intent().setAction(getString(actionId)).putExtra(getString(R.string.track_id_broadcast_extra), trackId);
sendBroadcast(intent, getString(R.string.permission_notification_value));
}
@@ -1106,24 +1104,24 @@ public class TrackRecordingService extends Service {
@Override
public SensorDataSet getSensorData() {
if (trackRecordingService.sensorManager == null) {
Log.d(TAG, "sensorManager is null.");
if (trackRecordingService.remoteSensorManager == null) {
Log.d(TAG, "remoteSensorManager is null.");
return null;
}
if (trackRecordingService.getSensorDataSet() == null) {
Log.d(TAG, "Sensor data set is null.");
return null;
}
return trackRecordingService.sensorManager.getSensorDataSet();
return trackRecordingService.remoteSensorManager.getSensorDataSet();
}
@Override
public SensorState getSensorState() {
if (trackRecordingService.sensorManager == null) {
Log.d(TAG, "sensorManager is null.");
if (trackRecordingService.remoteSensorManager == null) {
Log.d(TAG, "remoteSensorManager is null.");
return SensorState.NONE;
}
return trackRecordingService.sensorManager.getSensorState();
return trackRecordingService.remoteSensorManager.getSensorState();
}
/**
@@ -24,13 +24,13 @@ import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.util.ArrayList;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorState;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
/**
* Bluetooth sensor manager.
@@ -39,7 +39,7 @@ import java.util.ArrayList;
*
* @author Sandor Dornbush
*/
public class BluetoothSensorManager extends SensorManager {
public class BluetoothRemoteSensorManager extends RemoteSensorManager {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
@@ -71,7 +71,7 @@ public class BluetoothSensorManager extends SensorManager {
/**
* @param context the context
*/
public BluetoothSensorManager(Context context) {
public BluetoothRemoteSensorManager(Context context) {
this.context = context;
bluetoothConnectionManager = new BluetoothConnectionManager(context, messageHandler);
}
@@ -18,23 +18,23 @@ package de.dennisguse.opentracks.services.sensors;
import android.util.Log;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorState;
import java.util.Timer;
import java.util.TimerTask;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorState;
/**
* Manage the connection to a sensor.
* Manage the connection to a remote sensor.
*
* @author Sandor Dornbush
*/
public abstract class SensorManager {
public abstract class RemoteSensorManager {
public static final long MAX_SENSOR_DATE_SET_AGE = 5000;
public static final long MAX_SENSOR_STATE_AGE = 20000;
private static final String TAG = SensorManager.class.getSimpleName();
private static final String TAG = RemoteSensorManager.class.getSimpleName();
private static final int RETRY_PERIOD = 20000;
private SensorState sensorState = SensorState.NONE;
@@ -88,7 +88,7 @@ public abstract class SensorManager {
}
}
};
timer = new Timer(SensorManager.class.getSimpleName());
timer = new Timer(RemoteSensorManager.class.getSimpleName());
timer.schedule(timerTask, RETRY_PERIOD, RETRY_PERIOD);
}
@@ -19,16 +19,16 @@ package de.dennisguse.opentracks.services.sensors;
import android.content.Context;
/**
* A factory of {@link SensorManager}.
* A factory of {@link RemoteSensorManager}.
*
* @author Sandor Dornbush
*/
public class SensorManagerFactory {
public class RemoteSensorManagerFactory {
private static SensorManager systemSensorManager = null;
private static SensorManager sensorManagerTemporary = null;
private static RemoteSensorManager remoteSensorManagerSystem = null;
private static RemoteSensorManager remoteSensorManagerTemporary = null;
private SensorManagerFactory() {
private RemoteSensorManagerFactory() {
}
/**
@@ -36,49 +36,45 @@ public class SensorManagerFactory {
*
* @param context the context
*/
public static SensorManager getSystemSensorManager(Context context) {
public static RemoteSensorManager getSystemSensorManager(Context context) {
releaseSensorManagerTemporary();
releaseSystemSensorManager();
systemSensorManager = getSensorManager(context);
if (systemSensorManager != null) {
systemSensorManager.startSensor();
}
return systemSensorManager;
remoteSensorManagerSystem = getSensorManager(context);
remoteSensorManagerSystem.startSensor();
return remoteSensorManagerSystem;
}
/**
* Releases the system sensor manager.
*/
public static void releaseSystemSensorManager() {
if (systemSensorManager != null) {
systemSensorManager.stopSensor();
if (remoteSensorManagerSystem != null) {
remoteSensorManagerSystem.stopSensor();
}
systemSensorManager = null;
remoteSensorManagerSystem = null;
}
/**
* Gets the temp sensor manager.
*/
public static SensorManager getSensorManagerTemporary(Context context) {
public static RemoteSensorManager getSensorManagerTemporary(Context context) {
releaseSensorManagerTemporary();
if (systemSensorManager != null) {
if (remoteSensorManagerSystem != null) {
return null;
}
sensorManagerTemporary = getSensorManager(context);
if (sensorManagerTemporary != null) {
sensorManagerTemporary.startSensor();
}
return sensorManagerTemporary;
remoteSensorManagerTemporary = getSensorManager(context);
remoteSensorManagerTemporary.startSensor();
return remoteSensorManagerTemporary;
}
/**
* Releases the temp sensor manager.
*/
public static void releaseSensorManagerTemporary() {
if (sensorManagerTemporary != null) {
sensorManagerTemporary.stopSensor();
if (remoteSensorManagerTemporary != null) {
remoteSensorManagerTemporary.stopSensor();
}
sensorManagerTemporary = null;
remoteSensorManagerTemporary = null;
}
/**
@@ -86,7 +82,7 @@ public class SensorManagerFactory {
*
* @param context the context
*/
private static SensorManager getSensorManager(Context context) {
return new BluetoothSensorManager(context);
private static RemoteSensorManager getSensorManager(Context context) {
return new BluetoothRemoteSensorManager(context);
}
}
@@ -1,127 +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.settings;
import android.app.ActionBar;
import android.content.Context;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.speech.tts.TextToSpeech;
import android.view.MenuItem;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.R;
/**
* An abstract activity for all the settings activities.
*
* @author Jimmy Shih
*/
public class AbstractSettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
ActionBar actionBar = this.getActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setSharedPreferencesName(Constants.SETTINGS_NAME);
preferenceManager.setSharedPreferencesMode(Context.MODE_PRIVATE);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() != android.R.id.home) {
return super.onOptionsItemSelected(item);
}
finish();
return true;
}
/**
* Configures a list preference.
*
* @param listPreference the list preference
* @param summary the summary array
* @param options the options array
* @param values the values array
* @param value the value
* @param listener optional listener
*/
protected void configureListPreference(ListPreference listPreference, final String[] summary,
final String[] options, final String[] values, String value,
final OnPreferenceChangeListener listener) {
listPreference.setEntryValues(values);
listPreference.setEntries(options);
listPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
updatePreferenceSummary(pref, summary, values, (String) newValue);
if (listener != null) {
listener.onPreferenceChange(pref, newValue);
}
return true;
}
});
updatePreferenceSummary(listPreference, summary, values, value);
if (listener != null) {
listener.onPreferenceChange(listPreference, value);
}
}
/**
* Update the preference summary.
*
* @param preference the preference
* @param summary the summary array
* @param values the values array
* @param value the value
*/
private void updatePreferenceSummary(Preference preference, String[] summary, String[] values, String value) {
int index = getIndex(values, value);
if (index == -1) {
preference.setSummary(R.string.value_unknown);
} else {
preference.setSummary(summary[index]);
}
}
/**
* Get the array index for a value.
*
* @param values the array
* @param value the value
*/
private int getIndex(String[] values, String value) {
for (int i = 0; i < values.length; i++) {
if (value.equals(values[i])) {
return i;
}
}
return -1;
}
}
@@ -1,185 +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.settings;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.util.Log;
import android.widget.Toast;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
/**
* An activity for advanced settings.
*
* @author Jimmy Shih
*/
public class AdvancedSettingsActivity extends AbstractSettingsActivity {
private static final String TAG = AdvancedSettingsActivity.class.getSimpleName();
private static final int DIALOG_CONFIRM_RESET_ID = 1;
private Preference resetPreference;
private SharedPreferences sharedPreferences;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
/*
* Note that sharedPreferenceChangeListenr cannot be an anonymous inner class.
* Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key != null && key.equals(PreferencesUtils.getKey(
AdvancedSettingsActivity.this, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(
AdvancedSettingsActivity.this, R.string.recording_track_id_key);
runOnUiThread(new Runnable() {
@Override
public void run() {
updateUi();
}
});
}
}
};
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.settings_advanced);
ListPreference preference = (ListPreference) findPreference(getString(R.string.photo_size_key));
int value = PreferencesUtils.getInt(this, R.string.photo_size_key, PreferencesUtils.PHOTO_SIZE_DEFAULT);
String[] values = getResources().getStringArray(R.array.photo_size_values);
String[] options = new String[values.length];
String[] summary = new String[values.length];
setPhotoSizeSummaryAndOptions(summary, options, values);
configureListPreference(preference, summary, options, values, String.valueOf(value), null);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
resetPreference = findPreference(getString(R.string.settings_reset_key));
resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference arg0) {
showDialog(DIALOG_CONFIRM_RESET_ID);
return true;
}
});
}
@Override
protected void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);
updateUi();
}
@Override
protected void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_CONFIRM_RESET_ID:
return DialogUtils.createConfirmationDialog(this, R.string.settings_reset_confirm_title,
getString(R.string.settings_reset_confirm_message),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int button) {
onResetPreferencesConfirmed();
}
});
default:
return null;
}
}
private void setPhotoSizeSummaryAndOptions(String[] summary, String[] options, String[] values) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
if (value == -1) {
options[i] = getString(R.string.settings_advanced_photo_size_original);
summary[i] = getString(R.string.settings_advanced_photo_size_original);
} else if (value < 1024) {
options[i] = getString(R.string.value_integer_kilobyte, value);
summary[i] = getString(R.string.settings_advanced_photo_size_summary, options[i]);
} else {
int megabyte = value / 1024;
options[i] = getString(R.string.value_integer_megabyte, megabyte);
summary[i] = getString(R.string.settings_advanced_photo_size_summary, options[i]);
}
}
}
/**
* Updates the UI based on the recording state.
*/
private void updateUi() {
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
resetPreference.setEnabled(!isRecording);
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
}
/**
* Callback when the user confirms resetting all settings.
*/
private void onResetPreferencesConfirmed() {
// Change preferences in a separate thread
new Thread() {
@Override
public void run() {
Log.i(TAG, "Resetting all settings");
// Actually wipe preferences and save synchronously
sharedPreferences.edit().clear().apply();
// Give UI feedback in the UI thread
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(
AdvancedSettingsActivity.this, R.string.settings_reset_done, Toast.LENGTH_SHORT)
.show();
// Restart the settings activity so all changes are loaded
Intent intent = getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
}
}.start();
}
}
@@ -1,65 +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.settings;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
/**
* An activity for accessing chart settings.
*
* @author Jimmy Shih
*/
public class ChartSettingsActivity extends AbstractSettingsActivity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.settings_chart);
configXAxisListPreference();
}
@Override
protected void onResume() {
super.onResume();
updateUi();
}
private void updateUi() {
CheckBoxPreference speedCheckBoxPreference = (CheckBoxPreference) findPreference(
getString(R.string.chart_show_speed_key));
speedCheckBoxPreference.setTitle(
PreferencesUtils.isReportSpeed(this) ? R.string.stats_speed : R.string.stats_pace);
}
/**
* Configures the x axis list preference.
*/
private void configXAxisListPreference() {
ListPreference listPreference = (ListPreference) findPreference(
getString(R.string.chart_x_axis_key));
String value = PreferencesUtils.getString(
this, R.string.chart_x_axis_key, PreferencesUtils.CHART_X_AXIS_DEFAULT);
String[] values = getResources().getStringArray(R.array.chart_x_axis_values);
String[] options = getResources().getStringArray(R.array.chart_x_axis_options);
configureListPreference(listPreference, options, options, values, value, null);
}
}
@@ -20,6 +20,7 @@ import android.app.Dialog;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
@@ -33,7 +34,7 @@ import de.dennisguse.opentracks.R;
*
* @author Jimmy Shih
*/
public class RecordingSettingsActivity extends AbstractSettingsActivity
public class RecordingSettingsActivity extends PreferenceActivity
implements ChooseActivityTypeCaller {
private static final int DIALOG_CHOOSE_ACTIVITY = 0;
@@ -47,27 +48,17 @@ public class RecordingSettingsActivity extends AbstractSettingsActivity
boolean metricUnits = PreferencesUtils.isMetricUnits(this);
configFrequencyPreference(R.string.voice_frequency_key,
PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.array.frequency_values, metricUnits);
configFrequencyPreference(R.string.split_frequency_key,
PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.array.frequency_values, metricUnits);
configTrackName();
configFrequencyPreference(R.string.voice_frequency_key, metricUnits);
configFrequencyPreference(R.string.split_frequency_key, metricUnits);
configDefaultActivity();
configListPreference(R.string.min_recording_interval_key,
PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT, R.array.min_recording_interval_values,
configListPreference(R.string.min_recording_interval_key, R.array.min_recording_interval_values,
metricUnits);
configListPreference(R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT,
R.array.recording_distance_interval_values, metricUnits);
configListPreference(R.string.max_recording_distance_key,
PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT, R.array.max_recording_distance_values,
configListPreference(R.string.recording_distance_interval_key, R.array.recording_distance_interval_values, metricUnits);
configListPreference(R.string.max_recording_distance_key, R.array.max_recording_distance_values,
metricUnits);
configListPreference(R.string.recording_gps_accuracy_key,
PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT, R.array.recording_gps_accuracy_values,
configListPreference(R.string.recording_gps_accuracy_key, R.array.recording_gps_accuracy_values,
metricUnits);
configListPreference(R.string.auto_resume_track_timeout_key,
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT,
R.array.auto_resume_track_timeout_values, metricUnits);
configListPreference(R.string.auto_resume_track_timeout_key, R.array.auto_resume_track_timeout_values, metricUnits);
}
@Override
@@ -81,21 +72,11 @@ public class RecordingSettingsActivity extends AbstractSettingsActivity
return ChooseActivityTypeDialogFragment.getDialog(this, category, this);
}
private void configFrequencyPreference(int key, int defaultValue, int valueArray, boolean metricUnits) {
private void configFrequencyPreference(int key, boolean metricUnits) {
ListPreference preference = (ListPreference) findPreference(getString(key));
int value = PreferencesUtils.getInt(this, key, defaultValue);
String[] values = getResources().getStringArray(valueArray);
String[] options = StringUtils.getFrequencyOptions(this, metricUnits);
configureListPreference(preference, options, options, values, String.valueOf(value), null);
}
private void configTrackName() {
ListPreference preference = (ListPreference) findPreference(getString(R.string.track_name_key));
String value = PreferencesUtils.getString(
this, R.string.track_name_key, PreferencesUtils.TRACK_NAME_DEFAULT);
String[] values = getResources().getStringArray(R.array.track_name_values);
String[] options = getResources().getStringArray(R.array.track_name_options);
configureListPreference(preference, options, options, values, value, null);
String[] options = StringUtils.getFrequencyOptions(this, metricUnits);
preference.setEntries(options);
}
private void configDefaultActivity() {
@@ -120,78 +101,69 @@ public class RecordingSettingsActivity extends AbstractSettingsActivity
activityTypePreference.setRecordingSettingsActivity(this);
}
private void configListPreference(
int key, int defaultValue, int valueArray, boolean metricUnits) {
private void configListPreference(int key, int valueArray, boolean metricUnits) {
ListPreference preference = (ListPreference) findPreference(getString(key));
int value = PreferencesUtils.getInt(this, key, defaultValue);
@Deprecated
String[] values = getResources().getStringArray(valueArray);
String[] options = new String[values.length];
String[] summary = new String[values.length];
switch (key) {
case R.string.min_recording_interval_key:
setMinRecordingIntervalSummaryAndOptions(summary, options, values);
setMinRecordingIntervalOptions(options, values);
break;
case R.string.recording_distance_interval_key:
setRecordingDistanceIntervalSummaryAndOptions(summary, options, values, metricUnits);
setRecordingDistanceIntervalOptions(options, values, metricUnits);
break;
case R.string.max_recording_distance_key:
setMaxRecordingDistanceSummaryAndOptions(summary, options, values, metricUnits);
setMaxRecordingDistanceOptions(options, values, metricUnits);
break;
case R.string.recording_gps_accuracy_key:
setRecordingGpsAccuracySummaryAndOptions(summary, options, values, metricUnits);
setRecordingGpsAccuracyOptions(options, values, metricUnits);
break;
case R.string.auto_resume_track_timeout_key:
setAutoResumeTrackTimeoutSummaryAndOptions(summary, options, values);
setAutoResumeTrackTimeoutOptions(options, values);
break;
default:
return;
}
configureListPreference(preference, summary, options, values, String.valueOf(value), null);
preference.setEntries(options);
}
/**
* Sets the min recording interval summary and options.
* Sets the min recording interval options.
*
* @param summary the summary
* @param options the options
* @param values the values
*/
private void setMinRecordingIntervalSummaryAndOptions(
String[] summary, String[] options, String[] values) {
private void setMinRecordingIntervalOptions(String[] options, String[] values) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
switch (value) {
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
options[i] = getString(R.string.value_adapt_battery_life);
summary[i] = options[i];
break;
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY:
options[i] = getString(R.string.value_adapt_accuracy);
summary[i] = options[i];
break;
case PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT:
options[i] = getString(R.string.value_smallest_recommended);
summary[i] = getString(R.string.value_smallest);
break;
default:
options[i] = value < 60 ? getString(R.string.value_integer_second, value)
: getString(R.string.value_integer_minute, value / 60);
summary[i] = getString(
R.string.settings_recording_location_frequency_summary, options[i]);
}
}
}
/**
* Sets the recording distance interval summary and options.
* Sets the recording distance interval options.
*
* @param summary the summary
* @param options the options
* @param values the values
* @param metricUnits true for metric units
*/
private void setRecordingDistanceIntervalSummaryAndOptions(
String[] summary, String[] options, String[] values, boolean metricUnits) {
private void setRecordingDistanceIntervalOptions(String[] options, String[] values, boolean metricUnits) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
String displayValue;
@@ -211,20 +183,17 @@ public class RecordingSettingsActivity extends AbstractSettingsActivity
options[i] = displayValue;
}
}
summary[i] = getString(R.string.settings_recording_location_frequency_summary, displayValue);
}
}
/**
* Sets the max recording distance summary and options.
* Sets the max recording distance options.
*
* @param summary the summary
* @param options the options
* @param values the values
* @param metricUnits true for metric units
*/
private void setMaxRecordingDistanceSummaryAndOptions(
String[] summary, String[] options, String[] values, boolean metricUnits) {
private void setMaxRecordingDistanceOptions(String[] options, String[] values, boolean metricUnits) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
String displayValue;
@@ -250,21 +219,17 @@ public class RecordingSettingsActivity extends AbstractSettingsActivity
options[i] = displayValue;
}
}
summary[i] = getString(
R.string.settings_recording_max_recording_distance_summary, displayValue);
}
}
/**
* Sets the recording gps accuracy summary and options.
* Sets the recording gps accuracy options.
*
* @param summary the summary
* @param options the options
* @param values the values
* @param metricUnits true for metric units
*/
private void setRecordingGpsAccuracySummaryAndOptions(
String[] summary, String[] options, String[] values, boolean metricUnits) {
private void setRecordingGpsAccuracyOptions(String[] options, String[] values, boolean metricUnits) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
String displayValue;
@@ -307,37 +272,27 @@ public class RecordingSettingsActivity extends AbstractSettingsActivity
}
}
}
summary[i] = getString(
R.string.settings_recording_min_required_accuracy_summary, displayValue);
}
}
/**
* Sets the auto resume track timeout summary and options.
* Sets the auto resume track timeout options.
*
* @param summary the summary
* @param options the options
* @param values the values
*/
private void setAutoResumeTrackTimeoutSummaryAndOptions(
String[] summary, String[] options, String[] values) {
private void setAutoResumeTrackTimeoutOptions(String[] options, String[] values) {
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
switch (value) {
case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER:
options[i] = getString(R.string.value_never);
summary[i] = getString(
R.string.settings_recording_auto_resume_track_timeout_never_summary);
break;
case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS:
options[i] = getString(R.string.value_always);
summary[i] = getString(
R.string.settings_recording_auto_resume_track_timeout_always_summary);
break;
default:
options[i] = getString(R.string.value_integer_minute, value);
summary[i] = getString(
R.string.settings_recording_auto_resume_track_timeout_summary, options[i]);
}
}
}
@@ -0,0 +1,27 @@
package de.dennisguse.opentracks.settings;
import android.content.Context;
import android.content.DialogInterface;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.widget.Toast;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
public class ResetDialogPreference extends DialogPreference {
public ResetDialogPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onClick(DialogInterface dialog, int which) {
super.onClick(dialog, which);
if (which == DialogInterface.BUTTON_POSITIVE) {
PreferencesUtils.resetPreferences(getContext(), true);
Toast.makeText(getContext(), R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
}
}
}
@@ -1,90 +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.settings;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.provider.Settings;
import de.dennisguse.opentracks.util.BluetoothDeviceUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
import java.util.List;
/**
* An activity for accessing sensor settings.
*
* @author Jimmy Shih
*/
public class SensorSettingsActivity extends AbstractSettingsActivity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.settings_sensors);
findPreference(getString(R.string.settings_sensor_bluetooth_pairing_key))
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent settingsIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(settingsIntent);
return true;
}
});
}
@Override
protected void onResume() {
super.onResume();
// Update each time in case the list of bluetooth sensors has changed
configBluetoothSensor();
}
/**
* Configures the bluetooth sensor.
*/
private void configBluetoothSensor() {
ListPreference preference = (ListPreference) findPreference(getString(R.string.bluetooth_sensor_key));
String value = PreferencesUtils.getString(this, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
List<String> devicesNameList = new ArrayList<>();
List<String> devicesAddressList = new ArrayList<>();
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null) {
BluetoothDeviceUtils.populateDeviceLists(bluetoothAdapter, devicesNameList, devicesAddressList);
}
// Was the previously configured device unpaired? Then forget it.
if (!devicesAddressList.contains(value)) {
value = PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT;
PreferencesUtils.setString(this, R.string.bluetooth_sensor_key, value);
}
devicesNameList.add(0, getString(R.string.value_none));
devicesAddressList.add(0, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
String[] options = devicesNameList.toArray(new String[0]);
String[] values = devicesAddressList.toArray(new String[0]);
configureListPreference(preference, options, options, values, value, null);
}
}
@@ -16,20 +16,53 @@
package de.dennisguse.opentracks.settings;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.provider.Settings;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.BluetoothDeviceUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* An activity for accessing settings.
*
* @author Jimmy Shih
*/
public class SettingsActivity extends AbstractSettingsActivity {
public class SettingsActivity extends PreferenceActivity {
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
/*
* Note that sharedPreferenceChangeListenr cannot be an anonymous inner class.
* Anonymous inner class will get garbage collected.
*/
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key != null && key.equals(PreferencesUtils.getKey(SettingsActivity.this, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(SettingsActivity.this, R.string.recording_track_id_key);
}
//TODO Should only be called if something meaningful happens.
runOnUiThread(new Runnable() {
@Override
public void run() {
updateUI();
}
});
}
};
@Override
protected void onCreate(Bundle bundle) {
@@ -37,11 +70,33 @@ public class SettingsActivity extends AbstractSettingsActivity {
addPreferencesFromResource(R.xml.settings);
configPreference(R.string.settings_chart_key, ChartSettingsActivity.class);
configPreference(R.string.settings_stats_key, StatsSettingsActivity.class);
configPreference(R.string.settings_recording_key, RecordingSettingsActivity.class);
configPreference(R.string.settings_sensor_key, SensorSettingsActivity.class);
configPreference(R.string.settings_advanced_key, AdvancedSettingsActivity.class);
findPreference(getString(R.string.settings_sensor_bluetooth_pairing_key))
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent settingsIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(settingsIntent);
return true;
}
});
}
@Override
protected void onResume() {
super.onResume();
PreferencesUtils.getSharedPreferences(this).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);
updateUI();
configBluetoothSensor();
}
@Override
protected void onPause() {
super.onPause();
PreferencesUtils.getSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
/**
@@ -61,4 +116,44 @@ public class SettingsActivity extends AbstractSettingsActivity {
}
});
}
/**
* Configures the bluetooth sensor.
*/
private void configBluetoothSensor() {
ListPreference preference = (ListPreference) findPreference(getString(R.string.bluetooth_sensor_key));
String value = PreferencesUtils.getString(this, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
List<String> devicesNameList = new ArrayList<>();
List<String> devicesAddressList = new ArrayList<>();
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null) {
BluetoothDeviceUtils.populateDeviceLists(bluetoothAdapter, devicesNameList, devicesAddressList);
}
// Was the previously configured device unpaired? Then forget it.
if (!devicesAddressList.contains(value)) {
value = PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT;
PreferencesUtils.setString(this, R.string.bluetooth_sensor_key, value);
}
devicesNameList.add(0, getString(R.string.value_none));
devicesAddressList.add(0, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
String[] values = devicesAddressList.toArray(new String[0]);
preference.setEntryValues(values);
String[] options = devicesNameList.toArray(new String[0]);
preference.setEntries(options);
}
private void updateUI() {
//TODO Remove the following if recordingTrackId is replaced by direct communication rather than via preferences.
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
resetPreference.setEnabled(!isRecording);
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(this) ? R.string.stats_speed : R.string.stats_pace);
}
}
@@ -20,36 +20,34 @@ import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* An activity for accessing stats settings.
*
* @author Jimmy Shih
*/
public class StatsSettingsActivity extends AbstractSettingsActivity {
public class StatsSettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.settings_statistics);
/*
* Note configureUnitsListPreference will trigger
* configureRateListPreference
*/
configUnitsListPreference();
boolean metricUnits = PreferencesUtils.STATS_UNITS_DEFAULT.equals(PreferencesUtils.getString(this, R.string.stats_units_key, ""));
configRateListPreference(metricUnits);
}
/**
* Configures the preferred units list preference.
*/
private void configUnitsListPreference() {
ListPreference listPreference = (ListPreference) findPreference(
getString(R.string.stats_units_key));
OnPreferenceChangeListener listener = new OnPreferenceChangeListener() {
ListPreference listPreference = (ListPreference) findPreference(getString(R.string.stats_units_key));
final OnPreferenceChangeListener listener = new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
@@ -58,11 +56,15 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
return true;
}
};
String value = PreferencesUtils.getString(
this, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
String[] values = getResources().getStringArray(R.array.stats_units_values);
String[] options = getResources().getStringArray(R.array.stats_units_options);
configureListPreference(listPreference, options, options, values, value, listener);
listPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
if (listener != null) {
listener.onPreferenceChange(pref, newValue);
}
return true;
}
});
}
/**
@@ -71,13 +73,9 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
* @param metricUnits true if metric units
*/
private void configRateListPreference(boolean metricUnits) {
ListPreference listPreference = (ListPreference) findPreference(
getString(R.string.stats_rate_key));
String value = PreferencesUtils.getString(
this, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT);
String[] values = getResources().getStringArray(R.array.stats_rate_values);
String[] options = getResources().getStringArray(
metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
configureListPreference(listPreference, options, options, values, value, null);
ListPreference listPreference = (ListPreference) findPreference(getString(R.string.stats_rate_key));
String[] options = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
listPreference.setEntries(options);
}
}
@@ -77,30 +77,6 @@ public class DoubleBuffer {
return sum / numberOfEntries;
}
/**
* Gets the average and the variance of the buffer.
*
* @return an array of two elements - the first is the average, the second is
* the variance
*/
public double[] getAverageAndVariance() {
int numberOfEntries = isFull ? buffer.length : index;
if (numberOfEntries == 0) {
return new double[]{0, 0};
}
double sum = 0;
double sumSquares = 0;
for (int i = 0; i < numberOfEntries; i++) {
sum += buffer[i];
sumSquares += Math.pow(buffer[i], 2);
}
double average = sum / numberOfEntries;
double variance = sumSquares / numberOfEntries - Math.pow(average, 2);
return new double[]{average, variance};
}
/**
* Adds a double to the buffer. If the buffer is full the oldest element is
* overwritten.
@@ -35,10 +35,7 @@ public class FileUtils {
* Used to transfer picture from the camera.
*/
public static final String FILEPROVIDER = BuildConfig.APPLICATION_ID + ".fileprovider";
/**
* The play tracks directory in the app's cache directory.
*/
public static final String PLAY_TRACKS_DIR = "temp";
/**
* Our external storage directory.
*/
@@ -16,18 +16,28 @@
package de.dennisguse.opentracks.util;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;
import androidx.core.content.FileProvider;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.ShareContentProvider;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.R;
import java.io.File;
/**
* Utilities for creating intents.
@@ -36,6 +46,8 @@ import java.io.File;
*/
public class IntentUtils {
private final static String TAG = IntentUtils.class.getSimpleName();
private IntentUtils() {
}
@@ -50,38 +62,91 @@ public class IntentUtils {
return new Intent(context, cls).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
}
private static final String JPEG_EXTENSION = "jpeg";
/**
* Creates an intent to share a track file with an app.
*
* @param context the context
* @param trackId the track id
* @param filePath the file path
* @param trackFileFormat the track file format
* @param context the context
* @param trackIds the track ids
*/
public static Intent newShareFileIntent(Context context, long trackId, String filePath, TrackFileFormat trackFileFormat) {
Track track = ContentProviderUtils.Factory.get(context).getTrack(trackId);
String trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, null, null, false);
public static Intent newShareFileIntent(Context context, long[] trackIds) {
String trackDescription = "";
if (trackIds.length == 1) {
Track track = ContentProviderUtils.Factory.get(context).getTrack(trackIds[0]);
trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, false);
}
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_WITH_SENSORDATA_AND_PICTURES);
return new Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath)))
.putExtra(Intent.EXTRA_STREAM, uriAndMime.first)
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription))
.putExtra(context.getString(R.string.track_id_broadcast_extra), trackId)
.setType(trackFileFormat.getMimeType());
.setType(uriAndMime.second)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
public static Intent newShowOnMapIntent(Waypoint waypoint) {
return newShowOnMapIntent(waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
showCoordinateOnMap(context, waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
}
public static Intent newShowOnMapIntent(double latitude, double longitude, String label) {
public static void showCoordinateOnMap(Context context, double latitude, double longitude, String label) {
//SEE https://developer.android.com/guide/components/intents-common.html#Maps
Intent intent = new Intent(Intent.ACTION_VIEW);
String uri = "geo:0,0?q=" + latitude + "," + longitude;
if (label != null && label.length() > 0) {
uri += "(" + label + ")";
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(uri));
return intent;
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, context.getString(R.string.app_not_installed_show_on_map), Toast.LENGTH_SHORT).show();
}
}
/**
* Send intent to show tracks on a map (needs an another app).
*
* @param context the context
* @param trackIds the track ids
*/
public static void showTrackOnMap(Context context, long[] trackIds) {
if (trackIds.length == 0) {
return;
}
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_ONLY_TRACK);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, context.getString(R.string.app_not_installed_show_on_map), Toast.LENGTH_SHORT).show();
}
}
/**
* Sends a take picture request to the camera app.
* The picture is then stored in the track's folder.
*
* @param context the context
* @param trackId the track id
*/
public static Pair<Intent, Uri> createTakePictureIntent(Context context, long trackId) {
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));
Uri photoUri = FileProvider.getUriForFile(context, FileUtils.FILEPROVIDER, file);
Log.d(TAG, "Taking photo to URI: " + photoUri);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
return new Pair<>(intent, photoUri);
}
}
@@ -19,8 +19,8 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.R;
/**
@@ -55,7 +55,6 @@ public class PreferencesUtils {
// Value for split_frequency_key and voice_frequency_key
public static final int FREQUENCY_OFF = 0;
public static final int MAP_TYPE_DEFAULT = 1;
public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200;
// Values for min_recording_interval_key
@@ -63,7 +62,6 @@ public class PreferencesUtils {
public static final int MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = -2;
public static final int MIN_RECORDING_INTERVAL_DEFAULT = 0;
public static final int PHOTO_SIZE_DEFAULT = 1024; // 1024 kB
public static final int RECORDING_DISTANCE_INTERVAL_DEFAULT = 10;
// Values for recording_gps_accuracy
@@ -71,6 +69,7 @@ public class PreferencesUtils {
public static final int RECORDING_GPS_ACCURACY_EXCELLENT = 10;
public static final int RECORDING_GPS_ACCURACY_POOR = 2000;
@Deprecated //NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
@@ -84,6 +83,8 @@ public class PreferencesUtils {
public static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601";
public static final boolean SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN = false;
// Track widget
public static final int TRACK_WIDGET_ITEM1_DEFAULT = 3; // moving time
public static final int TRACK_WIDGET_ITEM2_DEFAULT = 0; // distance
@@ -94,6 +95,10 @@ public class PreferencesUtils {
private PreferencesUtils() {
}
public static SharedPreferences getSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
/**
* Gets a preference key
*
@@ -112,7 +117,7 @@ public class PreferencesUtils {
* @param defaultValue the default value
*/
public static boolean getBoolean(Context context, int keyId, boolean defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = getSharedPreferences(context);
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
}
@@ -124,7 +129,7 @@ public class PreferencesUtils {
* @param value the value
*/
public static void setBoolean(Context context, int keyId, boolean value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit();
editor.putBoolean(getKey(context, keyId), value);
editor.apply();
@@ -138,16 +143,21 @@ public class PreferencesUtils {
* @param defaultValue the default value
*/
public static int getInt(Context context, int keyId, int defaultValue) {
//NOTE: We assume that the data was stored as String due to use of ListPreference.
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
String stringValue = sharedPreferences.getString(getKey(context, keyId), null);
SharedPreferences sharedPreferences = getSharedPreferences(context);
int intValue = defaultValue;
try {
intValue = Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
} catch (ClassCastException e) {
//Ignore
}
//NOTE: We assume that the data was stored as String due to use of ListPreference.
try {
String stringValue = sharedPreferences.getString(getKey(context, keyId), null);
return Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
return defaultValue;
}
return intValue;
}
/**
@@ -158,7 +168,7 @@ public class PreferencesUtils {
* @param value the value
*/
public static void setInt(Context context, int keyId, int value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(context, keyId), value);
editor.apply();
@@ -171,7 +181,7 @@ public class PreferencesUtils {
* @param keyId the key id
*/
public static long getLong(Context context, int keyId) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = getSharedPreferences(context);
return sharedPreferences.getLong(getKey(context, keyId), -1L);
}
@@ -183,7 +193,7 @@ public class PreferencesUtils {
* @param value the value
*/
public static void setLong(Context context, int keyId, long value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit();
editor.putLong(getKey(context, keyId), value);
editor.apply();
@@ -197,7 +207,7 @@ public class PreferencesUtils {
* @param defaultValue default value
*/
public static String getString(Context context, int keyId, String defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = getSharedPreferences(context);
return sharedPreferences.getString(getKey(context, keyId), defaultValue);
}
@@ -209,7 +219,7 @@ public class PreferencesUtils {
* @param value the value
*/
public static void setString(Context context, int keyId, String value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences sharedPreferences = getSharedPreferences(context);
Editor editor = sharedPreferences.edit();
editor.putString(getKey(context, keyId), value);
editor.apply();
@@ -242,4 +252,10 @@ public class PreferencesUtils {
public static boolean isChartByDistance(Context context) {
return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_DEFAULT));
}
public static void resetPreferences(Context context, boolean readAgain) {
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
PreferenceManager.setDefaultValues(context, R.xml.settings_recording, readAgain);
PreferenceManager.setDefaultValues(context, R.xml.settings_statistics, readAgain);
}
}
@@ -23,13 +23,13 @@ import android.view.View;
import android.widget.Spinner;
import android.widget.TextView;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.SensorManager;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.R;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.RemoteSensorManager;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* Utilities for updating the statistics UI labels and values.
*
@@ -143,6 +143,8 @@ public class StatsUtils {
setDistanceValue(context, getView(activity, view, R.id.stats_distance), totalDistance, metricUnits);
// Set activity type
getView(activity, null, R.id.stats_activity_type_label).setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
Spinner spinner = (Spinner) getView(activity, view, R.id.stats_activity_type_icon);
spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
if (trackIconValue != null) {
@@ -292,7 +294,7 @@ public class StatsUtils {
private static void setHeartRateData(Context context, View viewValue, View viewSensor, SensorDataSet sensorDataSet) {
String heartRate = context.getString(R.string.value_unknown);
String sensor = context.getString(R.string.value_unknown);
if (sensorDataSet != null && sensorDataSet.hasHeartRate() && sensorDataSet.isRecent(SensorManager.MAX_SENSOR_DATE_SET_AGE)) {
if (sensorDataSet != null && sensorDataSet.hasHeartRate() && sensorDataSet.isRecent(RemoteSensorManager.MAX_SENSOR_DATE_SET_AGE)) {
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
sensor = sensorDataSet.getSensorName();
}
@@ -25,6 +25,8 @@ import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import androidx.core.content.pm.PackageInfoCompat;
import de.dennisguse.opentracks.BuildConfig;
/**
@@ -47,7 +49,7 @@ public class SystemUtils {
public static String getAppVersionName(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_META_DATA);
return pi.versionName;
return pi.versionName + "/" + BuildConfig.VERSION_NAME_FULL;
} catch (NameNotFoundException e) {
Log.w(TAG, "Failed to get version info.", e);
return "";
@@ -57,7 +59,7 @@ public class SystemUtils {
public static Long getAppVersionCode(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_META_DATA);
return pi.getLongVersionCode();
return PackageInfoCompat.getLongVersionCode(pi);
} catch (NameNotFoundException e) {
Log.w(TAG, "Failed to get version info.", e);
return -1L;
@@ -17,19 +17,14 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.R;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import de.dennisguse.opentracks.R;
/**
* Utilities for track name.
*
-142
View File
@@ -1,142 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Distance, calorie, total time, and moving time -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/stats_distance"
layout="@layout/stats_large_item" />
<include
android:id="@+id/stats_calorie"
layout="@layout/stats_activity_type" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:visibility="invisible" />
<include
android:id="@+id/stats_total_time"
layout="@layout/stats_large_item" />
<include
android:id="@+id/stats_moving_time"
layout="@layout/stats_large_item" />
</LinearLayout>
<!-- Horizontal line -->
<View style="@style/StatsHorizontalLine" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TableLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<!-- Speed and average speed -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/stats_speed"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_average_speed"
layout="@layout/stats_small_item" />
</LinearLayout>
<!-- Max speed and average moving speed -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/stats_max_speed"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_average_moving_speed"
layout="@layout/stats_small_item" />
</LinearLayout>
<!-- Latitude and longitude -->
<LinearLayout
android:id="@+id/stats_coordinate_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/stats_latitude"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_longitude"
layout="@layout/stats_small_item" />
</LinearLayout>
</TableLayout>
<!-- Vertical line -->
<View
android:id="@+id/stats_elevation_separator"
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/stats_separator" />
<LinearLayout
android:id="@+id/stats_elevation_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout style="@style/StatsSmallGroupContainer">
<!-- This is a placeholder -->
</LinearLayout>
<!-- Elevation -->
<LinearLayout style="@style/StatsSmallGroupContainer">
<!-- Elevation label -->
<TextView
style="@style/StatsSmallLabel"
android:text="@string/stats_elevation" />
<TableLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<!-- Elevation -->
<include
android:id="@+id/stats_elevation"
layout="@layout/stats_small_group_item" />
<!-- TODO DISABLED AS computation from GPS is not precise enough, go for getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure) -->
<!-- Elevation gain -->
<!--<include-->
<!--android:id="@+id/stats_elevation_gain"-->
<!--layout="@layout/stats_small_group_item" />-->
<!-- Elevation min -->
<include
android:id="@+id/stats_elevation_min"
layout="@layout/stats_small_group_item" />
<!-- Elevation max -->
<include
android:id="@+id/stats_elevation_max"
layout="@layout/stats_small_group_item" />
</TableLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</merge>
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false">
<include layout="@layout/track_controller" />
<TabHost
android:id="@android:id/tabhost"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<include layout="@layout/track_details_tab" />
</TabHost>
</LinearLayout>
-1
View File
@@ -8,7 +8,6 @@
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="239dp"
android:contentDescription="@string/image_my_tracks_logo"
android:scaleType="fitCenter"
android:src="@drawable/ic_logo" />
+1 -1
View File
@@ -25,5 +25,5 @@ limitations under the License.
<TextView
android:id="@+id/marker_list_empty"
style="@style/ListEmptyTextView"
android:text="@string/marker_list_empty_message"></TextView>
android:text="@string/marker_list_empty_message" />
</merge>
+1 -1
View File
@@ -25,5 +25,5 @@ limitations under the License.
<TextView
android:id="@+id/search_list_empty"
style="@style/ListEmptyTextView"
android:text="@string/search_list_empty_message"></TextView>
android:text="@string/search_list_empty_message" />
</merge>
+1 -1
View File
@@ -17,7 +17,7 @@ limitations under the License.
style="@style/StatsLargeItemContainer">
<!-- Label -->
<TextView
android:id="@+id/stats_label"
android:id="@+id/stats_activity_type_label"
style="@style/StatsLargeLabel"
android:text="@string/track_edit_activity_type_hint" />
<!-- Value container -->
+2 -2
View File
@@ -24,7 +24,7 @@ limitations under the License.
layout="@layout/stats_large_item" />
<include
android:id="@+id/stats_calorie"
android:id="@+id/stats_activity_type"
layout="@layout/stats_activity_type" />
</LinearLayout>
<!-- Total time and moving time -->
@@ -96,7 +96,7 @@ limitations under the License.
android:id="@+id/stats_elevation"
layout="@layout/stats_small_group_item" />
<!-- Elevation gain -->
<!-- TODO DISABLED AS computation from GPS is not precise enough, go for getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure) -->
<!-- TODO DISABLED AS computation from GPS is not precise enough, go for getAltitude(RemoteSensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure) -->
<!--<include-->
<!--android:id="@+id/stats_elevation_gain"-->
<!--layout="@layout/stats_small_group_item" />-->

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