Compare commits

...

38 Commits

Author SHA1 Message Date
Dennis Guse 931574bc11 Release v3.2.11 2019-12-25 10:19:22 +01:00
Dennis Guse caddbd8501 MarkerDetailActivity: removed leftover statistic functionality. 2019-12-23 21:44:22 +01:00
Dennis Guse 8ece2c8612 Removed WaypointCreationRequest. 2019-12-23 21:39:37 +01:00
Dennis Guse 3a219321d1 Removed country-specific translations that are identical. 2019-12-23 07:43:05 +01:00
Dennis Guse 35cb924a17 StatsFragment: show elevation without decimal digits. 2019-12-22 20:39:43 +01:00
Dennis Guse b67e5bbd20 StatsFragment: left align current elevation. 2019-12-22 20:33:23 +01:00
Dennis Guse 1db275230e Bugfix: crash on opening heart rate sensor dialog (settings). 2019-12-22 19:09:01 +01:00
Dennis Guse 5ac72092fb Test: export and import tracks via KML and GPX; now includes waypoints.
TripStatistics is not tested.
2019-12-22 18:59:23 +01:00
Dennis Guse bdd9ded1e8 Test: export and import tracks via KML and GPX.
KML now exports track icon (name; not the url).
2019-12-22 18:59:23 +01:00
Dennis Guse 759aeb6ed6 Removed DescriptionGenerator interface. 2019-12-22 18:59:23 +01:00
Dennis Guse 12023d51ae Move data classes into separate package. 2019-12-22 18:59:23 +01:00
Dennis Guse c426785b90 Cleanup. 2019-12-22 18:59:23 +01:00
Dennis Guse 09d960c7a1 First waypoint of a track does not store the statistics of the track. 2019-12-22 18:59:23 +01:00
Dennis Guse f37fe4a503 Removed WaypointType. 2019-12-22 18:59:23 +01:00
Dennis Guse 013e56bef0 Replaced TabHost by Material design TabsLayout. 2019-12-22 18:59:23 +01:00
Dennis Guse d6835ba766 TrackRecordingService does not save the context. 2019-12-22 18:59:23 +01:00
Dennis Guse d7890cbc81 Shortened fr-FR short description.
Thanks to Edern.
2019-12-17 20:35:41 +01:00
Dennis Guse 2b6886c534 Update release procedure. 2019-12-16 22:41:16 +01:00
Dennis Guse e77590a125 Release v3.2.10 2019-12-15 22:11:19 +01:00
Dennis Guse ca330b2d80 Cleanup. 2019-12-15 21:28:03 +01:00
Dennis Guse 20f1524701 Cleanup. 2019-12-15 17:09:44 +01:00
Dennis Guse 96b2a15643 Bugfix: import and export on Android 10 requires requesting (privacy-unfriendly) legacy behavior. 2019-12-15 16:50:13 +01:00
Dennis Guse ad59392e02 Fix travisCI. 2019-12-14 23:58:43 +01:00
Dennis Guse 29f92b887c Fixed TrackRecordingService.insertWaypoint() to allow deprecated behavior. 2019-12-14 23:55:04 +01:00
Dennis Guse 46328211c2 Added promotion sticker. 2019-12-14 23:55:04 +01:00
Dennis Guse 3760130d1f Github: add donation. 2019-12-14 23:42:07 +01:00
Dennis Guse 1094827fac TrackListActivity: fixed landscape layout. 2019-12-11 23:05:31 +01:00
Dennis Guse 36fcb44af5 TripRecordingService: removed unused trip statistics marker updater. 2019-12-11 23:05:31 +01:00
Dennis Guse a683fbac4b Device reboot: do not restart a not successfully ended track recording. 2019-12-11 23:05:31 +01:00
Dennis Guse 64ad9019d1 ChartView: is not configurable anymore. 2019-12-11 23:05:31 +01:00
Dennis Guse 1e80d5c8a8 ChartView: draw waypoints (again).
Functionality was (accidentally) removed in 5a264c5c53
2019-12-11 23:05:31 +01:00
Matthias Lisin ac765b1ee8 Revert duration hint to MAY_DUCK.
Pass OnAudioFocusChangeListener to abandonAudioFocus
2019-12-10 12:58:57 +01:00
Dennis Guse 850c4f0a9a TravisCI: disabled non-functioning installation of Android emulator. 2019-12-10 06:53:22 +01:00
Dennis Guse 4c32999752 Add Google Play link. 2019-12-10 06:50:58 +01:00
Dennis Guse 76823b167a TrackListActivity: removed delete all from action menu. 2019-12-08 13:12:15 +01:00
Dennis Guse 9994cfd260 Bugfix: NPE. 2019-12-07 13:37:06 +01:00
Dennis Guse 1750236474 Added automation for Google Play publication (using fastlane). 2019-12-06 22:26:40 +01:00
Dennis Guse fed6e31f9e Cleanup. 2019-12-06 08:00:20 +01:00
117 changed files with 2743 additions and 6084 deletions
+4
View File
@@ -0,0 +1,4 @@
# These are supported funding model platforms
liberapay: dennis.guse
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+6 -2
View File
@@ -73,8 +73,12 @@ captures/
.idea/workspace.xml .idea/workspace.xml
# Keystore files # Keystore files
# Uncomment the following line if you do not want to check your keystore files in. *.jks
#*.jks
# External native build folder generated in Android Studio 2.2 and later # External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild .externalNativeBuild
# Fastlane
Appfile
releasePlayStore/*
Gemfile.lock
+11 -10
View File
@@ -19,25 +19,26 @@ android:
# installing tools to start, then use `sdkmanager` below to get the rest # installing tools to start, then use `sdkmanager` below to get the rest
- tools - tools
- build-tools-28.0.3 - build-tools-28.0.3
- android-28 - android-29
# Emulator Management: Create, Start and Wait # Emulator Management: Create, Start and Wait
install: #install:
- echo y | sdkmanager "platform-tools" # - echo y | sdkmanager "platform-tools"
- echo y | sdkmanager --channel=4 "emulator" # - echo y | sdkmanager --channel=4 "emulator"
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI" # - echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M # - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 & # - emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
- android-wait-for-emulator # - android-wait-for-emulator
- adb shell input keyevent 82 & # - adb shell input keyevent 82 &
script: script:
- ./gradlew clean build - ./gradlew clean build
- ./gradlew connectedCheck # - ./gradlew connectedCheck
sbefore_cache: sbefore_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/ - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache: cache:
directories: directories:
- $HOME/.gradle/caches/ - $HOME/.gradle/caches/
-16
View File
@@ -1,16 +0,0 @@
# OpenTrack: Developer information
## Create releases for FDroid
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
2. Decide on _version name_ (semantic versioning)
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
4. Create changelog (`_version code_.txt`)
5. Create commit with all changes
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
7. Push commits and tags to public repository
8. Add changelog to tag (Github releases)
## Translations (localization)
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
+3
View File
@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"
+24 -6
View File
@@ -2,9 +2,30 @@
_OpenTracks_ is a sport tracking application that completely respects your privacy. _OpenTracks_ is a sport tracking application that completely respects your privacy.
<a href="https://f-droid.org/packages/de.dennisguse.opentracks"> <table>
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="60"> <tr>
</a> <th>Free</th>
<th>Paid</th>
<th>Donations</th>
</tr>
<tr>
<td>
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
<img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="60" align="middle">
</a>
</td>
<td>
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
</a>
</td>
<td>
<a href="https://liberapay.com/dennis.guse/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
</a>
</td>
</tr>
</table>
## Features: ## Features:
* __Tracking:__ track your sport and outdoor activities * __Tracking:__ track your sport and outdoor activities
@@ -26,9 +47,6 @@ _Please note:_
_Tracks_ does not contain a _map_ (as this would required Internet access). _Tracks_ does not contain a _map_ (as this would required Internet access).
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed. To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
<a href="https://liberapay.com/dennis.guse/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
</a>
## Screenshots ## Screenshots
<div> <div>
+37
View File
@@ -0,0 +1,37 @@
# OpenTracks: Developer information
## Craft new releases
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
2. Decide on _version name_ (semantic versioning)
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
4. Create changelog (`_version code_.txt`)
5. Create commit with all changes
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
7. Push commits and tags to public repository
8. Add changelog to tag (Github releases)
## F-Droid
Application id: de.dennisguse.opentracks
It if it properly tagged F-Droid takes care of building the app and publishing.
Usually, it takes two days before the update is published (build, signed, and published).
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
## Google Play Store
Application id: de.dennisguse.opentracks.playstore
Requirement: `gem install fastlane`
1. Build signed apk
(requires keystore)
2. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
3. Update store meta data (e.g., description and screenshots)
`fastlane supply --version_code VERSION_CODE --skip_upload_apk`
(use the version code from the manifest)
(requires API key)
## Translations (localization)
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
+6
View File
@@ -74,6 +74,11 @@ android {
release { release {
minifyEnabled false minifyEnabled false
} }
releasePlayStore {
applicationIdSuffix ".playstore"
versionNameSuffix "-PlayStore"
}
} }
applicationVariants.all { variant -> applicationVariants.all { variant ->
@@ -95,4 +100,5 @@ dependencies {
testImplementation 'androidx.test:core:1.2.0' testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.mockito:mockito-core:3.0.0' testImplementation 'org.mockito:mockito-core:3.0.0'
implementation 'com.google.android.material:material:1.0.0'
} }
+1 -1
View File
@@ -17,7 +17,7 @@
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:title></dc:title> <dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

+1 -1
View File
@@ -43,7 +43,7 @@
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:title></dc:title> <dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

+1 -1
View File
@@ -41,7 +41,7 @@
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:title></dc:title> <dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+1 -1
View File
@@ -33,7 +33,7 @@
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:title></dc:title> <dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+1 -1
View File
@@ -39,7 +39,7 @@
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:title></dc:title> <dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

+1 -1
View File
@@ -18,7 +18,7 @@
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:title></dc:title> <dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+1 -1
View File
@@ -21,7 +21,7 @@
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:title></dc:title> <dc:title />
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+38
View File
@@ -0,0 +1,38 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:android)
platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
# sh "your_script.sh"
# You can also use other beta testing services here
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
end
@@ -0,0 +1,11 @@
v3.2.10: OpenTracks
Changes:
- ChartView is not configurable anymore
- After reboot: removed automatic restart of an unfinished recording
Bugfix:
- Speech announcements use MAY_DUCK (again)
- ChartView shows markers (again)
- Fixed landscape layout of TrackListActivity
- Request legacy file API behavior (necessary for Android 10)
@@ -0,0 +1,12 @@
v3.2.11: OpenTracks
Bug:
- Max speed is identical to avg speed
Bugfix:
- Crash on heart rate sensor selection
Developer:
- Replaced TabHost by TabsLayout
- Removed special first waypoint
- Removed duplicate translations
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@@ -1 +1 @@
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée. OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.
Binary file not shown.
@@ -1,103 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.services.TrackRecordingService;
/**
* Tests for the BootReceiver.
*
* @author Youtao Liu
*/
@RunWith(AndroidJUnit4.class)
public class BootReceiverTest {
private static final String SERVICE_NAME = "de.dennisguse.opentracks.services.TrackRecordingService";
private Context context = InstrumentationRegistry.getInstrumentation().getContext();
/**
* Tests the behavior when receive notification which is the phone boot.
*/
@Test
public void testOnReceive_startService() {
// Make sure no TrackRecordingService
Intent stopIntent = new Intent(context, TrackRecordingService.class);
context.stopService(stopIntent);
Assert.assertFalse(isServiceExisted(context));
BootReceiver bootReceiver = new BootReceiver();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_BOOT_COMPLETED);
bootReceiver.onReceive(context, intent);
// Check if the service is started
Assert.assertFalse(isServiceExisted(context));
}
/**
* Tests the behavior when receive notification which is not the phone boot.
*/
@Test
public void testOnReceive_noStartService() {
// Make sure no TrackRecordingService
Intent stopIntent = new Intent(context, TrackRecordingService.class);
context.stopService(stopIntent);
Assert.assertFalse(isServiceExisted(context));
BootReceiver bootReceiver = new BootReceiver();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_BUG_REPORT);
bootReceiver.onReceive(context, intent);
// Check if the service is not started
Assert.assertFalse(isServiceExisted(context));
}
/**
* Checks if a service is started in a context.
*
* @param context the context for checking a service
*/
private boolean isServiceExisted(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
Assert.assertNotNull(activityManager);
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
for (int i = 0; i < serviceList.size(); i++) {
RunningServiceInfo serviceInfo = serviceList.get(i);
ComponentName componentName = serviceInfo.service;
if (componentName.getClassName().equals(SERVICE_NAME)) {
return true;
}
}
return false;
}
}
@@ -19,7 +19,7 @@ package de.dennisguse.opentracks;
import android.location.Location; import android.location.Location;
import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/** /**
@@ -27,6 +27,9 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import de.dennisguse.opentracks.content.CustomContentProvider.DatabaseHelper; import de.dennisguse.opentracks.content.CustomContentProvider.DatabaseHelper;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
/** /**
* Tests {@link CustomContentProvider}. * Tests {@link CustomContentProvider}.
@@ -35,7 +35,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import de.dennisguse.opentracks.content.Waypoint.WaypointType; import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -52,12 +57,9 @@ public class CustomContentProviderUtilsTest {
private static final String MOCK_DESC = "Mock Next Waypoint Desc!"; private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
private static final String TEST_DESC = "Test Desc!"; private static final String TEST_DESC = "Test Desc!";
private static final String TEST_DESC_NEW = "Test Desc new!"; private static final String TEST_DESC_NEW = "Test Desc new!";
private static final double INITIAL_LATITUDE = 37.0;
private static final double INITIAL_LONGITUDE = -57.0;
private static final double ALTITUDE_INTERVAL = 2.5;
private Context context = ApplicationProvider.getApplicationContext(); private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils; private ContentProviderUtils contentProviderUtils;
@Mock @Mock
private Cursor cursorMock; private Cursor cursorMock;
@@ -67,8 +69,8 @@ public class CustomContentProviderUtilsTest {
@Before @Before
public void setUp() { public void setUp() {
providerUtils = new ContentProviderUtils(context); contentProviderUtils = new ContentProviderUtils(context);
providerUtils.deleteAllTracks(context); contentProviderUtils.deleteAllTracks(context);
} }
@Test @Test
@@ -132,9 +134,9 @@ public class CustomContentProviderUtilsTest {
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); long lastPointId = initializeTrack(trackId, numPoints);
((ContentProviderUtils) providerUtils).setDefaultCursorBatchSize(batchSize); ((ContentProviderUtils) contentProviderUtils).setDefaultCursorBatchSize(batchSize);
List<Location> locations = new ArrayList<Location>(numPoints); List<Location> locations = new ArrayList<Location>(numPoints);
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) { try (LocationIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
while (it.hasNext()) { while (it.hasNext()) {
Location loc = it.next(); Location loc = it.next();
Assert.assertNotNull(loc); Assert.assertNotNull(loc);
@@ -153,8 +155,8 @@ public class CustomContentProviderUtilsTest {
track.setId(id); track.setId(id);
track.setName("Test: " + id); track.setName("Test: " + id);
track.setNumberOfPoints(numPoints); track.setNumberOfPoints(numPoints);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
track = providerUtils.getTrack(id); track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track); Assert.assertNotNull(track);
Location[] locations = new Location[numPoints]; Location[] locations = new Location[numPoints];
@@ -166,12 +168,12 @@ public class CustomContentProviderUtilsTest {
loc.setAltitude(i * 2.5); loc.setAltitude(i * 2.5);
locations[i] = loc; locations[i] = loc;
} }
providerUtils.bulkInsertTrackPoint(locations, numPoints, id); contentProviderUtils.bulkInsertTrackPoint(locations, numPoints, id);
// Load all inserted locations. // Load all inserted locations.
long lastPointId = -1; long lastPointId = -1;
int counter = 0; int counter = 0;
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) { try (LocationIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
while (it.hasNext()) { while (it.hasNext()) {
it.next(); it.next();
lastPointId = it.getLocationId(); lastPointId = it.getLocationId();
@@ -208,7 +210,7 @@ public class CustomContentProviderUtilsTest {
String name = NAME_PREFIX + trackId; String name = NAME_PREFIX + trackId;
when(cursorMock.getString(columnIndex++)).thenReturn(name); when(cursorMock.getString(columnIndex++)).thenReturn(name);
Track track = providerUtils.createTrack(cursorMock); Track track = contentProviderUtils.createTrack(cursorMock);
Assert.assertEquals(trackId, track.getId()); Assert.assertEquals(trackId, track.getId());
Assert.assertEquals(name, track.getName()); Assert.assertEquals(name, track.getName());
} }
@@ -220,10 +222,10 @@ public class CustomContentProviderUtilsTest {
public void testDeleteAllTracks() { public void testDeleteAllTracks() {
// Insert track, points and waypoint at first. // Insert track, points and waypoint at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track); insertTrackWithLocations(track);
Waypoint waypoint = new Waypoint(); Waypoint waypoint = new Waypoint();
providerUtils.insertWaypoint(waypoint); contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver(); 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()); Assert.assertEquals(1, tracksCursor.getCount());
@@ -232,7 +234,7 @@ public class CustomContentProviderUtilsTest {
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()); Assert.assertEquals(1, waypointCursor.getCount());
// Delete all. // Delete all.
providerUtils.deleteAllTracks(context); contentProviderUtils.deleteAllTracks(context);
// Check whether all have been deleted. // 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()); Assert.assertEquals(0, tracksCursor.getCount());
@@ -249,15 +251,15 @@ public class CustomContentProviderUtilsTest {
public void testDeleteTrack() { public void testDeleteTrack() {
// Insert three tracks, points of two tracks and way point of one track. // Insert three tracks, points of two tracks and way point of one track.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
insertTrackWithLocations(getTrack(trackId + 1, 10)); insertTrackWithLocations(TestDataUtil.getTrack(trackId + 1, 10));
insertTrackWithLocations(getTrack(trackId + 2, 10)); insertTrackWithLocations(TestDataUtil.getTrack(trackId + 2, 10));
Waypoint waypoint = new Waypoint(); Waypoint waypoint = new Waypoint();
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint); contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver(); 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);
@@ -267,7 +269,7 @@ public class CustomContentProviderUtilsTest {
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()); Assert.assertEquals(1, waypointCursor.getCount());
// Delete one track. // Delete one track.
providerUtils.deleteTrack(context, trackId); contentProviderUtils.deleteTrack(context, trackId);
// Check whether all data of a track has been deleted. // Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID); tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount()); Assert.assertEquals(2, tracksCursor.getCount());
@@ -282,10 +284,10 @@ public class CustomContentProviderUtilsTest {
*/ */
@Test @Test
public void testGetAllTracks() { public void testGetAllTracks() {
int initialTrackNumber = providerUtils.getAllTracks().size(); int initialTrackNumber = contentProviderUtils.getAllTracks().size();
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0)); contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
List<Track> allTracks = providerUtils.getAllTracks(); List<Track> allTracks = contentProviderUtils.getAllTracks();
Assert.assertEquals(initialTrackNumber + 1, allTracks.size()); Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId()); Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
} }
@@ -296,8 +298,8 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testGetLastTrack() { public void testGetLastTrack() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0)); contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
Assert.assertEquals(trackId, providerUtils.getLastTrack().getId()); Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
} }
/** /**
@@ -306,8 +308,8 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testGetTrack() { public void testGetTrack() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0)); contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
Assert.assertNotNull(providerUtils.getTrack(trackId)); Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
} }
/** /**
@@ -316,15 +318,15 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testUpdateTrack() { public void testUpdateTrack() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 0); Track track = TestDataUtil.getTrack(trackId, 0);
String nameOld = "name1"; String nameOld = "name1";
String nameNew = "name2"; String nameNew = "name2";
track.setName(nameOld); track.setName(nameOld);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
Assert.assertEquals(nameOld, providerUtils.getTrack(trackId).getName()); Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
track.setName(nameNew); track.setName(nameNew);
providerUtils.updateTrack(track); contentProviderUtils.updateTrack(track);
Assert.assertEquals(nameNew, providerUtils.getTrack(trackId).getName()); Assert.assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
} }
/** /**
@@ -333,8 +335,8 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testCreateContentValues_waypoint() { public void testCreateContentValues_waypoint() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
// Bottom // Bottom
long startTime = 1000L; long startTime = 1000L;
// AverageSpeed // AverageSpeed
@@ -355,7 +357,6 @@ public class CustomContentProviderUtilsTest {
// Insert at first. // Insert at first.
Waypoint waypoint = new Waypoint(); Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC); waypoint.setDescription(TEST_DESC);
waypoint.setType(WaypointType.STATISTICS);
waypoint.setTripStatistics(statistics); waypoint.setTripStatistics(statistics);
Location location = new Location("test"); Location location = new Location("test");
@@ -364,7 +365,7 @@ public class CustomContentProviderUtilsTest {
location.setAccuracy((float) 1 / 100.0f); location.setAccuracy((float) 1 / 100.0f);
location.setAltitude(2.5); location.setAltitude(2.5);
waypoint.setLocation(location); waypoint.setLocation(location);
providerUtils.insertWaypoint(waypoint); contentProviderUtils.insertWaypoint(waypoint);
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
@@ -406,7 +407,7 @@ public class CustomContentProviderUtilsTest {
long trackId = 11L; long trackId = 11L;
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId); when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
Waypoint waypoint = providerUtils.createWaypoint(cursorMock); Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
Assert.assertEquals(id, waypoint.getId()); Assert.assertEquals(id, waypoint.getId());
Assert.assertEquals(name, waypoint.getName()); Assert.assertEquals(name, waypoint.getName());
Assert.assertEquals(trackId, waypoint.getTrackId()); Assert.assertEquals(trackId, waypoint.getTrackId());
@@ -414,50 +415,37 @@ public class CustomContentProviderUtilsTest {
/** /**
* Tests the method * Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)} * {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* when there is only one waypoint in the track. * when there is only one waypoint in the track.
*/ */
@Test @Test
public void testDeleteWaypoint_onlyOneWayPoint() { public void testDeleteWaypoint_onlyOneWayPoint() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
// Insert at first. // Insert at first.
Waypoint waypoint1 = new Waypoint(); Waypoint waypoint1 = new Waypoint();
waypoint1.setDescription(TEST_DESC); waypoint1.setDescription(TEST_DESC);
waypoint1.setTrackId(trackId); waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS); contentProviderUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint1);
// Delete // Delete
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() { contentProviderUtils.deleteWaypoint(context, 1);
@Override Assert.assertNull(contentProviderUtils.getWaypoint(1));
public String generateWaypointDescription(TripStatistics tripStatistics) {
return CustomContentProviderUtilsTest.MOCK_DESC;
}
@Override
public String generateTrackDescription(Track aTrack, boolean html) {
return null;
}
};
providerUtils.deleteWaypoint(context, 1, descriptionGenerator);
Assert.assertNull(providerUtils.getWaypoint(1));
} }
/** /**
* Tests the method * Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)} * {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* when there is more than one waypoint in the track. * when there is more than one waypoint in the track.
*/ */
@Test @Test
public void testDeleteWaypoint_hasNextWayPoint() { public void testDeleteWaypoint_hasNextWayPoint() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
TripStatistics statistics = new TripStatistics(); TripStatistics statistics = new TripStatistics();
statistics.setStartTime(1000L); statistics.setStartTime(1000L);
@@ -475,120 +463,74 @@ public class CustomContentProviderUtilsTest {
// Insert at first. // Insert at first.
Waypoint waypoint1 = new Waypoint(); Waypoint waypoint1 = new Waypoint();
waypoint1.setDescription(Long.toString(trackId)); waypoint1.setDescription(MOCK_DESC);
waypoint1.setTrackId(trackId); waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setTripStatistics(statistics); waypoint1.setTripStatistics(statistics);
long waypoint1Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1)); long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint waypoint2 = new Waypoint(); Waypoint waypoint2 = new Waypoint();
waypoint2.setDescription(Long.toString(trackId)); waypoint2.setDescription(MOCK_DESC);
waypoint2.setTrackId(trackId); waypoint2.setTrackId(trackId);
waypoint2.setType(WaypointType.STATISTICS);
waypoint2.setTripStatistics(statistics); waypoint2.setTripStatistics(statistics);
long waypoint2Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2)); long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
// Delete // Delete
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() { Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
@Override contentProviderUtils.deleteWaypoint(context, waypoint1Id);
public String generateWaypointDescription(TripStatistics tripStatistics) { Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
return CustomContentProviderUtilsTest.MOCK_DESC;
}
@Override Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
public String generateTrackDescription(Track aTrack, boolean html) {
return null;
}
};
Assert.assertNotNull(providerUtils.getWaypoint(waypoint1Id));
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
Assert.assertEquals(CustomContentProviderUtilsTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
.getDescription());
} }
/** /**
* Tests the method {@link ContentProviderUtils#getFirstWaypointId(long)}. * Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}.
*/
@Test
public void testGetFirstWaypointId() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint();
waypoint1.setTrackId(trackId);
long waypoint1id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
Waypoint waypoint2 = new Waypoint();
waypoint2.setTrackId(trackId);
long waypoint2id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1));
Assert.assertEquals(waypoint1id, providerUtils.getFirstWaypointId(trackId));
Assert.assertEquals(waypoint2id, providerUtils.getLastWaypoint(trackId, WaypointType.WAYPOINT).getId());
}
/**
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long, WaypointType)}.
*/ */
@Test @Test
public void testGetNextWaypointNumber() { public void testGetNextWaypointNumber() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint(); Waypoint waypoint1 = new Waypoint();
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setTrackId(trackId); waypoint1.setTrackId(trackId);
Waypoint waypoint2 = new Waypoint(); Waypoint waypoint2 = new Waypoint();
waypoint2.setType(WaypointType.WAYPOINT);
waypoint2.setTrackId(trackId); waypoint2.setTrackId(trackId);
Waypoint waypoint3 = new Waypoint(); Waypoint waypoint3 = new Waypoint();
waypoint3.setType(WaypointType.STATISTICS);
waypoint3.setTrackId(trackId); waypoint3.setTrackId(trackId);
Waypoint waypoint4 = new Waypoint(); Waypoint waypoint4 = new Waypoint();
waypoint4.setType(WaypointType.WAYPOINT);
waypoint4.setTrackId(trackId); waypoint4.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint1); contentProviderUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint2); contentProviderUtils.insertWaypoint(waypoint2);
providerUtils.insertWaypoint(waypoint3); contentProviderUtils.insertWaypoint(waypoint3);
providerUtils.insertWaypoint(waypoint4); contentProviderUtils.insertWaypoint(waypoint4);
Assert.assertEquals(2, providerUtils.getNextWaypointNumber(trackId, WaypointType.STATISTICS)); Assert.assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
Assert.assertEquals(3, providerUtils.getNextWaypointNumber(trackId, WaypointType.WAYPOINT));
} }
/** /**
* Tests the method * Tests the method
* {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}. * {@link ContentProviderUtils#getLastWaypoint(long)}.
*/ */
@Test @Test
public void testGetLastStatisticsWaypoint() { public void testGetLastWaypoint() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint(); Waypoint waypoint1 = new Waypoint();
waypoint1.setTrackId(trackId); waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setDescription("Desc1"); waypoint1.setDescription("Desc1");
Waypoint waypoint2 = new Waypoint(); Waypoint waypoint2 = new Waypoint();
waypoint2.setTrackId(trackId); waypoint2.setTrackId(trackId);
waypoint2.setType(WaypointType.STATISTICS);
waypoint2.setDescription("Desc2"); waypoint2.setDescription("Desc2");
Waypoint waypoint3 = new Waypoint(); Waypoint waypoint3 = new Waypoint();
waypoint3.setTrackId(trackId); waypoint3.setTrackId(trackId);
waypoint3.setType(WaypointType.WAYPOINT);
waypoint3.setDescription("Desc3"); waypoint3.setDescription("Desc3");
providerUtils.insertWaypoint(waypoint1); contentProviderUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint2); contentProviderUtils.insertWaypoint(waypoint2);
providerUtils.insertWaypoint(waypoint3); contentProviderUtils.insertWaypoint(waypoint3);
Assert.assertEquals("Desc2", providerUtils.getLastWaypoint(trackId, WaypointType.STATISTICS).getDescription()); Assert.assertEquals("Desc3", contentProviderUtils.getLastWaypoint(trackId).getDescription());
} }
/** /**
@@ -598,15 +540,15 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testInsertAndGetWaypoint() { public void testInsertAndGetWaypoint() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
Waypoint waypoint = new Waypoint(); Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC); waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint)); long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(waypointId).getDescription()); Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
} }
/** /**
@@ -615,20 +557,20 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testUpdateWaypoint() { public void testUpdateWaypoint() {
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
// Insert at first. // Insert at first.
Waypoint waypoint = new Waypoint(); Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC); waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint)); long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
// Update // Update
waypoint = providerUtils.getWaypoint(waypointId); waypoint = contentProviderUtils.getWaypoint(waypointId);
waypoint.setDescription(TEST_DESC_NEW); waypoint.setDescription(TEST_DESC_NEW);
providerUtils.updateWaypoint(waypoint); contentProviderUtils.updateWaypoint(waypoint);
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(waypointId).getDescription()); Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
} }
/** /**
@@ -638,13 +580,13 @@ public class CustomContentProviderUtilsTest {
public void testBulkInsertTrackPoint() { public void testBulkInsertTrackPoint() {
// Insert track, point at first. // Insert track, point at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track); insertTrackWithLocations(track);
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId); contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
Assert.assertEquals(20, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId); contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
Assert.assertEquals(28, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
} }
/** /**
@@ -699,7 +641,7 @@ public class CustomContentProviderUtilsTest {
byte[] sensor = "Sensor state".getBytes(); byte[] sensor = "Sensor state".getBytes();
when(cursorMock.getBlob(index++)).thenReturn(sensor); when(cursorMock.getBlob(index++)).thenReturn(sensor);
Location location = providerUtils.createTrackPoint(cursorMock); Location location = contentProviderUtils.createTrackPoint(cursorMock);
Assert.assertEquals((double) longitude, location.getLongitude(), 0.01); Assert.assertEquals((double) longitude, location.getLongitude(), 0.01);
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01); Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
Assert.assertEquals(time, location.getTime(), 0.01); Assert.assertEquals(time, location.getTime(), 0.01);
@@ -714,11 +656,11 @@ public class CustomContentProviderUtilsTest {
public void testInsertTrackPoint() { public void testInsertTrackPoint() {
// Insert track, point at first. // Insert track, point at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track); insertTrackWithLocations(track);
providerUtils.insertTrackPoint(createLocation(22), trackId); contentProviderUtils.insertTrackPoint(TestDataUtil.createLocation(22), trackId);
Assert.assertEquals(11, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); Assert.assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
} }
/** /**
@@ -728,10 +670,10 @@ public class CustomContentProviderUtilsTest {
public void testGetFirstTrackPointId() { public void testGetFirstTrackPointId() {
// Insert track, point at first. // Insert track, point at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track); insertTrackWithLocations(track);
Assert.assertNotEquals(-1L, providerUtils.getFirstTrackPointId(trackId)); Assert.assertNotEquals(-1L, contentProviderUtils.getFirstTrackPointId(trackId));
} }
/** /**
@@ -741,10 +683,10 @@ public class CustomContentProviderUtilsTest {
public void testGetLastTrackPointId() { public void testGetLastTrackPointId() {
// Insert track, point at first. // Insert track, point at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track); insertTrackWithLocations(track);
Assert.assertTrue(providerUtils.getFirstTrackPointId(trackId) < providerUtils.getLastTrackPointId(trackId)); Assert.assertTrue(contentProviderUtils.getFirstTrackPointId(trackId) < contentProviderUtils.getLastTrackPointId(trackId));
} }
/** /**
@@ -754,10 +696,10 @@ public class CustomContentProviderUtilsTest {
public void testGetLastValidTrackPoint() { public void testGetLastValidTrackPoint() {
// Insert track, points at first. // Insert track, points at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track); insertTrackWithLocations(track);
Location lastLocation = providerUtils.getLastValidTrackPoint(trackId); Location lastLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
checkLocation(9, lastLocation); checkLocation(9, lastLocation);
} }
@@ -768,15 +710,15 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointCursor_desc() { public void testGetTrackPointCursor_desc() {
// Insert track, points at first. // Insert track, points at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()]; long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) { for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId())); trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
} }
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true); Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
Assert.assertEquals(2, cursor.getCount()); Assert.assertEquals(2, cursor.getCount());
} }
@@ -787,15 +729,15 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointCursor_asc() { public void testGetTrackPointCursor_asc() {
// Insert track, points at first. // Insert track, points at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()]; long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) { for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId())); trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
} }
Cursor cursor = providerUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false); Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
Assert.assertEquals(2, cursor.getCount()); Assert.assertEquals(2, cursor.getCount());
} }
@@ -806,17 +748,17 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointLocationIterator_desc() { public void testGetTrackPointLocationIterator_desc() {
// Insert track, points at first. // Insert track, points at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()]; long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) { for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId())); trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
} }
long startTrackPointId = trackpointIds[9]; long startTrackPointId = trackpointIds[9];
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY); LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
for (int i = 0; i < trackpointIds.length; i++) { for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext()); Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next(); Location location = locationIterator.next();
@@ -833,17 +775,17 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointLocationIterator_asc() { public void testGetTrackPointLocationIterator_asc() {
// Insert track, point at first. // Insert track, point at first.
long trackId = System.currentTimeMillis(); long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10); Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()]; long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) { for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId())); trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
} }
long startTrackPointId = trackpointIds[0]; long startTrackPointId = trackpointIds[0];
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY); LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
for (int i = 0; i < trackpointIds.length; i++) { for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext()); Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next(); Location location = locationIterator.next();
@@ -854,50 +796,17 @@ public class CustomContentProviderUtilsTest {
Assert.assertFalse(locationIterator.hasNext()); Assert.assertFalse(locationIterator.hasNext());
} }
/**
* Simulates a track which is used for testing.
*
* @param id the id of the track
* @param numPoints the location number in the track
* @return the simulated track
*/
private Track getTrack(long id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
for (int i = 0; i < numPoints; i++) {
track.addLocation(createLocation(i));
}
return track;
}
/**
* Creates a location.
*
* @param i the index to set the value of location.
* @return created location
*/
private Location createLocation(int i) {
Location loc = new Location("test");
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * ALTITUDE_INTERVAL);
return loc;
}
/** /**
* Checks the value of a location. * Checks the value of a location.
* *
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsTest#getTrack(long, int)} * @param i the index of this location which created in the method {@link TestDataUtil#getTrack(long, int)}
* @param location the location to be checked * @param location the location to be checked
*/ */
private void checkLocation(int i, Location location) { private void checkLocation(int i, Location location) {
Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01); Assert.assertEquals(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01); Assert.assertEquals(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
Assert.assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01); Assert.assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude(), 0.01); Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
} }
/** /**
@@ -906,7 +815,7 @@ public class CustomContentProviderUtilsTest {
* @param track track to be inserted * @param track track to be inserted
*/ */
private void insertTrackWithLocations(Track track) { private void insertTrackWithLocations(Track track) {
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId()); contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
} }
} }
@@ -27,29 +27,31 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
/** /**
* Tests for {@link DescriptionGeneratorImpl}. * Tests for {@link DescriptionGenerator}.
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DescriptionGeneratorImplTest { public class DescriptionGeneratorTest {
private static final long START_TIME = 1288721514000L; private static final long START_TIME = 1288721514000L;
private DescriptionGeneratorImpl descriptionGenerator; private DescriptionGenerator descriptionGenerator;
private Context context = ApplicationProvider.getApplicationContext(); private Context context = ApplicationProvider.getApplicationContext();
@Before @Before
public void setUp() { public void setUp() {
descriptionGenerator = new DescriptionGeneratorImpl(ApplicationProvider.getApplicationContext()); descriptionGenerator = new DescriptionGenerator(ApplicationProvider.getApplicationContext());
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, boolean)}. * Tests {@link DescriptionGenerator#generateTrackDescription(Track, boolean)}.
*/ */
@Test @Test
public void testGenerateTrackDescription() { public void testGenerateTrackDescription() {
@@ -92,7 +94,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#generateWaypointDescription(TripStatistics)}. * Tests {@link DescriptionGenerator#generateWaypointDescription(TripStatistics)}.
*/ */
@Test @Test
public void testGenerateWaypointDescription() { public void testGenerateWaypointDescription() {
@@ -128,7 +130,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, int, String)}. * Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
*/ */
@Test @Test
public void testWriteDistance() { public void testWriteDistance() {
@@ -138,7 +140,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, String)}. * Tests {@link DescriptionGenerator#writeTime(long, StringBuilder, int, String)}.
*/ */
@Test @Test
public void testWriteTime() { public void testWriteTime() {
@@ -148,7 +150,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, int, String)}. * Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}.
*/ */
@Test @Test
public void testWriteSpeed() { public void testWriteSpeed() {
@@ -158,7 +160,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, int, String)}. * Tests {@link DescriptionGenerator#writeElevation(double, StringBuilder, int, String)}.
*/ */
@Test @Test
public void testWriteElevation() { public void testWriteElevation() {
@@ -168,7 +170,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, String)}. * Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}.
*/ */
@Test @Test
public void testWritePace() { public void testWritePace() {
@@ -178,7 +180,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)}. * Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)}.
*/ */
@Test @Test
public void testWriteGrade() { public void testWriteGrade() {
@@ -188,7 +190,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with a NaN. * Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with a NaN.
*/ */
@Test @Test
public void testWriteGrade_nan() { public void testWriteGrade_nan() {
@@ -198,7 +200,7 @@ public class DescriptionGeneratorImplTest {
} }
/** /**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with an infinite number. * Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with an infinite number.
*/ */
@Test @Test
public void testWriteGrade_infinite() { public void testWriteGrade_infinite() {
@@ -36,6 +36,8 @@ import java.util.List;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult; import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery; import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
/** /**
@@ -0,0 +1,44 @@
package de.dennisguse.opentracks.content.data;
import android.location.Location;
public class TestDataUtil {
public static final double INITIAL_LATITUDE = 37.0;
public static final double INITIAL_LONGITUDE = -57.0;
public static final double ALTITUDE_INTERVAL = 2.5;
/**
* Simulates a track which is used for testing.
*
* @param id the id of the track
* @param numPoints the location number in the track
* @return the simulated track
*/
public static Track getTrack(long id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
for (int i = 0; i < numPoints; i++) {
track.addLocation(createLocation(i));
}
return track;
}
/**
* Creates a location.
*
* @param i the index to set the value of location.
* @return created location
*/
public static Location createLocation(int i) {
Location loc = new Location("test");
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * ALTITUDE_INTERVAL);
loc.setTime(i + 1);
return loc;
}
}
@@ -33,10 +33,10 @@ import java.util.Locale;
import java.util.SimpleTimeZone; import java.util.SimpleTimeZone;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
@@ -0,0 +1,219 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.location.Location;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
* <p>
* TODO: test ignores {@link de.dennisguse.opentracks.stats.TripStatistics} for now.
*/
@RunWith(JUnit4.class)
public class ExportImportTest {
private static final String TAG = ExportImportTest.class.getSimpleName();
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
private static final String TRACK_ICON = "the track icon";
private static final String TRACK_CATEGORY = "the category";
private static final String TRACK_DESCRIPTION = "the description";
private final List<Waypoint> waypoints = new ArrayList<>();
private long importTrackId;
private long trackId = System.currentTimeMillis();
@Before
public void setUp() {
Track track = TestDataUtil.getTrack(trackId, 10);
track.setIcon(TRACK_ICON);
track.setCategory(TRACK_CATEGORY);
track.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.insertTrack(track);
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
for (int i = 0; i < 3; i++) {
Waypoint waypoint = new Waypoint();
waypoint.setName("the waypoint " + i);
waypoint.setDescription("the waypoint description " + i);
waypoint.setCategory("the waypoint category" + i);
waypoint.setIcon("the waypoing icon" + i);
waypoint.setPhotoUrl("the photo url" + i);
waypoint.setTrackId(trackId);
waypoint.setLocation(track.getLocations().get(i));
contentProviderUtils.insertWaypoint(waypoint);
waypoints.add(waypoint);
}
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(trackId));
}
@After
public void tearDown() {
contentProviderUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, importTrackId);
}
@LargeTest
@Test
public void kml_only_track() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kml_with_trackdetail() {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
System.out.println(outputStream.toString());
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
importTrackId = trackImporter.importFile(inputStream);
// then
// 1. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals(track.getLocations(), importedTrack.getLocations());
assertEquals(track.getCategory(), importedTrack.getCategory());
assertEquals(track.getDescription(), importedTrack.getDescription());
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.getIcon(), importedTrack.getIcon());
// 2. waypoints
assertWaypoints();
//TODO Check absolute time of trackpoints
}
@LargeTest
@Test
public void kml_with_trackdetail_and_sensordata() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kmz_only_track() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kmz_with_trackdetail() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kmz_with_trackdetail_and_sensordata() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void kmz_with_trackdetail_and_sensordata_and_pictures() {
// TODO
Log.e(TAG, "Test not implemented.");
}
@LargeTest
@Test
public void gpx() {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
// then
// 1. track
Track trackImported = contentProviderUtils.getTrack(importTrackId);
assertNotNull(trackImported);
assertEquals(track.getLocations(), trackImported.getLocations());
assertEquals(track.getCategory(), trackImported.getCategory());
assertEquals(track.getDescription(), trackImported.getDescription());
assertEquals(track.getName(), trackImported.getName());
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
//assertEquals(track.getIcon(), trackImported.getIcon());
// 2. waypoints
assertWaypoints();
//TODO Check absolute time of trackpoints
}
private void assertWaypoints() {
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(importTrackId));
List<Waypoint> importedWaypoints = contentProviderUtils.getWaypoints(importTrackId);
for (int i = 0; i < waypoints.size(); i++) {
Waypoint waypoint = waypoints.get(i);
Waypoint importedWaypoint = importedWaypoints.get(i);
assertEquals(waypoint.getCategory(), importedWaypoint.getCategory());
assertEquals(waypoint.getDescription(), importedWaypoint.getDescription());
// assertEquals(waypoint.getIcon(), importedWaypoint.getIcon()); // TODO for KML
assertEquals(waypoint.getName(), importedWaypoint.getName());
assertEquals("", importedWaypoint.getPhotoUrl());
assertEquals(waypoint.getLocation().getLatitude(), importedWaypoint.getLocation().getLatitude(), 0.001);
assertEquals(waypoint.getLocation().getLongitude(), importedWaypoint.getLocation().getLongitude(), 0.001);
assertEquals(waypoint.getLocation().getAltitude(), importedWaypoint.getLocation().getAltitude(), 0.001);
}
}
}
@@ -27,7 +27,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
@@ -27,7 +27,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
@@ -53,10 +53,8 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
assertEquals(MAX, adocationListenerPolicy.getDesiredPollingInterval()); assertEquals(MAX, adocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL); adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the // First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
// nearest second. assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, adocationListenerPolicy.getDesiredPollingInterval());
assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000,
adocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL); adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval()); assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval());
@@ -44,10 +44,8 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider; import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -56,7 +54,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
* *
* @author Bartlomiej Niechwiej * @author Bartlomiej Niechwiej
* <p> * <p>
* ATTENTION: This tests deletes all stored tracks in the database. * //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost. * So, if it is executed on a real device, data might be lost.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@@ -69,19 +67,14 @@ public class TrackRecordingServiceTest {
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION); public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private Context context = ApplicationProvider.getApplicationContext(); private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils; private ContentProviderUtils contentProviderUtils;
private long trackId = Math.abs(new Random().nextLong()); private final long trackId = Math.abs(new Random().nextLong());
static Intent createStartIntent(Context context) { static Intent createStartIntent(Context context) {
return new Intent(context, TrackRecordingService.class); return new Intent(context, TrackRecordingService.class);
} }
static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
PreferencesUtils.setString(context, R.string.auto_resume_track_timeout_key, "" + timeoutMins);
}
@Before @Before
public void setUp() { public void setUp() {
// Set up the mock content resolver // Set up the mock content resolver
@@ -89,17 +82,14 @@ public class TrackRecordingServiceTest {
}; };
customContentProvider.attachInfo(context, null); customContentProvider.attachInfo(context, null);
providerUtils = new ContentProviderUtils(context); contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values. // Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context); SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply(); sharedPreferences.edit().clear().apply();
// Disable auto resume by default.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
// Ensure that the database is empty before every test // Ensure that the database is empty before every test
providerUtils.deleteAllTracks(context); contentProviderUtils.deleteAllTracks(context);
} }
@After @After
@@ -111,7 +101,7 @@ public class TrackRecordingServiceTest {
} }
// Ensure that the database is empty after every test // Ensure that the database is empty after every test
providerUtils.deleteAllTracks(context); contentProviderUtils.deleteAllTracks(context);
} }
@SmallTest @SmallTest
@@ -128,128 +118,10 @@ public class TrackRecordingServiceTest {
Assert.assertNotNull(service); Assert.assertNotNull(service);
} }
@MediumTest
@Test
public void testResumeAfterReboot_shouldResume() throws Exception {
// Insert a dummy track and mark it as recording track.
createDummyTrack(trackId, System.currentTimeMillis(), true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// then
Assert.assertNotNull(service);
// We expect to resume the previous track.
Assert.assertTrue(service.isRecording());
Assert.assertEquals(trackId, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_simulateReboot() throws Exception {
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
// Simulate recording a track.
long id = service.startNewTrack();
Assert.assertTrue(service.isRecording());
Assert.assertEquals(id, service.getRecordingTrackId());
mServiceRule.unbindService();
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
// then
Assert.assertNotNull(service);
Assert.assertTrue(service.isRecording());
}
@MediumTest
@Test
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
// Insert a dummy track and mark it as recording track.
createDummyTrack(trackId, System.currentTimeMillis(), false);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// then
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because it was stopped.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_expiredTrack() throws Exception {
// Insert a dummy track last updated 20 min ago.
createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
// Clear the number of attempts and set the timeout to 10 min.
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
// then
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because it has expired.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest
@Test
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
// Insert a dummy track.
createDummyTrack(trackId, System.currentTimeMillis(), true);
// Set the number of attempts to max.
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
// Start the service in "resume" mode (simulates the on-reboot action).
Intent startIntent = createStartIntent(context);
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
//then
Assert.assertNotNull(service);
// We don't expect to resume the previous track, because there were already too many attempts.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@MediumTest @MediumTest
@Test @Test
public void testRecording_noTracks() throws Exception { public void testRecording_noTracks() throws Exception {
List<Track> tracks = providerUtils.getAllTracks(); List<Track> tracks = contentProviderUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty()); Assert.assertTrue(tracks.isEmpty());
Intent startIntent = createStartIntent(context); Intent startIntent = createStartIntent(context);
@@ -289,16 +161,18 @@ public class TrackRecordingServiceTest {
@MediumTest @MediumTest
@Test @Test
public void testStartNewTrack_alreadyRecording() throws Exception { public void testStartNewTrack_alreadyRecording() throws Exception {
createDummyTrack(trackId, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context))); ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording()); Assert.assertTrue(service.isRecording());
long trackId = service.getRecordingTrackId();
long newTrackId = service.startNewTrack(); long newTrackId = service.startNewTrack();
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId); Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context)); Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId()); Assert.assertEquals(trackId, service.getRecordingTrackId());
service.endCurrentTrack();
} }
@MediumTest @MediumTest
@@ -314,44 +188,7 @@ public class TrackRecordingServiceTest {
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
} }
@MediumTest // NOTE: Do not use to create a track that is currently recording.
@Test
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
Assert.assertEquals(-1L, waypointId);
}
@MediumTest
@Test
public void testInsertStatisticsMarker_validLocation() throws Exception {
createDummyTrack(trackId, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertTrue(service.isRecording());
Assert.assertFalse(service.isPaused());
insertLocation(service);
long waypointId1 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
Assert.assertNotEquals(-1L, waypointId1);
long waypointId2 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
Assert.assertNotEquals(-1L, waypointId2);
Waypoint wpt = providerUtils.getWaypoint(waypointId1);
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 0), wpt.getName());
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
Assert.assertEquals(trackId, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
Assert.assertNotNull(wpt.getTripStatistics());
// TODO check the rest of the params.
// TODO: Check waypoint 2.
}
private void createDummyTrack(long id, long stopTime, boolean isRecording) { private void createDummyTrack(long id, long stopTime, boolean isRecording) {
Track dummyTrack = new Track(); Track dummyTrack = new Track();
dummyTrack.setId(id); dummyTrack.setId(id);
@@ -364,8 +201,8 @@ public class TrackRecordingServiceTest {
private void addTrack(Track track, boolean isRecording) { private void addTrack(Track track, boolean isRecording) {
Assert.assertTrue(track.getId() >= 0); Assert.assertTrue(track.getId() >= 0);
providerUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
Assert.assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId()); Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); PreferencesUtils.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); PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
} }
@@ -392,28 +229,29 @@ public class TrackRecordingServiceTest {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context))); ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT); long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertEquals(-1L, waypointId); Assert.assertEquals(-1L, waypointId);
} }
@MediumTest @MediumTest
@Test @Test
public void testInsertWaypointMarker_validWaypoint() throws Exception { public void testInsertWaypointMarker_validWaypoint() throws Exception {
createDummyTrack(trackId, -1L, true);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context))); ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording()); Assert.assertTrue(service.isRecording());
insertLocation(service); insertLocation(service);
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT); long trackId = service.getRecordingTrackId();
long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertNotEquals(-1L, waypointId); Assert.assertNotEquals(-1L, waypointId);
Waypoint wpt = providerUtils.getWaypoint(waypointId); Waypoint wpt = contentProviderUtils.getWaypoint(waypointId);
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon()); Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName()); Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
Assert.assertEquals(WaypointType.WAYPOINT, wpt.getType());
Assert.assertEquals(trackId, wpt.getTrackId()); Assert.assertEquals(trackId, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01); Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation()); Assert.assertNotNull(wpt.getLocation());
Assert.assertNull(wpt.getTripStatistics()); Assert.assertNull(wpt.getTripStatistics());
service.endCurrentTrack();
} }
} }
@@ -27,8 +27,7 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider; import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -74,9 +73,6 @@ public class TrackRecordingServiceTestLooper {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context); SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply(); sharedPreferences.edit().clear().apply();
// Disable auto resume by default.
TrackRecordingServiceTest.updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
// Ensure that the database is empty before every test // Ensure that the database is empty before every test
providerUtils.deleteAllTracks(context); providerUtils.deleteAllTracks(context);
} }
@@ -221,10 +217,8 @@ public class TrackRecordingServiceTestLooper {
location.setBearing(3.0f); location.setBearing(3.0f);
service.insertTrackPoint(location); service.insertTrackPoint(location);
if (i % 10 == 0) { if (i % 7 == 0) {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS); service.insertWaypoint(null, null, null, null);
} else if (i % 7 == 0) {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
} }
} }
+4 -15
View File
@@ -16,8 +16,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks" package="de.dennisguse.opentracks"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="3175" android:versionCode="3213"
android:versionName="v3.2.9"> <!-- For FDroid. --> android:versionName="v3.2.11"> <!-- For FDroid. -->
<!-- Permission to initialize services --> <!-- Permission to initialize services -->
<application <application
android:name=".Startup" android:name=".Startup"
@@ -25,6 +25,7 @@ limitations under the License.
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@drawable/ic_logo_color_24dp" android:icon="@drawable/ic_logo_color_24dp"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:theme="@style/ThemeCustom"> android:theme="@style/ThemeCustom">
<!-- Activities --> <!-- Activities -->
@@ -83,9 +84,7 @@ limitations under the License.
<activity android:name=".TrackEditActivity" /> <activity android:name=".TrackEditActivity" />
<activity <activity android:name=".TrackListActivity">
android:name=".TrackListActivity"
android:screenOrientation="userPortrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@@ -101,7 +100,6 @@ limitations under the License.
<activity android:name=".settings.SettingsActivity" /> <activity android:name=".settings.SettingsActivity" />
<!-- Services -->
<provider <provider
android:name=".content.ShareContentProvider" android:name=".content.ShareContentProvider"
android:authorities="${applicationId}.content" android:authorities="${applicationId}.content"
@@ -118,14 +116,6 @@ limitations under the License.
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
<!-- Receivers -->
<receiver android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- Providers -->
<service <service
android:name=".services.TrackRecordingService" android:name=".services.TrackRecordingService"
android:enabled="true" android:enabled="true"
@@ -157,7 +147,6 @@ limitations under the License.
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -1,57 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import de.dennisguse.opentracks.services.TrackRecordingService;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
/**
* This class react to the BOOT_COMPLETED broadcast.
* <p>
* One example of a broadcast message that this class is interested in,
* is notification about the phone boot. We may want to resume a previously
* started tracking session if the phone crashed (hopefully not), or the user
* decided to swap the battery or some external event occurred which forced
* a phone reboot.
* <p>
* This class simply delegates to {@link TrackRecordingService} to make a
* decision whether to continue with the previous track (if any), or just
* abandon it.
*
* @author Bartlomiej Niechwiej
*/
public class BootReceiver extends BroadcastReceiver {
private static final String TAG = BootReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "BootReceiver.onReceive: " + intent.getAction());
if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Intent startIntent = new Intent(context, TrackRecordingService.class)
.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
context.startService(startIntent);
} else {
Log.w(TAG, "BootReceiver: unsupported action");
}
}
}
@@ -31,7 +31,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.fragments.MarkerDetailFragment; import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
@@ -26,9 +26,7 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
/** /**
@@ -48,9 +46,6 @@ public class MarkerEditActivity extends AbstractActivity {
private Waypoint waypoint; private Waypoint waypoint;
// UI elements // UI elements
private View statisticsSection;
private EditText statisticsName;
private View waypointSection;
private EditText waypointName; private EditText waypointName;
private AutoCompleteTextView waypointMarkerType; private AutoCompleteTextView waypointMarkerType;
private EditText waypointDescription; private EditText waypointDescription;
@@ -65,10 +60,6 @@ public class MarkerEditActivity extends AbstractActivity {
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
// Setup UI elements // Setup UI elements
statisticsSection = findViewById(R.id.marker_edit_statistics_section);
statisticsName = findViewById(R.id.marker_edit_statistics_name);
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
waypointName = findViewById(R.id.marker_edit_waypoint_name); waypointName = findViewById(R.id.marker_edit_waypoint_name);
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type); waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
@@ -95,7 +86,7 @@ public class MarkerEditActivity extends AbstractActivity {
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
trackRecordingServiceConnection.unbind(); trackRecordingServiceConnection.unbind(this);
} }
@Override @Override
@@ -124,9 +115,7 @@ public class MarkerEditActivity extends AbstractActivity {
}); });
if (newMarker) { if (newMarker) {
statisticsSection.setVisibility(View.GONE); int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
waypointSection.setVisibility(View.VISIBLE);
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
if (nextWaypointNumber == -1) { if (nextWaypointNumber == -1) {
nextWaypointNumber = 0; nextWaypointNumber = 0;
} }
@@ -141,46 +130,31 @@ public class MarkerEditActivity extends AbstractActivity {
finish(); finish();
return; return;
} }
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
statisticsSection.setVisibility(statistics ? View.VISIBLE : View.GONE);
waypointSection.setVisibility(statistics ? View.GONE : View.VISIBLE);
if (statistics) {
statisticsName.setText(waypoint.getName());
} else {
waypointName.setText(waypoint.getName()); waypointName.setText(waypoint.getName());
waypointMarkerType.setText(waypoint.getCategory()); waypointMarkerType.setText(waypoint.getCategory());
waypointDescription.setText(waypoint.getDescription()); waypointDescription.setText(waypoint.getDescription());
} }
} }
}
/** /**
* Adds a marker. * Adds a marker.
*/ */
private void addMarker() { private void addMarker() {
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest( trackRecordingServiceConnection.addMarker(this,
WaypointType.WAYPOINT,
false,
waypointName.getText().toString(), waypointName.getText().toString(),
waypointMarkerType.getText().toString(), waypointMarkerType.getText().toString(),
waypointDescription.getText().toString(), waypointDescription.getText().toString(),
null,
null); null);
trackRecordingServiceConnection.addMarker(this, waypointCreationRequest);
} }
/** /**
* Saves a marker. * Saves a marker.
*/ */
private void saveMarker() { private void saveMarker() {
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
if (statistics) {
waypoint.setName(statisticsName.getText().toString());
} else {
waypoint.setName(waypointName.getText().toString()); waypoint.setName(waypointName.getText().toString());
waypoint.setCategory(waypointMarkerType.getText().toString()); waypoint.setCategory(waypointMarkerType.getText().toString());
waypoint.setDescription(waypointDescription.getText().toString()); waypoint.setDescription(waypointDescription.getText().toString());
}
new ContentProviderUtils(this).updateWaypoint(waypoint); new ContentProviderUtils(this).updateWaypoint(waypoint);
} }
} }
@@ -38,8 +38,8 @@ import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.ActivityUtils;
@@ -74,8 +74,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
* Note that sharedPreferenceChangeListener cannot be an anonymous inner * Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected. * class. Anonymous inner class will get garbage collected.
*/ */
private final OnSharedPreferenceChangeListener private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
// Note that the key can be null // Note that the key can be null
@@ -127,17 +126,14 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null; track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null;
final long trackFirstWaypointId = trackId != -1 ? contentProviderUtils.getFirstWaypointId(trackId) : -1;
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
listView = findViewById(R.id.marker_list); listView = findViewById(R.id.marker_list);
listView.setEmptyView(findViewById(R.id.marker_list_empty)); listView.setEmptyView(findViewById(R.id.marker_list_empty));
@@ -166,6 +162,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
String category = cursor.getString(categoryIndex); String category = cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex); String description = cursor.getString(descriptionIndex);
String photoUrl = cursor.getString(photoUrlIndex); String photoUrl = cursor.getString(photoUrlIndex);
//TODO also show latitude and longitude in list
double latitude = cursor.getDouble(latitudeIndex); double latitude = cursor.getDouble(latitudeIndex);
double longitude = cursor.getDouble(longitudeIndex); double longitude = cursor.getDouble(longitudeIndex);
@@ -181,8 +178,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
if (track != null) { if (track != null) {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION, return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + "!=?", WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(track.getId()), String.valueOf(trackFirstWaypointId)}, null); new String[]{String.valueOf(track.getId())}, null);
} else { } else {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION, return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.STARTTIME + " IS NULL", null, null); WaypointsColumns.STARTTIME + " IS NULL", null, null);
@@ -244,7 +241,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (track != null && item.getItemId() == R.id.marker_list_insert_marker) { if (track != null && item.getItemId() == R.id.marker_list_insert_marker) {
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class) Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId()); .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId());
@@ -44,8 +44,8 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.SearchEngine; import de.dennisguse.opentracks.content.SearchEngine;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult; import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery; import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
@@ -245,7 +245,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(); trackRecordingServiceConnection.unbind(this);
} }
@Override @Override
@@ -1,153 +0,0 @@
/*
* Copyright 2013q Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabHost.TabContentFactory;
import android.widget.TabWidget;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
/**
* This is a helper class that implements the management of tabs and all details
* of connecting a ViewPager with associated TabHost. It relies on a trick.
* Normally a tab host has a simple API for supplying a View or Intent that each
* tab will show. This is not sufficient for switching between pages. So instead
* we make the content part of the tab host 0dp high (it is not shown) and the
* TabsAdapter supplies its own dummy view to show as the tab content. It
* listens to changes in tabs, and takes care of switch to the correct paged in
* the ViewPager whenever the selected tab changes.
* <p>
* Copied from the FragmentTabsPager sample in the support library.
*
* @author Jimmy Shih
*/
//TODO Rework or better remove.
public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
private final Context context;
private final TabHost tabHost;
private final ViewPager viewPager;
private final ArrayList<TabInfo> tabInfos = new ArrayList<>();
public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager viewPager) {
super(activity.getSupportFragmentManager());
this.context = activity;
this.tabHost = tabHost;
this.viewPager = viewPager;
this.tabHost.setOnTabChangedListener(this);
this.viewPager.setAdapter(this);
this.viewPager.setOnPageChangeListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle bundle) {
tabSpec.setContent(new DummyTabFactory(context));
TabInfo info = new TabInfo(clss, bundle);
tabInfos.add(info);
tabHost.addTab(tabSpec);
notifyDataSetChanged();
}
@Override
public int getCount() {
return tabInfos.size();
}
@Override
@NonNull
public Fragment getItem(int position) {
TabInfo info = tabInfos.get(position);
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
}
@Override
public void onTabChanged(String tabId) {
int position = tabHost.getCurrentTab();
viewPager.setCurrentItem(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
/*
* Unfortunately when TabHost changes the current tab, it kindly also takes care of putting focus on it when not in touch mode.
* The jerk. This hack tries to prevent this from pulling focus out of our ViewPager.
*/
TabWidget tabWidget = tabHost.getTabWidget();
int oldFocusability = tabWidget.getDescendantFocusability();
tabWidget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
tabHost.setCurrentTab(position);
tabWidget.setDescendantFocusability(oldFocusability);
}
@Override
public void onPageScrollStateChanged(int state) {
}
/**
* An object to hold a tab's info.
*
* @author Jimmy Shih
*/
private static final class TabInfo {
private final Class<?> clss;
private final Bundle bundle;
TabInfo(Class<?> clss, Bundle bundle) {
this.clss = clss;
this.bundle = bundle;
}
}
/**
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the {@link TabHost} API.
*
* @author Jimmy Shih
*/
private static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context context;
DummyTabFactory(Context context) {
this.context = context;
}
@Override
public View createTabContent(String tag) {
View view = new View(context);
view.setMinimumWidth(0);
view.setMinimumHeight(0);
return view;
}
}
}
@@ -31,20 +31,21 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub; import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint.WaypointType; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.fragments.ChartDistanceFragment; import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
import de.dennisguse.opentracks.fragments.ChartTimeFragment; import de.dennisguse.opentracks.fragments.ChartTimeFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
@@ -86,7 +87,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
private TabHost tabHost; private ViewPager pager;
private TrackController trackController; private TrackController trackController;
// From intent // From intent
@@ -106,8 +107,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
trackController.update(isRecording(), recordingTrackPaused); trackController.update(isRecording(), recordingTrackPaused);
if (hasPhoto && photoUri != null) { if (hasPhoto && photoUri != null) {
hasPhoto = false; hasPhoto = false;
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString()); long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, null, null, null, photoUri.toString());
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, waypointCreationRequest);
if (markerId != -1L) { if (markerId != -1L) {
//TODO: Make configurable. //TODO: Make configurable.
FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri); FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri);
@@ -117,10 +117,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
}); });
} }
}; };
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner // Note that sharedPreferenceChangeListener cannot be an anonymous inner class. Anonymous inner class will get garbage collected.
* class. Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
@@ -204,28 +202,46 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
trackDataHub = TrackDataHub.newInstance(this); trackDataHub = TrackDataHub.newInstance(this);
tabHost = findViewById(R.id.tackdetail_tabhost); FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
tabHost.setup(); @Override
public int getCount() {
ViewPager viewPager = findViewById(R.id.pager); return 3;
TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager);
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
TabSpec chartTimeTabSpec = tabHost.newTabSpec(ChartTimeFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_time));
tabsAdapter.addTab(chartTimeTabSpec, ChartTimeFragment.class, null);
TabSpec chartDistanceTabSpec = tabHost.newTabSpec(ChartDistanceFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_distance));
tabsAdapter.addTab(chartDistanceTabSpec, ChartDistanceFragment.class, null);
if (savedInstanceState != null) {
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
} }
// Set the background after all three tabs are added @NonNull
for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) { @Override
tabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.tab_indicator); public Fragment getItem(int position) {
switch (position) {
case 0:
return new StatsFragment();
case 1:
return new ChartTimeFragment();
case 2:
return new ChartDistanceFragment();
}
return null;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.track_detail_stats_tab);
case 1:
return getString(R.string.settings_chart_by_time);
case 2:
return getString(R.string.settings_chart_by_distance);
}
return "Unknown Tab";
}
};
pager = findViewById(R.id.track_detail_activity_view_pager);
pager.setAdapter(adapter);
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
tabs.setupWithViewPager(pager);
if (savedInstanceState != null) {
pager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
} }
trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener); trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener);
@@ -293,14 +309,15 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(); trackRecordingServiceConnection.unbind(this);
trackDataHub.stop(); trackDataHub.stop();
} }
@Override @Override
protected void onSaveInstanceState(@NonNull Bundle outState) { protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag()); outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
if (photoUri != null) { if (photoUri != null) {
outState.putParcelable(PHOTO_URI_KEY, photoUri); outState.putParcelable(PHOTO_URI_KEY, photoUri);
} }
@@ -31,7 +31,7 @@ import android.widget.Spinner;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -175,7 +175,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
trackRecordingServiceConnection.unbind(); trackRecordingServiceConnection.unbind(this);
} }
@Override @Override
@@ -51,7 +51,7 @@ import androidx.loader.content.Loader;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.ITrackRecordingService; import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -141,7 +141,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) { if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this); recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) { if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
trackRecordingServiceConnection.startAndBind(); trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
} }
} }
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) { if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
@@ -164,7 +164,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Menu items // Menu items
private MenuItem searchMenuItem; private MenuItem searchMenuItem;
private MenuItem startGpsMenuItem; private MenuItem startGpsMenuItem;
private MenuItem deleteAllMenuItem; // private MenuItem deleteAllMenuItem;
private final OnClickListener stopListener = new OnClickListener() { private final OnClickListener stopListener = new OnClickListener() {
@Override @Override
@@ -344,7 +344,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
super.onStop(); super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(); trackRecordingServiceConnection.unbind(this);
} }
@Override @Override
@@ -371,7 +371,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController); ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps); startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@@ -407,14 +406,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Invoke trackRecordingService // Invoke trackRecordingService
if (startGps) { if (startGps) {
trackRecordingServiceConnection.startAndBind(); trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run(); bindChangedCallback.run();
} else { } else {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) { if (trackRecordingService != null) {
trackRecordingService.stopGps(); trackRecordingService.stopGps();
} }
trackRecordingServiceConnection.unbindAndStop(); trackRecordingServiceConnection.unbindAndStop(this);
} }
// Update menu after starting or stopping gps // Update menu after starting or stopping gps
@@ -425,9 +424,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
intent = IntentUtils.newIntent(this, MarkerListActivity.class); intent = IntentUtils.newIntent(this, MarkerListActivity.class);
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.track_list_delete_all:
deleteTracks(new long[]{-1L});
return true;
case R.id.track_list_settings: case R.id.track_list_settings:
intent = IntentUtils.newIntent(this, SettingsActivity.class); intent = IntentUtils.newIntent(this, SettingsActivity.class);
startActivity(intent); startActivity(intent);
@@ -495,9 +491,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp); startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
} }
} }
if (deleteAllMenuItem != null) {
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
}
} }
/** /**
@@ -505,7 +498,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
*/ */
private void startRecording() { private void startRecording() {
startNewRecording = true; startNewRecording = true;
trackRecordingServiceConnection.startAndBind(); trackRecordingServiceConnection.startAndBind(this);
/* /*
* If the binding has happened, then invoke the callback to start a new recording. * If the binding has happened, then invoke the callback to start a new recording.
@@ -23,8 +23,6 @@ import android.graphics.Paint.Align;
import android.graphics.Paint.Style; import android.graphics.Paint.Style;
import android.graphics.Path; import android.graphics.Path;
import androidx.annotation.VisibleForTesting;
import java.text.NumberFormat; import java.text.NumberFormat;
import de.dennisguse.opentracks.stats.ExtremityMonitor; import de.dennisguse.opentracks.stats.ExtremityMonitor;
@@ -203,7 +201,6 @@ class ChartValueSeries {
/** /**
* Gets the interval value. * Gets the interval value.
*/ */
@VisibleForTesting
int getInterval() { int getInterval() {
return interval; return interval;
} }
@@ -218,7 +215,6 @@ class ChartValueSeries {
/** /**
* Gets the max marker value. * Gets the max marker value.
*/ */
@VisibleForTesting
int getMaxMarkerValue() { int getMaxMarkerValue() {
return maxMarkerValue; return maxMarkerValue;
} }
@@ -37,10 +37,11 @@ import androidx.core.view.GestureDetectorCompat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.MarkerDetailActivity; import de.dennisguse.opentracks.MarkerDetailActivity;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.ExtremityMonitor; import de.dennisguse.opentracks.stats.ExtremityMonitor;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.MarkerUtils; import de.dennisguse.opentracks.util.MarkerUtils;
@@ -56,10 +57,10 @@ import de.dennisguse.opentracks.util.UnitConversions;
*/ */
public class ChartView extends View { public class ChartView extends View {
public static final float MEDIUM_TEXT_SIZE = 18f; static final float MEDIUM_TEXT_SIZE = 18f;
public static final float SMALL_TEXT_SIZE = 12f; static final float SMALL_TEXT_SIZE = 12f;
public static final int Y_AXIS_INTERVALS = 5; static final int Y_AXIS_INTERVALS = 5;
public static final int NUM_SERIES = 6; public static final int NUM_SERIES = 6;
public static final int ELEVATION_SERIES = 0; public static final int ELEVATION_SERIES = 0;
@@ -80,19 +81,24 @@ public class ChartView extends View {
private static final int SPACER = 4; private static final int SPACER = 4;
private static final int Y_AXIS_OFFSET = 16; private static final int Y_AXIS_OFFSET = 16;
//TODO Determine from actual size of the used drawable
private static final float WAYPOINT_X_ANCHOR = 13f / 48f;
static { static {
X_FRACTION_FORMAT.setMaximumFractionDigits(1); X_FRACTION_FORMAT.setMaximumFractionDigits(1);
X_FRACTION_FORMAT.setMinimumFractionDigits(1); X_FRACTION_FORMAT.setMinimumFractionDigits(1);
} }
private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES]; private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES];
private final ArrayList<double[]> chartData = new ArrayList<>(); private final List<double[]> chartData = new ArrayList<>();
private final ArrayList<Waypoint> waypoints = new ArrayList<>(); private final List<Waypoint> waypoints = new ArrayList<>();
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor(); private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
private final Paint axisPaint; private final Paint axisPaint;
private final Paint xAxisMarkerPaint; private final Paint xAxisMarkerPaint;
private final Paint gridPaint; private final Paint gridPaint;
private final Paint markerPaint;
private final Drawable pointer; private final Drawable pointer;
private final Drawable waypointMarker;
private final int markerWidth; private final int markerWidth;
private final int markerHeight; private final int markerHeight;
private final Scroller scroller; private final Scroller scroller;
@@ -146,7 +152,7 @@ public class ChartView extends View {
@Override @Override
public boolean onSingleTapConfirmed(MotionEvent event) { public boolean onSingleTapConfirmed(MotionEvent event) {
// Check if the y event is within markerHeight of the marker center // Check if the y event is within markerHeight of the marker center
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) { if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) {
int minDistance = Integer.MAX_VALUE; int minDistance = Integer.MAX_VALUE;
Waypoint nearestWaypoint = null; Waypoint nearestWaypoint = null;
synchronized (waypoints) { synchronized (waypoints) {
@@ -256,7 +262,7 @@ public class ChartView extends View {
gridPaint.setAntiAlias(false); gridPaint.setAntiAlias(false);
gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0)); gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0));
Paint markerPaint = new Paint(); markerPaint = new Paint();
markerPaint.setStyle(Style.STROKE); markerPaint.setStyle(Style.STROKE);
markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray)); markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray));
markerPaint.setAntiAlias(false); markerPaint.setAntiAlias(false);
@@ -264,7 +270,7 @@ public class ChartView extends View {
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp); pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight()); pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
Drawable waypointMarker = MarkerUtils.getDefaultPhoto(context); waypointMarker = MarkerUtils.getDefaultPhoto(context);
markerWidth = waypointMarker.getIntrinsicWidth(); markerWidth = waypointMarker.getIntrinsicWidth();
markerHeight = waypointMarker.getIntrinsicHeight(); markerHeight = waypointMarker.getIntrinsicHeight();
waypointMarker.setBounds(0, 0, markerWidth, markerHeight); waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
@@ -321,11 +327,10 @@ public class ChartView extends View {
* *
* @param dataPoints an array of data points to be added * @param dataPoints an array of data points to be added
*/ */
public void addDataPoints(ArrayList<double[]> dataPoints) { public void addDataPoints(List<double[]> dataPoints) {
synchronized (chartData) { synchronized (chartData) {
chartData.addAll(dataPoints); chartData.addAll(dataPoints);
for (int i = 0; i < dataPoints.size(); i++) { for (double[] dataPoint : dataPoints) {
double[] dataPoint = dataPoints.get(i);
xExtremityMonitor.update(dataPoint[0]); xExtremityMonitor.update(dataPoint[0]);
for (int j = 0; j < series.length; j++) { for (int j = 0; j < series.length; j++) {
if (!Double.isNaN(dataPoint[j + 1])) { if (!Double.isNaN(dataPoint[j + 1])) {
@@ -490,6 +495,7 @@ public class ChartView extends View {
clipToGraphArea(canvas); clipToGraphArea(canvas);
drawDataSeries(canvas); drawDataSeries(canvas);
drawWaypoints(canvas);
drawGrid(canvas); drawGrid(canvas);
canvas.restore(); canvas.restore();
@@ -530,6 +536,33 @@ public class ChartView extends View {
} }
} }
/**
* Draws the waypoints.
*
* @param canvas the canvas
*/
private void drawWaypoints(Canvas canvas) {
synchronized (waypoints) {
for (int i = 0; i < waypoints.size(); i++) {
final Waypoint waypoint = waypoints.get(i);
if (waypoint.getLocation() == null) {
continue;
}
double xValue = getWaypointXValue(waypoint);
if (xValue > maxX) {
continue;
}
canvas.save();
float x = getX(getWaypointXValue(waypoint));
canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
canvas.translate(x - (markerWidth * WAYPOINT_X_ANCHOR), topBorder + spacer);
waypointMarker.draw(canvas);
canvas.restore();
}
}
}
/** /**
* Draws the grid. * Draws the grid.
* *
@@ -537,9 +570,9 @@ public class ChartView extends View {
*/ */
private void drawGrid(Canvas canvas) { private void drawGrid(Canvas canvas) {
// X axis grid // X axis grid
ArrayList<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval()); List<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval());
for (int i = 0; i < xAxisMarkerPositions.size(); i++) { for (double position : xAxisMarkerPositions) {
int x = getX(xAxisMarkerPositions.get(i)); int x = getX(position);
canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint); canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint);
} }
// Y axis grid // Y axis grid
@@ -611,10 +644,10 @@ public class ChartView extends View {
canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint); canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint);
double interval = getXAxisInterval(); double interval = getXAxisInterval();
ArrayList<Double> markerPositions = getXAxisMarkerPositions(interval);
NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT; NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT;
for (int i = 0; i < markerPositions.size(); i++) {
drawXAxisMarker(canvas, markerPositions.get(i), numberFormat, spacer + yOffset); for (double markerPosition : getXAxisMarkerPositions(interval)) {
drawXAxisMarker(canvas, markerPosition, numberFormat, spacer + yOffset);
} }
} }
@@ -664,8 +697,8 @@ public class ChartView extends View {
/** /**
* Gets the x axis marker positions. * Gets the x axis marker positions.
*/ */
private ArrayList<Double> getXAxisMarkerPositions(double interval) { private List<Double> getXAxisMarkerPositions(double interval) {
ArrayList<Double> markers = new ArrayList<>(); List<Double> markers = new ArrayList<>();
markers.add(0d); markers.add(0d);
for (int i = 1; i * interval < maxX; i++) { for (int i = 1; i * interval < maxX; i++) {
markers.add(i * interval); markers.add(i * interval);
@@ -775,8 +808,7 @@ public class ChartView extends View {
private void drawPaths() { private void drawPaths() {
boolean[] hasMoved = new boolean[series.length]; boolean[] hasMoved = new boolean[series.length];
for (int i = 0; i < chartData.size(); i++) { for (double[] dataPoint : chartData) {
double[] dataPoint = chartData.get(i);
for (int j = 0; j < series.length; j++) { for (int j = 0; j < series.length; j++) {
double value = dataPoint[j + 1]; double value = dataPoint[j + 1];
if (Double.isNaN(value)) { if (Double.isNaN(value)) {
@@ -35,7 +35,11 @@ import java.util.NoSuchElementException;
import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.android.ContentResolverWrapper; import de.dennisguse.opentracks.android.ContentResolverWrapper;
import de.dennisguse.opentracks.android.IContentResolver; import de.dennisguse.opentracks.android.IContentResolver;
import de.dennisguse.opentracks.content.Waypoint.WaypointType; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
@@ -51,11 +55,6 @@ public class ContentProviderUtils {
private static final String TAG = ContentProviderUtils.class.getSimpleName(); private static final String TAG = ContentProviderUtils.class.getSimpleName();
private static final int MAX_LATITUDE = 90000000; private static final int MAX_LATITUDE = 90000000;
/**
* Maximum number of waypoints that will be loaded at one time.
*/
public static int MAX_LOADED_WAYPOINTS_POINTS = 10000;
/** /**
* 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.
*/ */
@@ -64,7 +63,12 @@ public class ContentProviderUtils {
/** /**
* The base URI for the app's content provider. * The base URI for the app's content provider.
*/ */
static String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE; public static String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
/**
* Maximum number of waypoints that will be loaded at one time.
*/
public static int MAX_LOADED_WAYPOINTS_POINTS = 10000;
private final IContentResolver contentResolver; private final IContentResolver contentResolver;
private int defaultCursorBatchSize = 2000; private int defaultCursorBatchSize = 2000;
@@ -443,9 +447,6 @@ public class ContentProviderUtils {
if (!cursor.isNull(trackIdIndex)) { if (!cursor.isNull(trackIdIndex)) {
waypoint.setTrackId(cursor.getLong(trackIdIndex)); waypoint.setTrackId(cursor.getLong(trackIdIndex));
} }
if (!cursor.isNull(typeIndex)) {
waypoint.setType(WaypointType.values()[cursor.getInt(typeIndex)]);
}
if (!cursor.isNull(lengthIndex)) { if (!cursor.isNull(lengthIndex)) {
waypoint.setLength(cursor.getFloat(lengthIndex)); waypoint.setLength(cursor.getFloat(lengthIndex));
} }
@@ -539,25 +540,10 @@ public class ContentProviderUtils {
* The generator is used to update the next statistics waypoint. * The generator is used to update the next statistics waypoint.
* *
* @param waypointId the waypoint id * @param waypointId the waypoint id
* @param descriptionGenerator the description generator. Can be null for waypoint marker
*/ */
public void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator) { public void deleteWaypoint(Context context, long waypointId) {
final Waypoint waypoint = getWaypoint(waypointId); final Waypoint waypoint = getWaypoint(waypointId);
if (waypoint != null && waypoint.getType() == WaypointType.STATISTICS
&& descriptionGenerator != null) {
final Waypoint nextWaypoint = getNextStatisticsWaypointAfter(waypoint);
if (nextWaypoint == null) {
Log.d(TAG, "Unable to find the next statistics marker after deleting one.");
} else {
nextWaypoint.getTripStatistics().merge(waypoint.getTripStatistics());
nextWaypoint.setDescription(
descriptionGenerator.generateWaypointDescription(nextWaypoint.getTripStatistics()));
if (!updateWaypoint(nextWaypoint)) {
Log.e(TAG, "Unable to update the next statistics marker after deleting one.");
}
}
}
if (waypoint != null && waypoint.hasPhoto()) { if (waypoint != null && waypoint.hasPhoto()) {
Uri uri = waypoint.getPhotoURI(); Uri uri = waypoint.getPhotoURI();
File file = new File(uri.getPath()); File file = new File(uri.getPath());
@@ -573,37 +559,17 @@ public class ContentProviderUtils {
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)}); contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
} }
/**
* Gets the first waypoint id for a track.
* The first waypoint is special as it contains the stats for the track.
* Returns -1L if it doesn't exist.
*
* @param trackId the track id
*/
public long getFirstWaypointId(long trackId) {
if (trackId < 0) {
return -1L;
}
try (Cursor cursor = getWaypointCursor(new String[]{WaypointsColumns._ID}, WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)}, WaypointsColumns._ID, 1)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(WaypointsColumns._ID));
}
}
return -1L;
}
/** /**
* Gets the last waypoint for a type. Returns null if it doesn't exist. * Gets the last waypoint for a type. Returns null if it doesn't exist.
* *
* @param trackId the track id * @param trackId the track id
* @param waypointType the waypoint type
*/ */
public Waypoint getLastWaypoint(long trackId, WaypointType waypointType) { public Waypoint getLastWaypoint(long trackId) {
if (trackId < 0) { if (trackId < 0) {
return null; return null;
} }
String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId), Integer.toString(waypointType.ordinal())}; String[] selectionArgs = new String[]{Long.toString(trackId)};
try (Cursor cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1)) { try (Cursor cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
@@ -618,20 +584,17 @@ public class ContentProviderUtils {
* Returns -1 if not able to get the next waypoint number. * Returns -1 if not able to get the next waypoint number.
* *
* @param trackId the track id * @param trackId the track id
* @param waypointType the waypoint type
*/ */
public int getNextWaypointNumber(long trackId, WaypointType waypointType) { public int getNextWaypointNumber(long trackId) {
if (trackId < 0) { if (trackId < 0) {
return -1; return -1;
} }
String[] projection = {WaypointsColumns._ID}; String[] projection = {WaypointsColumns._ID};
String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId), Integer.toString(waypointType.ordinal())}; String[] selectionArgs = new String[]{Long.toString(trackId)};
try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) { try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) {
if (cursor != null) { if (cursor != null) {
int count = cursor.getCount(); return cursor.getCount();
// For statistics markers, the first marker is for the track statistics, so return the count as the next user visible number.
return waypointType == WaypointType.STATISTICS ? count : count + 1;
} }
} }
return -1; return -1;
@@ -695,6 +658,19 @@ public class ContentProviderUtils {
return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints); return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints);
} }
@VisibleForTesting
public List<Waypoint> getWaypoints(long trackId) {
ArrayList<Waypoint> waypoints = new ArrayList<>();
try (Cursor cursor = getWaypointCursor(trackId, -1L, -1)) {
if (cursor.moveToFirst()) {
do {
waypoints.add(createWaypoint(cursor));
} while (cursor.moveToNext());
}
}
return waypoints;
}
/** /**
* Gets the number of waypoints for a track. * Gets the number of waypoints for a track.
* *
@@ -713,8 +689,7 @@ public class ContentProviderUtils {
cursor.moveToFirst(); cursor.moveToFirst();
int count = cursor.getInt(0); int count = cursor.getInt(0);
cursor.close(); cursor.close();
// not count the first waypoint return count;
return count > 0 ? count - 1 : 0;
} }
/** /**
@@ -751,7 +726,6 @@ public class ContentProviderUtils {
values.put(WaypointsColumns.CATEGORY, waypoint.getCategory()); values.put(WaypointsColumns.CATEGORY, waypoint.getCategory());
values.put(WaypointsColumns.ICON, waypoint.getIcon()); values.put(WaypointsColumns.ICON, waypoint.getIcon());
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId()); values.put(WaypointsColumns.TRACKID, waypoint.getTrackId());
values.put(WaypointsColumns.TYPE, waypoint.getType().ordinal());
values.put(WaypointsColumns.LENGTH, waypoint.getLength()); values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration()); values.put(WaypointsColumns.DURATION, waypoint.getDuration());
values.put(WaypointsColumns.STARTID, waypoint.getStartId()); values.put(WaypointsColumns.STARTID, waypoint.getStartId());
@@ -796,18 +770,6 @@ public class ContentProviderUtils {
return values; return values;
} }
private Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint) {
String selection = WaypointsColumns._ID + ">? AND " + WaypointsColumns.TRACKID + "=? AND "
+ WaypointsColumns.TYPE + "=" + WaypointType.STATISTICS.ordinal();
String[] selectionArgs = new String[]{Long.toString(waypoint.getId()), Long.toString(waypoint.getTrackId())};
try (Cursor cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, 1)) {
if (cursor != null && cursor.moveToFirst()) {
return createWaypoint(cursor);
}
}
return null;
}
/** /**
* Gets a waypoint cursor. * Gets a waypoint cursor.
* *
@@ -925,23 +887,6 @@ public class ContentProviderUtils {
return -1L; return -1L;
} }
/**
* Gets the first valid location for a track.
* Returns null if it doesn't exist.
*
* @param trackId the track id
*/
public Location getFirstValidTrackPoint(long trackId) {
if (trackId < 0) {
return null;
}
String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID + ") from "
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND "
+ TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
String[] selectionArgs = new String[]{Long.toString(trackId)};
return findTrackPointBy(selection, selectionArgs);
}
/** /**
* Gets the last valid location for a track. * Gets the last valid location for a track.
* Returns null if it doesn't exist. * Returns null if it doesn't exist.
@@ -1117,9 +1062,11 @@ public class ContentProviderUtils {
values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); values.put(TrackPointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6));
// Hack for Samsung phones that don't properly populate the time field
long time = location.getTime(); long time = location.getTime();
// TODO: Check if this is really necessary!
// Hack for Samsung phones that don't properly populate the time field
if (time == 0) { if (time == 0) {
Log.w(TAG, "location has no timestamp; setting current time");
time = System.currentTimeMillis(); time = System.currentTimeMillis();
} }
values.put(TrackPointsColumns.TIME, time); values.put(TrackPointsColumns.TIME, time);
@@ -34,6 +34,10 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
/** /**
* A {@link ContentProvider} that handles access to track points, tracks, and waypoints tables. * A {@link ContentProvider} that handles access to track points, tracks, and waypoints tables.
* *
@@ -25,6 +25,10 @@ import android.util.Log;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
/** /**
* Creates observers/listeners and manages their registration with {@link DataSource}. * Creates observers/listeners and manages their registration with {@link DataSource}.
* The observers/listeners calls {@link DataSourceListener} when data changes. * The observers/listeners calls {@link DataSourceListener} when data changes.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010 Google Inc. * Copyright 2012 Google Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -13,16 +13,37 @@
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package de.dennisguse.opentracks.content; package de.dennisguse.opentracks.content;
import android.content.Context;
import android.util.Pair;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/** /**
* An interface for an object that can generate descriptions of track and waypoint. * Generates descriptions for tracks and waypoints.
* *
* @author Sandor Dornbush * @author Jimmy Shih
*/ */
public interface DescriptionGenerator { public class DescriptionGenerator {
private static final String HTML_LINE_BREAK = "<br>";
private static final String HTML_PARAGRAPH_SEPARATOR = "<p>";
private static final String TEXT_LINE_BREAK = "\n";
private static final String TEXT_PARAGRAPH_SEPARATOR = "\n\n";
private Context context;
public DescriptionGenerator(Context context) {
this.context = context;
}
/** /**
* Generates a track description. * Generates a track description.
@@ -30,12 +51,205 @@ public interface DescriptionGenerator {
* @param track the track * @param track the track
* @param html true to output html, false to output plain text * @param html true to output html, false to output plain text
*/ */
String generateTrackDescription(Track track, 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='" + context.getString(R.string.app_web_url) + "'>" + context.getString(R.string.app_name) + "</a>"
: context.getString(R.string.app_name);
builder.append(creator);
builder.append(paragraphSeparator);
writeString(track.getName(), builder, R.string.generic_name_line, lineBreak);
writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak);
writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak);
builder.append(generateTripStatisticsDescription(track.getTripStatistics(), html));
return builder.toString();
}
/** /**
* Generate a waypoint description from a trip statistics. * Generate a waypoint description from a trip statistics.
* *
* @param tripStatistics the trip statistics * @param tripStatistics the trip statistics
*/ */
String generateWaypointDescription(TripStatistics tripStatistics); public String generateWaypointDescription(TripStatistics tripStatistics) {
return generateTripStatisticsDescription(tripStatistics, false);
}
/**
* Writes a string to a string builder.
*
* @param text the string
* @param builder the string builder
* @param resId the resource id containing one string placeholder
* @param lineBreak the line break
*/
private void writeString(String text, StringBuilder builder, int resId, String lineBreak) {
if (text == null || text.length() == 0) {
text = context.getString(R.string.value_unknown);
}
builder.append(context.getString(resId, text));
builder.append(lineBreak);
}
/**
* Generates a description for a {@link TripStatistics}.
*
* @param stats the trip statistics
* @param html true to use "<br>" for line break instead of "\n"
*/
private String generateTripStatisticsDescription(TripStatistics stats, boolean html) {
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
StringBuilder builder = new StringBuilder();
// Total distance
writeDistance(stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
// Total time
writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak);
// Moving time
writeTime(stats.getMovingTime(), builder, R.string.description_moving_time, lineBreak);
// Average speed
writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak);
// Average moving speed
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, lineBreak);
// Max speed
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
// Average pace
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
// Average moving pace
writePace(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_pace_in_minute, lineBreak);
// Fastest pace
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
// Max elevation
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
// Min elevation
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
// Elevation gain
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
// Max grade
writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak);
// Min grade
writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak);
// Recorded time
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
builder.append(lineBreak);
return builder.toString();
}
/**
* Writes distance.
*
* @param distance distance in meters
* @param builder StringBuilder to append distance
* @param resId resource id of distance string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) {
double distanceInKm = distance * UnitConversions.M_TO_KM;
double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, distanceInKm, distanceInMi));
builder.append(lineBreak);
}
/**
* Writes time.
*
* @param time time in milliseconds.
* @param builder StringBuilder to append time
* @param resId resource id of time string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeTime(long time, StringBuilder builder, int resId, String lineBreak) {
builder.append(context.getString(resId, StringUtils.formatElapsedTime(time)));
builder.append(lineBreak);
}
/**
* Writes speed.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append speed
* @param resId resource id of speed string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
builder.append(lineBreak);
}
/**
* Writes pace.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append pace
* @param resId resource id of pace string
* @param lineBreak line break string
*/
@VisibleForTesting
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
builder.append(context.getString(resId, formattedPaceMetrics, formattedPaceImperial));
builder.append(lineBreak);
}
/**
* Writes elevation.
*
* @param elevation elevation in meters
* @param builder StringBuilder to append elevation
* @param resId resource id of elevation string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeElevation(double elevation, StringBuilder builder, int resId, String lineBreak) {
long elevationInM = Math.round(elevation);
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
builder.append(context.getString(resId, elevationInM, elevationInFt));
builder.append(lineBreak);
}
/**
* Writes grade.
*
* @param grade grade in fraction
* @param builder StringBuilder to append grade
* @param resId resource id grade string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) {
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L : Math.round(grade * 100);
builder.append(context.getString(resId, gradeInPercent));
builder.append(lineBreak);
}
} }
@@ -1,243 +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.util.Pair;
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;
/**
* @author Jimmy Shih
*/
public class DescriptionGeneratorImpl implements DescriptionGenerator {
private static final String HTML_LINE_BREAK = "<br>";
private static final String HTML_PARAGRAPH_SEPARATOR = "<p>";
private static final String TEXT_LINE_BREAK = "\n";
private static final String TEXT_PARAGRAPH_SEPARATOR = "\n\n";
private Context context;
public DescriptionGeneratorImpl(Context context) {
this.context = context;
}
@Override
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='" + context.getString(R.string.app_web_url) + "'>" + context.getString(R.string.app_name) + "</a>"
: context.getString(R.string.app_name);
builder.append(creator);
builder.append(paragraphSeparator);
writeString(track.getName(), builder, R.string.generic_name_line, lineBreak);
writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak);
writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak);
builder.append(generateTripStatisticsDescription(track.getTripStatistics(), html));
return builder.toString();
}
@Override
public String generateWaypointDescription(TripStatistics tripStatistics) {
return generateTripStatisticsDescription(tripStatistics, false);
}
/**
* Writes a string to a string builder.
*
* @param text the string
* @param builder the string builder
* @param resId the resource id containing one string placeholder
* @param lineBreak the line break
*/
private void writeString(String text, StringBuilder builder, int resId, String lineBreak) {
if (text == null || text.length() == 0) {
text = context.getString(R.string.value_unknown);
}
builder.append(context.getString(resId, text));
builder.append(lineBreak);
}
/**
* Generates a description for a {@link TripStatistics}.
*
* @param stats the trip statistics
* @param html true to use "<br>" for line break instead of "\n"
*/
private String generateTripStatisticsDescription(TripStatistics stats, boolean html) {
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
StringBuilder builder = new StringBuilder();
// Total distance
writeDistance(stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak);
// Total time
writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak);
// Moving time
writeTime(stats.getMovingTime(), builder, R.string.description_moving_time, lineBreak);
// Average speed
writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak);
// Average moving speed
writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, lineBreak);
// Max speed
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
// Average pace
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
// Average moving pace
writePace(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_pace_in_minute, lineBreak);
// Fastest pace
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
// Max elevation
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
// Min elevation
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
// Elevation gain
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
// Max grade
writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak);
// Min grade
writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak);
// Recorded time
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
builder.append(lineBreak);
return builder.toString();
}
/**
* Writes distance.
*
* @param distance distance in meters
* @param builder StringBuilder to append distance
* @param resId resource id of distance string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) {
double distanceInKm = distance * UnitConversions.M_TO_KM;
double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, distanceInKm, distanceInMi));
builder.append(lineBreak);
}
/**
* Writes time.
*
* @param time time in milliseconds.
* @param builder StringBuilder to append time
* @param resId resource id of time string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeTime(long time, StringBuilder builder, int resId, String lineBreak) {
builder.append(context.getString(resId, StringUtils.formatElapsedTime(time)));
builder.append(lineBreak);
}
/**
* Writes speed.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append speed
* @param resId resource id of speed string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
builder.append(lineBreak);
}
/**
* Writes pace.
*
* @param speed speed in meters per second
* @param builder StringBuilder to append pace
* @param resId resource id of pace string
* @param lineBreak line break string
*/
@VisibleForTesting
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
builder.append(context.getString(resId, formattedPaceMetrics, formattedPaceImperial));
builder.append(lineBreak);
}
/**
* Writes elevation.
*
* @param elevation elevation in meters
* @param builder StringBuilder to append elevation
* @param resId resource id of elevation string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeElevation(double elevation, StringBuilder builder, int resId, String lineBreak) {
long elevationInM = Math.round(elevation);
long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT);
builder.append(context.getString(resId, elevationInM, elevationInFt));
builder.append(lineBreak);
}
/**
* Writes grade.
*
* @param grade grade in fraction
* @param builder StringBuilder to append grade
* @param resId resource id grade string
* @param lineBreak line break string
*/
@VisibleForTesting
void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) {
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L : Math.round(grade * 100);
builder.append(context.getString(resId, gradeInPercent));
builder.append(lineBreak);
}
}
@@ -28,6 +28,10 @@ import java.util.Locale;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
@@ -21,6 +21,8 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import de.dennisguse.opentracks.android.IContentResolver; import de.dennisguse.opentracks.android.IContentResolver;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter; import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
@@ -30,6 +30,8 @@ import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -40,17 +42,24 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
*/ */
public class TrackDataHub implements DataSourceListener { public class TrackDataHub implements DataSourceListener {
public synchronized static TrackDataHub newInstance(Context context) {
return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
}
/** /**
* Target number of track points displayed by the map overlay. * Target number of track points displayed by the map overlay.
* We may display more than this number of points. * We may display more than this number of points.
*/ */
@Deprecated
private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000; private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
/** /**
* Maximum number of waypoints to displayed. * Maximum number of waypoints to displayed.
*/ */
@VisibleForTesting @VisibleForTesting
@Deprecated
private static final int MAX_DISPLAYED_WAYPOINTS = 128; private static final int MAX_DISPLAYED_WAYPOINTS = 128;
private static final String TAG = TrackDataHub.class.getSimpleName(); private static final String TAG = TrackDataHub.class.getSimpleName();
private final Context context; private final Context context;
@@ -95,10 +104,6 @@ public class TrackDataHub implements DataSourceListener {
resetSamplingState(); resetSamplingState();
} }
public synchronized static TrackDataHub newInstance(Context context) {
return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
}
public void start() { public void start() {
if (started) { if (started) {
Log.i(TAG, "TrackDataHub already started, ignoring start."); Log.i(TAG, "TrackDataHub already started, ignoring start.");
@@ -396,8 +401,7 @@ public class TrackDataHub implements DataSourceListener {
trackDataListener.clearWaypoints(); trackDataListener.clearWaypoints();
} }
try (Cursor cursor = contentProviderUtils.getWaypointCursor( try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
do { do {
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
@@ -18,6 +18,9 @@ package de.dennisguse.opentracks.content;
import android.location.Location; import android.location.Location;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
/** /**
* Listener for track data changes. * Listener for track data changes.
* *
@@ -1,85 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
/**
* A request for the service to create a waypoint at the current location.
*
* @author Sandor Dornbush
*/
public class WaypointCreationRequest {
public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest(WaypointType.WAYPOINT, false);
public final static WaypointCreationRequest DEFAULT_STATISTICS = new WaypointCreationRequest(WaypointType.STATISTICS, false);
public final static WaypointCreationRequest DEFAULT_START_TRACK = new WaypointCreationRequest(WaypointType.STATISTICS, true);
private WaypointType type;
// true if this marker contains the track statistics
private boolean isTrackStatistics;
private String name;
private String category;
private String description;
private String iconUrl;
private String photoUrl;
private WaypointCreationRequest(WaypointType type, boolean isTrackStatistics) {
this(type, isTrackStatistics, null, null, null, null, null);
}
public WaypointCreationRequest(WaypointType type, boolean isTrackStatistics, String name, String category, String description, String iconUrl, String photoUrl) {
this.type = type;
this.isTrackStatistics = isTrackStatistics;
this.name = name;
this.category = category;
this.description = description;
this.iconUrl = iconUrl;
this.photoUrl = photoUrl;
}
public WaypointType getType() {
return type;
}
public boolean isTrackStatistics() {
return isTrackStatistics;
}
public String getName() {
return name;
}
public String getCategory() {
return category;
}
public String getDescription() {
return description;
}
public String getIconUrl() {
return iconUrl;
}
public String getPhotoUrl() {
return photoUrl;
}
}
@@ -14,7 +14,7 @@
* the License. * the License.
*/ */
package de.dennisguse.opentracks.content; package de.dennisguse.opentracks.content.data;
import android.location.Location; import android.location.Location;
@@ -14,11 +14,13 @@
* the License. * the License.
*/ */
package de.dennisguse.opentracks.content; package de.dennisguse.opentracks.content.data;
import android.net.Uri; import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import de.dennisguse.opentracks.content.ContentProviderUtils;
/** /**
* Constants for the track points table. * Constants for the track points table.
* *
@@ -14,11 +14,13 @@
* the License. * the License.
*/ */
package de.dennisguse.opentracks.content; package de.dennisguse.opentracks.content.data;
import android.net.Uri; import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import de.dennisguse.opentracks.content.ContentProviderUtils;
/** /**
* Constants for the tracks table. * Constants for the tracks table.
* *
@@ -14,7 +14,7 @@
* the License. * the License.
*/ */
package de.dennisguse.opentracks.content; package de.dennisguse.opentracks.content.data;
import android.location.Location; import android.location.Location;
import android.net.Uri; import android.net.Uri;
@@ -35,7 +35,6 @@ public final class Waypoint {
private String category = ""; private String category = "";
private String icon = ""; private String icon = "";
private long trackId = -1L; private long trackId = -1L;
private WaypointType type = WaypointType.WAYPOINT;
private double length = 0.0; private double length = 0.0;
private long duration = 0; private long duration = 0;
private long startId = -1L; private long startId = -1L;
@@ -47,13 +46,12 @@ public final class Waypoint {
public Waypoint() { public Waypoint() {
} }
public Waypoint(String name, String description, String category, String icon, long trackId, WaypointType type, double length, long duration, long startId, long stopId, Location location, TripStatistics tripStatistics, String photoUrl) { public Waypoint(String name, String description, String category, String icon, long trackId, double length, long duration, long startId, long stopId, Location location, TripStatistics tripStatistics, String photoUrl) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.category = category; this.category = category;
this.icon = icon; this.icon = icon;
this.trackId = trackId; this.trackId = trackId;
this.type = type;
this.length = length; this.length = length;
this.duration = duration; this.duration = duration;
this.startId = startId; this.startId = startId;
@@ -111,14 +109,6 @@ public final class Waypoint {
this.trackId = trackId; this.trackId = trackId;
} }
public WaypointType getType() {
return type;
}
public void setType(WaypointType type) {
this.type = type;
}
public double getLength() { public double getLength() {
return length; return length;
} }
@@ -186,8 +176,4 @@ public final class Waypoint {
public boolean hasPhoto() { public boolean hasPhoto() {
return photoUrl != null && !"".equals(photoUrl); return photoUrl != null && !"".equals(photoUrl);
} }
public enum WaypointType {
WAYPOINT, STATISTICS
}
} }
@@ -14,11 +14,13 @@
* the License. * the License.
*/ */
package de.dennisguse.opentracks.content; package de.dennisguse.opentracks.content.data;
import android.net.Uri; import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import de.dennisguse.opentracks.content.ContentProviderUtils;
/** /**
* Constants for waypoints table. * Constants for waypoints table.
* *
@@ -38,6 +40,7 @@ public interface WaypointsColumns extends BaseColumns {
String CATEGORY = "category"; // waypoint category String CATEGORY = "category"; // waypoint category
String ICON = "icon"; // waypoint icon String ICON = "icon"; // waypoint icon
String TRACKID = "trackid"; // track id String TRACKID = "trackid"; // track id
@Deprecated //Unused
String TYPE = "type"; // type String TYPE = "type"; // type
String LENGTH = "length"; // length of the track (without smoothing) String LENGTH = "length"; // length of the track (without smoothing)
String DURATION = "duration"; // total duration of the track (not from last waypoint) String DURATION = "duration"; // total duration of the track (not from last waypoint)
@@ -5,8 +5,8 @@ public final class SensorDataSet {
public static final float DATA_UNAVAILABLE = Float.NaN; public static final float DATA_UNAVAILABLE = Float.NaN;
//TODO It might be necessary to consider: if sensor was connected as well. //TODO It might be necessary to consider: if sensor was connected as well.
public String sensorName; private String sensorName;
public String sensorAddress; private String sensorAddress;
private float heartRate; private float heartRate;
private float cadence; private float cadence;
private float power; private float power;
@@ -31,16 +31,17 @@ import androidx.fragment.app.FragmentActivity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity; import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.chart.ChartView; import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub; import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener; import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.TrackDataType; import de.dennisguse.opentracks.content.TrackDataType;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.stats.TripStatisticsUpdater; import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
@@ -58,7 +59,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
private static final String STATE_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance"; private static final String STATE_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
private final ArrayList<double[]> pendingPoints = new ArrayList<>(); private final List<double[]> pendingPoints = new ArrayList<>();
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
@@ -103,7 +104,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext()); recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
// Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume. // Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
chartView = new ChartView(getActivity(), chartByDistance); chartView = new ChartView(getContext(), chartByDistance);
} }
@Override @Override
@@ -257,9 +258,8 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
} }
reportSpeed = speed; reportSpeed = speed;
chartView.setReportSpeed(reportSpeed); chartView.setReportSpeed(reportSpeed);
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity()); setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed); setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -296,26 +296,14 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
*/ */
private void checkChartSettings() { private void checkChartSettings() {
boolean needUpdate = false; boolean needUpdate = false;
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.shouldChartShowElevation(getActivity()))) {
if (setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed)) {
needUpdate = true; needUpdate = true;
} }
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.shouldChartShowPower(getActivity()))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.shouldChartShowCadence(getActivity()))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.shouldChartShowHeartRate(getActivity()))) {
needUpdate = true;
}
if (needUpdate) { if (needUpdate) {
chartView.postInvalidate(); chartView.postInvalidate();
} }
@@ -28,7 +28,6 @@ import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.util.DialogUtils; import de.dennisguse.opentracks.util.DialogUtils;
/** /**
@@ -83,8 +82,7 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
public void run() { public void run() {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
for (long markerId : markerIds) { for (long markerId : markerIds) {
contentProviderUtils.deleteWaypoint(fragmentActivity, contentProviderUtils.deleteWaypoint(fragmentActivity, markerId);
markerId, new DescriptionGeneratorImpl(fragmentActivity));
} }
caller.onDeleteMarkerDone(); caller.onDeleteMarkerDone();
} }
@@ -41,7 +41,7 @@ import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.MarkerEditActivity; import de.dennisguse.opentracks.MarkerEditActivity;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils; import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.MarkerUtils; import de.dennisguse.opentracks.util.MarkerUtils;
@@ -37,11 +37,11 @@ import java.util.EnumSet;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity; import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub; import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener; import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.TrackDataType; import de.dennisguse.opentracks.content.TrackDataType;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.ITrackRecordingService; import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -68,6 +68,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
private Handler handlerUpdateUI; private Handler handlerUpdateUI;
//TODO Initialize immediately and remove in onDestroy()
private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackRecordingServiceConnection trackRecordingServiceConnection;
private Location lastLocation = null; private Location lastLocation = null;
@@ -237,7 +238,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
trackRecordingServiceConnection.unbind(); if (trackRecordingServiceConnection != null) {
trackRecordingServiceConnection.unbind(getContext());
}
trackRecordingServiceConnection = null; trackRecordingServiceConnection = null;
} }
@@ -569,7 +572,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation(); double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation();
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits); Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
elevationMinValue.setText(parts.first); elevationMinValue.setText(parts.first);
elevationMinUnit.setText(parts.second); elevationMinUnit.setText(parts.second);
} }
@@ -578,7 +580,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation(); double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation();
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits); Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
elevationMaxValue.setText(parts.first); elevationMaxValue.setText(parts.first);
elevationMaxUnit.setText(parts.second); elevationMaxUnit.setText(parts.second);
} }
@@ -6,7 +6,7 @@ import java.util.Locale;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter; import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter; import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter; import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
@@ -146,7 +146,7 @@ public enum TrackFileFormat {
} }
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) { public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos); return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
} }
public String getExtension() { public String getExtension() {
@@ -188,7 +188,7 @@ public enum TrackFileFormat {
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) { public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1); TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener); return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
} }
@@ -103,16 +103,8 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
} }
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension()); directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
//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 = new ExportAsyncTask(this, trackFileFormat, directory);
exportAsyncTask.execute(); exportAsyncTask.execute();
// }
} }
@Override @Override
@@ -28,8 +28,8 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -29,8 +29,8 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.LocationFactory; import de.dennisguse.opentracks.content.LocationFactory;
import de.dennisguse.opentracks.content.LocationIterator; import de.dennisguse.opentracks.content.LocationIterator;
import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.LocationUtils;
/** /**
@@ -99,7 +99,6 @@ public class FileTrackExporter implements TrackExporter {
boolean hasWaypoints = false; boolean hasWaypoints = false;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) { try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
// Intentionally skip first waypoint (contains statistics).
for (int i = 0; i < cursor.getCount(); i++) { for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
throw new InterruptedException(); throw new InterruptedException();
@@ -23,8 +23,8 @@ import java.io.PrintWriter;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
/** /**
@@ -119,12 +119,9 @@ public class GpxTrackWriter implements TrackWriter {
if (location.hasAltitude()) { if (location.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>"); printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
} }
printWriter.println( printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
"<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>"); printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>");
printWriter.println("<cmt>" + StringUtils.formatCData(waypoint.getType().name()) + "</cmt>"); printWriter.println("<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
printWriter.println(
"<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
printWriter.println("<type>" + StringUtils.formatCData(waypoint.getCategory()) + "</type>"); printWriter.println("<type>" + StringUtils.formatCData(waypoint.getCategory()) + "</type>");
printWriter.println("</wpt>"); printWriter.println("</wpt>");
} }
@@ -28,11 +28,9 @@ import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGenerator; import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
@@ -43,19 +41,17 @@ import de.dennisguse.opentracks.util.StringUtils;
*/ */
public class KmlTrackWriter implements TrackWriter { public class KmlTrackWriter implements TrackWriter {
private static final String WAYPOINT_STYLE = "waypoint"; public static final String WAYPOINT_STYLE = "waypoint";
private static final String STATISTICS_STYLE = "statistics";
private static final String START_STYLE = "start"; private static final String START_STYLE = "start";
private static final String END_STYLE = "end"; private static final String END_STYLE = "end";
private static final String TRACK_STYLE = "track"; private static final String TRACK_STYLE = "track";
private static final String SCHEMA_ID = "schema"; private static final String SCHEMA_ID = "schema";
private static final String SENSOR_TYPE_CADENCE = "cadence"; public static final String SENSOR_TYPE_CADENCE = "cadence";
private static final String SENSOR_TYPE_HEART_RATE = "heart_rate"; public static final String SENSOR_TYPE_HEART_RATE = "heart_rate";
private static final String SENSOR_TYPE_POWER = "power"; public static final String SENSOR_TYPE_POWER = "power";
private static final String WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.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 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 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 TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
@@ -88,7 +84,7 @@ public class KmlTrackWriter implements TrackWriter {
this.exportTrackDetail = exportTrackDetail; this.exportTrackDetail = exportTrackDetail;
this.exportSensorData = exportSensorData; this.exportSensorData = exportSensorData;
this.exportPhotos = exportPhotos; this.exportPhotos = exportPhotos;
this.descriptionGenerator = new DescriptionGeneratorImpl(context); this.descriptionGenerator = new DescriptionGenerator(context);
this.contentProviderUtils = new ContentProviderUtils(context); this.contentProviderUtils = new ContentProviderUtils(context);
} }
@@ -125,7 +121,6 @@ public class KmlTrackWriter implements TrackWriter {
writeTrackStyle(); writeTrackStyle();
writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1); writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1);
writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1); writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1);
writePlacemarkerStyle(STATISTICS_STYLE, STATISTICS_ICON, 20, 2);
writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2); writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2);
printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">"); printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">");
@@ -167,13 +162,11 @@ public class KmlTrackWriter implements TrackWriter {
@Override @Override
public void writeWaypoint(Waypoint waypoint) { public void writeWaypoint(Waypoint waypoint) {
if (printWriter != null && exportTrackDetail) { if (printWriter != null && exportTrackDetail) {
String styleName = waypoint.getType() == WaypointType.STATISTICS ? STATISTICS_STYLE : WAYPOINT_STYLE;
if (waypoint.hasPhoto() && exportPhotos) { if (waypoint.hasPhoto() && exportPhotos) {
float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation()); float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation());
writePhotoOverlay(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation(), waypoint.getPhotoUrl(), heading); writePhotoOverlay(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), WAYPOINT_STYLE, waypoint.getLocation(), waypoint.getPhotoUrl(), heading);
} else { } else {
writePlacemark(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation()); writePlacemark(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), WAYPOINT_STYLE, waypoint.getLocation());
} }
} }
} }
@@ -205,6 +198,7 @@ public class KmlTrackWriter implements TrackWriter {
if (exportTrackDetail) { if (exportTrackDetail) {
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>"); 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("<icon>" + StringUtils.formatCData(track.getIcon()) + "</icon>");
} }
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>"); printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
@@ -32,8 +32,8 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
/** /**
@@ -19,8 +19,8 @@ import android.location.Location;
import java.io.OutputStream; import java.io.OutputStream;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
/** /**
* Interface for writing tracks to a file. The expected sequence of calls is: * Interface for writing tracks to a file. The expected sequence of calls is:
@@ -39,12 +39,10 @@ import javax.xml.parsers.SAXParserFactory;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.LocationFactory; import de.dennisguse.opentracks.content.LocationFactory;
import de.dennisguse.opentracks.content.LocationIterator; import de.dennisguse.opentracks.content.LocationIterator;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics; import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.stats.TripStatisticsUpdater; import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
@@ -71,12 +69,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
private final int recordingDistanceInterval; private final int recordingDistanceInterval;
private final List<Long> trackIds; private final List<Long> trackIds = new ArrayList<>();
private final List<Waypoint> waypoints; private final List<Waypoint> waypoints = new ArrayList<>();
// The current element content // The current element content
//TODO Should be made private and getter be used by child classes. //TODO Should be made private and getter be used by child classes.
protected String content; protected String content;
protected String icon;
protected String name; protected String name;
protected String description; protected String description;
protected String category; protected String category;
@@ -104,8 +103,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
this.importTrackId = importTrackId; this.importTrackId = importTrackId;
this.contentProviderUtils = contentProviderUtils; this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context); this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
trackIds = new ArrayList<>();
waypoints = new ArrayList<>();
} }
@Override @Override
@@ -177,6 +174,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
return; return;
} }
} }
if (location == null) { if (location == null) {
if (!locationIterator.hasNext()) { if (!locationIterator.hasNext()) {
// No more track points. Ignore the rest of the waypoints. // No more track points. Ignore the rest of the waypoints.
@@ -186,9 +184,11 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval); trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval);
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval); markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
} }
if (waypoint.getLocation().getTime() > location.getTime()) { if (waypoint.getLocation().getTime() > location.getTime()) {
location = null; location = null;
} else if (waypoint.getLocation().getTime() < location.getTime()) { } else if (waypoint.getLocation().getTime() < location.getTime()) {
Log.w(TAG, "Ignoring waypoint: current location was after waypoint.");
waypoint = null; waypoint = null;
} else { } else {
// The waypoint location time matches the track point time // The waypoint location time matches the track point time
@@ -201,27 +201,14 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// Valid location // Valid location
if (location.getLatitude() == waypoint.getLocation().getLatitude() && location.getLongitude() == waypoint.getLocation().getLongitude()) { if (location.getLatitude() == waypoint.getLocation().getLatitude() && location.getLongitude() == waypoint.getLocation().getLongitude()) {
// Get tripStatistics, description, and icon TripStatistics tripStatistics = null;
TripStatistics tripStatistics; String waypointDescription = waypoint.getDescription();
String waypointDescription; String icon = context.getString(R.string.marker_waypoint_icon_url);
String icon;
if (waypoint.getType() == WaypointType.STATISTICS) {
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime());
waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription(tripStatistics);
icon = context.getString(R.string.marker_statistics_icon_url);
} else {
tripStatistics = null;
waypointDescription = waypoint.getDescription();
icon = context.getString(R.string.marker_waypoint_icon_url);
}
// Get length and duration
double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance(); double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance();
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime(); long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
// Insert waypoint // Insert waypoint
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration, -1L, -1L, location, tripStatistics, waypoint.getPhotoUrl()); Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), length, duration, -1L, -1L, location, tripStatistics, waypoint.getPhotoUrl());
contentProviderUtils.insertWaypoint(newWaypoint); contentProviderUtils.insertWaypoint(newWaypoint);
} }
@@ -265,8 +252,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
} }
if (category != null) { if (category != null) {
trackData.track.setCategory(category); trackData.track.setCategory(category);
//TODO remove when GPX and KML support reading this property.
if (icon == null)
trackData.track.setIcon(TrackIconUtils.getIconValue(context, category)); trackData.track.setIcon(TrackIconUtils.getIconValue(context, category));
} }
if (icon != null) {
trackData.track.setIcon(icon);
}
if (trackData.tripStatisticsUpdater == null) { if (trackData.tripStatisticsUpdater == null) {
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime); trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime);
trackData.tripStatisticsUpdater.updateTime(trackData.importTime); trackData.tripStatisticsUpdater.updateTime(trackData.importTime);
@@ -274,7 +266,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics()); trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
trackData.track.setNumberOfPoints(trackData.numberOfLocations); trackData.track.setNumberOfPoints(trackData.numberOfLocations);
contentProviderUtils.updateTrack(trackData.track); contentProviderUtils.updateTrack(trackData.track);
insertFirstWaypoint(trackData.track);
} }
/** /**
@@ -292,10 +283,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
/** /**
* Adds a waypoint. * Adds a waypoint.
*
* @param type the waypoint type
*/ */
protected void addWaypoint(WaypointType type) throws SAXException { protected void addWaypoint() throws SAXException {
// Waypoint must have a time, else cannot match to the track points // Waypoint must have a time, else cannot match to the track points
if (time == null) { if (time == null) {
return; return;
@@ -318,7 +307,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (category != null) { if (category != null) {
waypoint.setCategory(category); waypoint.setCategory(category);
} }
waypoint.setType(type);
if (photoUrl != null) { if (photoUrl != null) {
waypoint.setPhotoUrl(photoUrl); waypoint.setPhotoUrl(photoUrl);
@@ -500,27 +488,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
data.track.setStopId(contentProviderUtils.getLastTrackPointId(data.track.getId())); data.track.setStopId(contentProviderUtils.getLastTrackPointId(data.track.getId()));
} }
/**
* Inserts the first waypoint, the track statistics waypoint.
*
* @param track the track
*/
private void insertFirstWaypoint(Track track) {
String waypointName = context.getString(R.string.marker_split_name_format, 0);
String waypointCategory = "";
TripStatisticsUpdater updater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
TripStatistics tripStatistics = updater.getTripStatistics();
String waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription(tripStatistics);
String icon = context.getString(R.string.marker_statistics_icon_url);
double length = 0.0;
long duration = 0L;
Location waypointLocation = new Location("");
waypointLocation.setLatitude(100);
waypointLocation.setLongitude(180);
Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon, track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation, tripStatistics, "");
contentProviderUtils.insertWaypoint(waypoint);
}
/** /**
* Cleans up import. * Cleans up import.
*/ */
@@ -535,6 +502,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
// TODO Why private inner class?
private class TrackData { private class TrackData {
// The current track // The current track
Track track = new Track(); Track track = new Track();
@@ -25,7 +25,6 @@ import org.xml.sax.Attributes;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
/** /**
* Imports a GPX file. * Imports a GPX file.
@@ -184,6 +183,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
* On waypoint end. * On waypoint end.
*/ */
private void onWaypointEnd() throws SAXException { private void onWaypointEnd() throws SAXException {
addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS : WaypointType.WAYPOINT); addWaypoint();
} }
} }
@@ -29,7 +29,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -30,8 +30,8 @@ import java.util.ArrayList;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
/** /**
* Imports a KML file. * Imports a KML file.
@@ -42,15 +42,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG = KmlFileTrackImporter.class.getSimpleName(); private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
private static final String CADENCE = "cadence"; private static final String WAYPOINT_STYLE = "#" + KmlTrackWriter.WAYPOINT_STYLE;
private static final String HEART_RATE = "heart_rate";
private static final String POWER = "power";
private static final String STATISTICS_STYLE = "#statistics";
private static final String WAYPOINT_STYLE = "#waypoint";
private static final String TAG_COORDINATES = "coordinates"; private static final String TAG_COORDINATES = "coordinates";
private static final String TAG_DESCRIPTION = "description"; private static final String TAG_DESCRIPTION = "description";
private static final String TAG_ICON = "icon";
private static final String TAG_GX_COORD = "gx:coord"; private static final String TAG_GX_COORD = "gx:coord";
private static final String TAG_GX_MULTI_TRACK = "gx:MultiTrack"; private static final String TAG_GX_MULTI_TRACK = "gx:MultiTrack";
private static final String TAG_GX_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData"; private static final String TAG_GX_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData";
@@ -140,6 +136,10 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (content != null) { if (content != null) {
description = content.trim(); description = content.trim();
} }
} else if (localName.equals(TAG_ICON)) {
if (content != null) {
icon = content.trim();
}
} else if (localName.equals(TAG_VALUE)) { } else if (localName.equals(TAG_VALUE)) {
if (content != null) { if (content != null) {
category = content.trim(); category = content.trim();
@@ -168,6 +168,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private void onWaypointStart() { private void onWaypointStart() {
// Reset all Placemark variables // Reset all Placemark variables
name = null; name = null;
icon = null;
description = null; description = null;
category = null; category = null;
photoUrl = null; photoUrl = null;
@@ -182,20 +183,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
* On waypoint end. * On waypoint end.
*/ */
private void onWaypointEnd() throws SAXException { private void onWaypointEnd() throws SAXException {
if (waypointType == null) { if (!WAYPOINT_STYLE.equals(waypointType)) {
return;
}
// Add a waypoint if the waypointType matches
WaypointType type;
switch (waypointType) {
case WAYPOINT_STYLE:
type = WaypointType.WAYPOINT;
break;
case STATISTICS_STYLE:
type = WaypointType.STATISTICS;
break;
default:
return; return;
} }
@@ -204,7 +192,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
photoUrl = getPhotoUrl(uri.getLastPathSegment()); photoUrl = getPhotoUrl(uri.getLastPathSegment());
} }
addWaypoint(type); addWaypoint();
} }
/** /**
@@ -315,13 +303,13 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e); throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
} }
switch (sensorType) { switch (sensorType) {
case POWER: case KmlTrackWriter.SENSOR_TYPE_POWER:
powerList.add(value); powerList.add(value);
break; break;
case HEART_RATE: case KmlTrackWriter.SENSOR_TYPE_HEART_RATE:
heartRateList.add(value); heartRateList.add(value);
break; break;
case CADENCE: case KmlTrackWriter.SENSOR_TYPE_CADENCE:
cadenceList.add(value); cadenceList.add(value);
break; break;
default: default:
@@ -19,7 +19,6 @@ import android.location.Location;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/** /**
@@ -83,10 +82,9 @@ public interface ITrackRecordingService {
/** /**
* Inserts a waypoint in the current recording track. * Inserts a waypoint in the current recording track.
* *
* @param request the details of the waypoint to be inserted
* @return the ID of the inserted waypoint * @return the ID of the inserted waypoint
*/ */
long insertWaypoint(WaypointCreationRequest request); long insertWaypoint(String name, String category, String description, String photoUrl);
/** /**
* ONLY FOR TESTING * ONLY FOR TESTING
@@ -44,14 +44,11 @@ import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.TrackListActivity; import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider; import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.LocationFactory; import de.dennisguse.opentracks.content.LocationFactory;
import de.dennisguse.opentracks.content.LocationIterator; import de.dennisguse.opentracks.content.LocationIterator;
import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
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.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory; import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
@@ -74,8 +71,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
*/ */
public class TrackRecordingService extends Service { public class TrackRecordingService extends Service {
// The name of extra intent property to indicate whether we want to resume a previously recorded track.
public static final String RESUME_TRACK_EXTRA_NAME = "RESUME_TRACK";
private static final int NOTIFICATION_ID = 123; private static final int NOTIFICATION_ID = 123;
public static final double PAUSE_LATITUDE = 100.0; public static final double PAUSE_LATITUDE = 100.0;
@@ -83,8 +78,6 @@ public class TrackRecordingService extends Service {
// Anything faster than that (in meters per second) will be considered moving. // Anything faster than that (in meters per second) will be considered moving.
public static final double MAX_NO_MOVEMENT_SPEED = 0.224; public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
@VisibleForTesting
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
private static final String TAG = TrackRecordingService.class.getSimpleName(); private static final String TAG = TrackRecordingService.class.getSimpleName();
// 1 minute in milliseconds // 1 minute in milliseconds
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS); private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
@@ -102,7 +95,6 @@ public class TrackRecordingService extends Service {
private int recordingDistanceInterval; private int recordingDistanceInterval;
private int maxRecordingDistance; private int maxRecordingDistance;
private int recordingGpsAccuracy; private int recordingGpsAccuracy;
private int autoResumeTrackTimeout;
private long currentRecordingInterval; private long currentRecordingInterval;
// The following variables are set when recording: // The following variables are set when recording:
@@ -149,12 +141,9 @@ public class TrackRecordingService extends Service {
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) { if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context); recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
} }
if (PreferencesUtils.isKey(context, R.string.auto_resume_track_timeout_key, key)) {
autoResumeTrackTimeout = PreferencesUtils.getAutoResumeTrackTimeout(context);
}
} }
}; };
private TripStatisticsUpdater markerTripStatisticsUpdater;
private WakeLock wakeLock; private WakeLock wakeLock;
private BluetoothRemoteSensorManager remoteSensorManager; private BluetoothRemoteSensorManager remoteSensorManager;
private Location lastLocation; private Location lastLocation;
@@ -240,7 +229,6 @@ public class TrackRecordingService extends Service {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
handleStartCommand(intent, startId);
return START_STICKY; return START_STICKY;
} }
@@ -285,23 +273,14 @@ public class TrackRecordingService extends Service {
super.onDestroy(); super.onDestroy();
} }
/**
* Returns true if the service is recording.
*/
public boolean isRecording() { public boolean isRecording() {
return PreferencesUtils.isRecording(recordingTrackId); return PreferencesUtils.isRecording(recordingTrackId);
} }
/**
* Returns true if the current recording is paused.
*/
public boolean isPaused() { public boolean isPaused() {
return recordingTrackPaused; return recordingTrackPaused;
} }
/**
* Gets the trip statistics.
*/
public TripStatistics getTripStatistics() { public TripStatistics getTripStatistics() {
if (trackTripStatisticsUpdater == null) { if (trackTripStatisticsUpdater == null) {
return null; return null;
@@ -312,130 +291,42 @@ public class TrackRecordingService extends Service {
/** /**
* Inserts a waypoint. * Inserts a waypoint.
* *
* @param waypointCreationRequest the waypoint creation request
* @return the waypoint id * @return the waypoint id
*/ */
public long insertWaypoint(WaypointCreationRequest waypointCreationRequest) { public long insertWaypoint(String name, String category, String description, String photoUrl) {
if (!isRecording() || isPaused()) { if (!isRecording() || isPaused()) {
return -1L; return -1L;
} }
WaypointType waypointType = waypointCreationRequest.getType(); if (name == null) {
boolean isStatistics = waypointType == WaypointType.STATISTICS; int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId);
// Get name
String name;
if (waypointCreationRequest.getName() != null) {
name = waypointCreationRequest.getName();
} else {
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId, waypointType);
if (nextWaypointNumber == -1) { if (nextWaypointNumber == -1) {
nextWaypointNumber = 0; nextWaypointNumber = 1;
} }
name = getString(isStatistics ? R.string.marker_split_name_format : R.string.marker_name_format, nextWaypointNumber); name = getString(R.string.marker_name_format, nextWaypointNumber + 1);
} }
// Get category
String category = waypointCreationRequest.getCategory() != null ? waypointCreationRequest.getCategory() : "";
// Get tripStatistics, description, and icon
TripStatistics tripStatistics;
String description;
String icon;
if (isStatistics) {
long now = System.currentTimeMillis();
markerTripStatisticsUpdater.updateTime(now);
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
markerTripStatisticsUpdater = new TripStatisticsUpdater(now);
description = new DescriptionGeneratorImpl(this).generateWaypointDescription(tripStatistics);
icon = getString(R.string.marker_statistics_icon_url);
} else {
tripStatistics = null;
description = waypointCreationRequest.getDescription() != null ? waypointCreationRequest.getDescription() : "";
//TODO Bundle icon?
icon = getString(R.string.marker_waypoint_icon_url);
}
// Get length and duration
double length;
long duration;
Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId); Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (location != null && trackTripStatisticsUpdater != null) { if (location == null) {
TripStatistics stats = trackTripStatisticsUpdater.getTripStatistics(); Log.i(TAG, "Could not create a waypoint as location is unknown.");
length = stats.getTotalDistance();
duration = stats.getTotalTime();
} else {
if (!waypointCreationRequest.isTrackStatistics()) {
return -1L; return -1L;
} }
// For track statistics, make it an impossible location
location = new Location("");
location.setLatitude(100);
location.setLongitude(180);
length = 0.0;
duration = 0L;
}
String photoUrl = waypointCreationRequest.getPhotoUrl() != null ? waypointCreationRequest.getPhotoUrl() : ""; category = category != null ? category : "";
description = description != null ? description : "";
String icon = getString(R.string.marker_waypoint_icon_url);
photoUrl = photoUrl != null ? photoUrl : "";
TripStatistics stats = trackTripStatisticsUpdater.getTripStatistics();
double length = stats.getTotalDistance();
long duration = stats.getTotalTime();
// Insert waypoint // Insert waypoint
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, waypointType, length, duration, -1L, -1L, location, tripStatistics, photoUrl); Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, -1L, -1L, location, null, photoUrl);
Uri uri = contentProviderUtils.insertWaypoint(waypoint); Uri uri = contentProviderUtils.insertWaypoint(waypoint);
return Long.parseLong(uri.getLastPathSegment()); return Long.parseLong(uri.getLastPathSegment());
} }
/**
* Handles start command.
*
* @param intent the intent
* @param startId the start id
*/
private void handleStartCommand(Intent intent, int startId) {
// Check if the service is called to resume track (from phone reboot)
boolean resumeTrackRequested = intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false);
if (resumeTrackRequested && !shouldResumeTrack()) {
Log.i(TAG, "Stop resume track.");
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
stopSelfResult(startId);
}
}
/**
* Returns true if should resume.
*/
private boolean shouldResumeTrack() {
Track track = contentProviderUtils.getTrack(recordingTrackId);
if (track == null) {
Log.d(TAG, "Not resuming. Track is null.");
return false;
}
int retries = PreferencesUtils.getAutoResumeTrackCurrentRetryDefault(this);
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
Log.d(TAG, "Not resuming. Exceeded maximum retry attempts.");
return false;
}
PreferencesUtils.incrementAutoResumeTrackCurrentRetryDefault(this);
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_never))) {
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
return false;
}
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_always))) {
Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
return true;
}
if (track.getTripStatistics() == null) {
Log.d(TAG, "Not resuming. No trip statistics.");
return false;
}
long stopTime = track.getTripStatistics().getStopTime();
return stopTime > 0 && (System.currentTimeMillis() - stopTime) <= autoResumeTrackTimeout * ONE_MINUTE;
}
/** /**
* Starts a new track. * Starts a new track.
* *
@@ -448,7 +339,6 @@ public class TrackRecordingService extends Service {
} }
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
trackTripStatisticsUpdater = new TripStatisticsUpdater(now); trackTripStatisticsUpdater = new TripStatisticsUpdater(now);
markerTripStatisticsUpdater = new TripStatisticsUpdater(now);
// Insert a track // Insert a track
Track track = new Track(); Track track = new Track();
@@ -457,7 +347,6 @@ public class TrackRecordingService extends Service {
// Update shared preferences // Update shared preferences
updateRecordingState(trackId, false); updateRecordingState(trackId, false);
PreferencesUtils.resetAutoResumeTrackCurrentRetryDefault(this);
// Update database // Update database
track.setId(trackId); track.setId(trackId);
@@ -468,7 +357,6 @@ public class TrackRecordingService extends Service {
track.setIcon(TrackIconUtils.getIconValue(this, category)); track.setIcon(TrackIconUtils.getIconValue(this, category));
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics()); track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
contentProviderUtils.updateTrack(track); contentProviderUtils.updateTrack(track);
insertWaypoint(WaypointCreationRequest.DEFAULT_START_TRACK);
startRecording(true); startRecording(true);
return trackId; return trackId;
@@ -485,23 +373,11 @@ public class TrackRecordingService extends Service {
TripStatistics tripStatistics = track.getTripStatistics(); TripStatistics tripStatistics = track.getTripStatistics();
trackTripStatisticsUpdater = new TripStatisticsUpdater(tripStatistics.getStartTime()); trackTripStatisticsUpdater = new TripStatisticsUpdater(tripStatistics.getStartTime());
long markerStartTime;
Waypoint waypoint = contentProviderUtils.getLastWaypoint(recordingTrackId, WaypointType.STATISTICS);
if (waypoint != null && waypoint.getTripStatistics() != null) {
markerStartTime = waypoint.getTripStatistics().getStopTime();
} else {
markerStartTime = tripStatistics.getStartTime();
}
markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime);
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) { try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
while (locationIterator.hasNext()) { while (locationIterator.hasNext()) {
Location location = locationIterator.next(); Location location = locationIterator.next();
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval); trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
if (location.getTime() > markerStartTime) {
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
}
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
Log.e(TAG, "RuntimeException", e); Log.e(TAG, "RuntimeException", e);
@@ -536,8 +412,7 @@ public class TrackRecordingService extends Service {
} }
/** /**
* Common code for starting a new track, resuming a track, or restarting after * Common code for starting a new track, resuming a track, or restarting after phone reboot.
* phone reboot.
* *
* @param trackStarted true if track is started, false if track is resumed * @param trackStarted true if track is started, false if track is resumed
*/ */
@@ -597,7 +472,7 @@ public class TrackRecordingService extends Service {
contentProviderUtils.updateTrack(track); contentProviderUtils.updateTrack(track);
} }
} }
endRecording(true, trackId); endRecording(true);
} }
/** /**
@@ -625,7 +500,7 @@ public class TrackRecordingService extends Service {
insertLocation(track, pause, null); insertLocation(track, pause, null);
} }
endRecording(false, recordingTrackId); endRecording(false);
notificationManager.updateContent(getString(R.string.generic_paused)); notificationManager.updateContent(getString(R.string.generic_paused));
} }
@@ -634,9 +509,8 @@ public class TrackRecordingService extends Service {
* Common code for ending a track or pausing a track. * Common code for ending a track or pausing a track.
* *
* @param trackStopped true if track is stopped, false if track is paused * @param trackStopped true if track is stopped, false if track is paused
* @param trackId the track id
*/ */
private void endRecording(boolean trackStopped, long trackId) { private void endRecording(boolean trackStopped) {
// Shutdown periodic tasks // Shutdown periodic tasks
voiceExecutor.shutdown(); voiceExecutor.shutdown();
@@ -815,7 +689,6 @@ public class TrackRecordingService extends Service {
Uri uri = contentProviderUtils.insertTrackPoint(location, track.getId()); Uri uri = contentProviderUtils.insertTrackPoint(location, track.getId());
long trackPointId = Long.parseLong(uri.getLastPathSegment()); long trackPointId = Long.parseLong(uri.getLastPathSegment());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval); trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location)); updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
} catch (SQLiteException e) { } catch (SQLiteException e) {
/* /*
@@ -1003,8 +876,8 @@ public class TrackRecordingService extends Service {
} }
@Override @Override
public long insertWaypoint(WaypointCreationRequest waypointCreationRequest) { public long insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(waypointCreationRequest); return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
} }
@VisibleForTesting @VisibleForTesting
@@ -1015,15 +888,7 @@ public class TrackRecordingService extends Service {
@Override @Override
public SensorDataSet getSensorData() { public SensorDataSet getSensorData() {
if (trackRecordingService.remoteSensorManager == null) { return trackRecordingService.getSensorDataSet();
Log.d(TAG, "remoteSensorManager is null.");
return null;
}
if (trackRecordingService.getSensorDataSet() == null) {
Log.d(TAG, "Sensor data set is null.");
return null;
}
return trackRecordingService.remoteSensorManager.getSensorDataSet();
} }
/** /**
@@ -31,7 +31,6 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackEditActivity; import de.dennisguse.opentracks.TrackEditActivity;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.ServiceUtils; import de.dennisguse.opentracks.util.ServiceUtils;
@@ -48,7 +47,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName(); private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
private final Context context;
private final Runnable callback; private final Runnable callback;
private ITrackRecordingService trackRecordingService; private ITrackRecordingService trackRecordingService;
@@ -60,29 +58,28 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* @param callback the callback to invoke when the service binding changes * @param callback the callback to invoke when the service binding changes
*/ */
public TrackRecordingServiceConnection(Context context, Runnable callback) { public TrackRecordingServiceConnection(Context context, Runnable callback) {
this.context = context;
this.callback = callback; this.callback = callback;
} }
/** /**
* Starts and binds the service. * Starts and binds the service.
*/ */
public void startAndBind() { public void startAndBind(Context context) {
bindService(true); bindService(context, true);
} }
/** /**
* Binds the service if it is started. * Binds the service if it is started.
*/ */
public void bindIfStarted() { public void bindIfStarted(Context context) {
bindService(false); bindService(context, false);
} }
/** /**
* Unbinds and stops the service. * Unbinds and stops the service.
*/ */
public void unbindAndStop() { public void unbindAndStop(Context context) {
unbind(); unbind(context);
context.stopService(new Intent(context, TrackRecordingService.class)); context.stopService(new Intent(context, TrackRecordingService.class));
} }
@@ -109,7 +106,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/** /**
* Unbinds the service (but leave it running). * Unbinds the service (but leave it running).
*/ */
public void unbind() { public void unbind(Context context) {
try { try {
context.unbindService(this); context.unbindService(this);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -146,7 +143,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* *
* @param startIfNeeded start the service if needed * @param startIfNeeded start the service if needed
*/ */
private void bindService(boolean startIfNeeded) { private void bindService(Context context, boolean startIfNeeded) {
if (trackRecordingService != null) { if (trackRecordingService != null) {
// Service is already started and bound. // Service is already started and bound.
return; return;
@@ -173,7 +170,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* @param context the context * @param context the context
*/ */
public void startConnection(@NonNull Context context) { public void startConnection(@NonNull Context context) {
bindIfStarted(); bindIfStarted(context);
if (!ServiceUtils.isTrackRecordingServiceRunning(context)) { if (!ServiceUtils.isTrackRecordingServiceRunning(context)) {
resetRecordingState(context); resetRecordingState(context);
} }
@@ -214,13 +211,13 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* *
* @return the id of the marker or -1L if none could be created. * @return the id of the marker or -1L if none could be created.
*/ */
public long addMarker(Context context, WaypointCreationRequest waypointCreationRequest) { public long addMarker(Context context, String name, String category, String description, String photoUrl) {
ITrackRecordingService trackRecordingService = getServiceIfBound(); ITrackRecordingService trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) { if (trackRecordingService == null) {
Log.d(TAG, "Unable to add marker, no track recording service"); Log.d(TAG, "Unable to add marker, no track recording service");
} else { } else {
try { try {
long markerId = trackRecordingService.insertWaypoint(waypointCreationRequest); long markerId = trackRecordingService.insertWaypoint(name, category, description, photoUrl);
if (markerId != -1L) { if (markerId != -1L) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return markerId; return markerId;
@@ -264,6 +261,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
Log.e(TAG, "Unable to stop recording.", e); Log.e(TAG, "Unable to stop recording.", e);
} }
} }
unbindAndStop(); unbindAndStop(context);
} }
} }
@@ -81,7 +81,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() { private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() {
@Override @Override
public void onStart(String utteranceId) { public void onStart(String utteranceId) {
int result = audioManager.requestAudioFocus(audioFocusChangeListener, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); int result = audioManager.requestAudioFocus(audioFocusChangeListener, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "Failed to request audio focus."); Log.w(TAG, "Failed to request audio focus.");
} }
@@ -89,7 +89,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
@Override @Override
public void onDone(String utteranceId) { public void onDone(String utteranceId) {
int result = audioManager.abandonAudioFocus(null); int result = audioManager.abandonAudioFocus(audioFocusChangeListener);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "Failed to relinquish audio focus."); Log.w(TAG, "Failed to relinquish audio focus.");
} }
@@ -142,22 +142,4 @@ final class PreferenceHelper {
return entries; return entries;
} }
static String[] getAutoResumeTrackTimeoutEntries(Context context) {
String[] entryValues = context.getResources().getStringArray(R.array.auto_resume_track_timeout_values);
String[] entries = new String[entryValues.length];
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
entries[i] = context.getString(R.string.value_never);
} else if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
entries[i] = context.getString(R.string.value_always);
} else {
entries[i] = context.getString(R.string.value_integer_minute, value);
}
}
return entries;
}
} }
@@ -73,14 +73,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
} }
}); });
} }
if (PreferencesUtils.isKey(getActivity(), R.string.stats_rate_key, key)) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
updateSpeed();
}
});
}
} }
}; };
@@ -104,7 +96,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
updateSpeed();
updateUnits(); updateUnits();
updateReset(); updateReset();
updateBluetooth(); updateBluetooth();
@@ -150,11 +141,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
resetPreference.setEnabled(!isRecording); resetPreference.setEnabled(!isRecording);
} }
private void updateSpeed() {
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace);
}
private void updateBluetooth() { private void updateBluetooth() {
// Disable Bluetooth preference if device does not have Bluetooth // Disable Bluetooth preference if device does not have Bluetooth
BluetoothLePreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key)); BluetoothLePreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key));
@@ -179,9 +165,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key)); ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key));
recordingGpsAccuracy.setEntries(PreferenceHelper.getRecordingGpsAccuracyEntries(getActivity(), metricUnits)); recordingGpsAccuracy.setEntries(PreferenceHelper.getRecordingGpsAccuracyEntries(getActivity(), metricUnits));
ListPreference autoresumeTrack = findPreference(getString(R.string.auto_resume_track_timeout_key));
autoresumeTrack.setEntries(PreferenceHelper.getAutoResumeTrackTimeoutEntries(getActivity()));
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key)); ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options); String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
statsRatePreferences.setEntries(entries); statsRatePreferences.setEntries(entries);
@@ -34,10 +34,10 @@ import java.util.Date;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl; import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.ShareContentProvider; import de.dennisguse.opentracks.content.ShareContentProvider;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
/** /**
@@ -77,7 +77,7 @@ public class IntentUtils {
String trackDescription = ""; String trackDescription = "";
if (trackIds.length == 1) { if (trackIds.length == 1) {
Track track = new ContentProviderUtils(context).getTrack(trackIds[0]); Track track = new ContentProviderUtils(context).getTrack(trackIds[0]);
trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, false); trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false);
} }
String action = trackIds.length == 1 ? Intent.ACTION_SEND : Intent.ACTION_SEND_MULTIPLE; String action = trackIds.length == 1 ? Intent.ACTION_SEND : Intent.ACTION_SEND_MULTIPLE;
@@ -22,7 +22,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
/** /**
* Utility class for decimating tracks at a given level of precision. * Utility class for decimating tracks at a given level of precision.
@@ -164,6 +164,7 @@ public class LocationUtils {
* @param location the location to test * @param location the location to test
* @return true if the location is a valid location. * @return true if the location is a valid location.
*/ */
//TODO Still needed?
public static boolean isValidLocation(Location location) { public static boolean isValidLocation(Location location) {
return location != null return location != null
&& Math.abs(location.getLatitude()) <= 90 && Math.abs(location.getLatitude()) <= 90
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
@@ -41,29 +40,13 @@ public class PreferencesUtils {
@Deprecated @Deprecated
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly. //NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
//NOTE: This is also used to recover from a reboot, but this data should not be exposed to the whole application. //NOTE: This was also used to recover from a reboot, but this data should not be exposed to the whole application.
public static final long RECORDING_TRACK_ID_DEFAULT = -1L; public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
public static long getRecordingTrackId(Context context) { public static long getRecordingTrackId(Context context) {
return PreferencesUtils.getLong(context, R.string.recording_track_id_key, RECORDING_TRACK_ID_DEFAULT); return PreferencesUtils.getLong(context, R.string.recording_track_id_key, RECORDING_TRACK_ID_DEFAULT);
} }
@VisibleForTesting
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
public static int getAutoResumeTrackCurrentRetryDefault(Context context) {
return PreferencesUtils.getInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
}
public static void resetAutoResumeTrackCurrentRetryDefault(Context context) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
}
public static void incrementAutoResumeTrackCurrentRetryDefault(Context context) {
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, getAutoResumeTrackCurrentRetryDefault(context) + 1);
}
public static String getDefaultActivity(Context context) { public static String getDefaultActivity(Context context) {
return PreferencesUtils.getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default)); return PreferencesUtils.getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
} }
@@ -86,7 +69,7 @@ public class PreferencesUtils {
* Compares if keyId and key belong to the same shared preference key. * Compares if keyId and key belong to the same shared preference key.
* *
* @param keyId The resource id of the key * @param keyId The resource id of the key
* @param key * @param key The key of the preference
* @return true if key == null or key belongs to keyId * @return true if key == null or key belongs to keyId
*/ */
public static boolean isKey(Context context, int keyId, String key) { public static boolean isKey(Context context, int keyId, String key) {
@@ -209,33 +192,16 @@ public class PreferencesUtils {
editor.apply(); editor.apply();
} }
/**
* Returns true if metric units.
*
* @param context the context
*/
public static boolean isMetricUnits(Context context) { public static boolean isMetricUnits(Context context) {
final String STATS_UNIT = context.getString(R.string.stats_units_default); final String STATS_UNIT = context.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(context, R.string.stats_units_key, STATS_UNIT)); return STATS_UNIT.equals(getString(context, R.string.stats_units_key, STATS_UNIT));
} }
/**
* Returns true if the preferred rate is speed, false if the preferred rate is
* pace.
*
* @param context the context
*/
public static boolean isReportSpeed(Context context) { public static boolean isReportSpeed(Context context) {
final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default); final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT)); return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT));
} }
public static int getAutoResumeTrackTimeout(Context context) {
final int AUTO_RESUME_TRACK_TIMEOUT = Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default));
return PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, AUTO_RESUME_TRACK_TIMEOUT);
}
public static boolean isRecordingTrackPaused(Context context) { public static boolean isRecordingTrackPaused(Context context) {
return PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context)); return PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
} }
@@ -265,31 +231,6 @@ public class PreferencesUtils {
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothHeartRateSensorAddressNone(context)); return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothHeartRateSensorAddressNone(context));
} }
public static boolean shouldChartShowCadence(Context context) {
final boolean CHART_SHOW_CADENCE = context.getResources().getBoolean(R.bool.chart_show_cadence_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_cadence_key, CHART_SHOW_CADENCE);
}
public static boolean shouldChartShowElevation(Context context) {
final boolean CHART_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.chart_show_elevation_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_elevation_key, CHART_SHOW_ELEVATION);
}
public static boolean shouldChartShowHeartRate(Context context) {
final boolean CHART_SHOW_HEARTRATE = context.getResources().getBoolean(R.bool.chart_show_heart_rate_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_heart_rate_key, CHART_SHOW_HEARTRATE);
}
public static boolean shouldChartShowPower(Context context) {
final boolean CHART_SHOW_POWER = context.getResources().getBoolean(R.bool.chart_show_power_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_power_key, CHART_SHOW_POWER);
}
public static boolean shouldChartShowSpeed(Context context) {
final boolean CHART_SHOW_SPEED = context.getResources().getBoolean(R.bool.chart_show_speed_default);
return PreferencesUtils.getBoolean(context, R.string.chart_show_speed_key, CHART_SHOW_SPEED);
}
public static boolean shouldShowStatsOnLockscreen(Context context) { public static boolean shouldShowStatsOnLockscreen(Context context) {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default); final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT); return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
@@ -316,7 +257,6 @@ public class PreferencesUtils {
} }
public static int getRecordingDistanceInterval(Context context) { public static int getRecordingDistanceInterval(Context context) {
final int RECORDING_DISTANCE_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
return PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context)); return PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
} }
@@ -347,9 +287,6 @@ public class PreferencesUtils {
} }
public static int getRecordingGPSAccuracy(Context context) { public static int getRecordingGPSAccuracy(Context context) {
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)); final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
return PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY); return PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
@@ -154,33 +154,33 @@ public class StringUtils {
* Get the formatted distance with unit. * Get the formatted distance with unit.
* *
* @param context the context * @param context the context
* @param distance the distance * @param distance_m the distance
* @param metricUnits true to use metric unit * @param metricUnits true to use metric unit
* @return the formatted distance (or null) and it's unit as {@link Pair} * @return the formatted distance (or null) and it's unit as {@link Pair}
*/ */
public static Pair<String, String> getDistanceParts(Context context, double distance, boolean metricUnits) { public static Pair<String, String> getDistanceParts(Context context, double distance_m, boolean metricUnits) {
if (Double.isNaN(distance) || Double.isInfinite(distance)) { if (Double.isNaN(distance_m) || Double.isInfinite(distance_m)) {
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet)); return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
} }
int unitId; int unitId;
if (metricUnits) { if (metricUnits) {
if (distance > 500.0) { if (distance_m > 500.0) {
distance *= UnitConversions.M_TO_KM; distance_m *= UnitConversions.M_TO_KM;
unitId = R.string.unit_kilometer; unitId = R.string.unit_kilometer;
} else { } else {
unitId = R.string.unit_meter; unitId = R.string.unit_meter;
} }
} else { } else {
if (distance * UnitConversions.M_TO_MI > 0.5) { if (distance_m * UnitConversions.M_TO_MI > 0.5) {
distance *= UnitConversions.M_TO_MI; distance_m *= UnitConversions.M_TO_MI;
unitId = R.string.unit_mile; unitId = R.string.unit_mile;
} else { } else {
distance *= UnitConversions.M_TO_FT; distance_m *= UnitConversions.M_TO_FT;
unitId = R.string.unit_feet; unitId = R.string.unit_feet;
} }
} }
return new Pair<>(formatDecimal(distance), context.getString(unitId)); return new Pair<>(formatDecimal(distance_m), context.getString(unitId));
} }
/** /**
@@ -382,11 +382,10 @@ public class StringUtils {
String value = context.getString(R.string.value_unknown); String value = context.getString(R.string.value_unknown);
String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet); String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
if (!Double.isNaN(elevation) && !Double.isInfinite(elevation)) { if (!Double.isNaN(elevation) && !Double.isInfinite(elevation)) {
if (metricUnits) { if (!metricUnits) {
value = StringUtils.formatDecimal(elevation); elevation *= UnitConversions.M_TO_FT;
} else {
value = StringUtils.formatDecimal(elevation * UnitConversions.M_TO_FT);
} }
value = StringUtils.formatDecimal(elevation, 0);
} }
return new Pair<>(value, unit); return new Pair<>(value, unit);
} }
@@ -19,8 +19,8 @@ package de.dennisguse.opentracks.util;
import android.content.Context; import android.content.Context;
import de.dennisguse.opentracks.content.ContentProviderUtils; import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
/** /**
* Utilities for updating track. * Utilities for updating track.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

+8 -1
View File
@@ -17,10 +17,16 @@ limitations under the License.
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:baselineAligned="false" android:baselineAligned="false"
android:orientation="horizontal"> android:orientation="vertical">
<include layout="@layout/toolbar" /> <include layout="@layout/toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal">
<include layout="@layout/track_controller" /> <include layout="@layout/track_controller" />
<ListView <ListView
@@ -93,4 +99,5 @@ limitations under the License.
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>
@@ -17,10 +17,6 @@ limitations under the License.
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView <ImageView
android:id="@+id/marker_detail_waypoint_photo" android:id="@+id/marker_detail_waypoint_photo"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -67,5 +63,4 @@ limitations under the License.
style="@style/TextSmall" /> style="@style/TextSmall" />
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
</FrameLayout>
</FrameLayout> </FrameLayout>
-16
View File
@@ -33,22 +33,6 @@ limitations under the License.
android:text="@string/generic_name" /> android:text="@string/generic_name" />
<LinearLayout <LinearLayout
android:id="@+id/marker_edit_statistics_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/marker_edit_statistics_name"
style="@style/EditNameMarker"
android:drawableLeft="@drawable/ic_marker_blue_pushpin"
android:hint="@string/generic_name"
android:imeOptions="actionDone"
android:importantForAutofill="no" />
</LinearLayout>
<LinearLayout
android:id="@+id/marker_edit_waypoint_section"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
+364 -1
View File
@@ -21,6 +21,369 @@ limitations under the License.
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/stats_content" /> <!-- Distance and activity type -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Distance -->
<LinearLayout style="@style/StatsLargeItemContainer">
<TextView
style="@style/StatsLargeLabel"
android:text="@string/stats_distance" />
<LinearLayout style="@style/StatsLargeValueContainer">
<TextView
android:id="@+id/stats_distance_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_distance_unit"
style="@style/StatsUnit" />
</LinearLayout>
</LinearLayout>
<!-- Activity type -->
<include
android:id="@+id/stats_activity_type"
layout="@layout/stats_activity_type" />
</LinearLayout>
<!-- Total time and moving time -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Total time -->
<LinearLayout style="@style/StatsLargeItemContainer">
<TextView
style="@style/StatsLargeLabel"
android:text="@string/stats_total_time" />
<TextView
android:id="@+id/stats_total_time_value"
style="@style/StatsLargeValue" />
</LinearLayout>
<!-- Moving time -->
<LinearLayout style="@style/StatsLargeItemContainer">
<TextView
style="@style/StatsLargeLabel"
android:text="@string/stats_moving_time" />
<TextView
android:id="@+id/stats_moving_time_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown" />
</LinearLayout>
</LinearLayout>
<!-- Horizontal line -->
<View style="@style/StatsHorizontalLine" />
<!-- Speed/pace and average -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Speed/pace -->
<LinearLayout
android:id="@+id/stats_speed"
style="@style/StatsSmallItemContainer">
<TextView
android:id="@+id/stats_speed_label"
style="@style/StatsSmallLabel" />
<LinearLayout style="@style/StatsSmallValueContainer">
<TextView
android:id="@+id/stats_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_speed_unit"
style="@style/StatsUnit" />
</LinearLayout>
</LinearLayout>
<!-- Average -->
<LinearLayout style="@style/StatsSmallItemContainer">
<TextView
android:id="@+id/stats_average_speed_label"
style="@style/StatsSmallLabel" />
<LinearLayout style="@style/StatsSmallValueContainer">
<TextView
android:id="@+id/stats_average_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_average_speed_unit"
style="@style/StatsUnit" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- Speed max and moving -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Speed max -->
<LinearLayout style="@style/StatsSmallItemContainer">
<TextView
android:id="@+id/stats_max_speed_label"
style="@style/StatsSmallLabel" />
<LinearLayout style="@style/StatsSmallValueContainer">
<TextView
android:id="@+id/stats_max_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_max_speed_unit"
style="@style/StatsUnit" />
</LinearLayout>
</LinearLayout>
<!-- Speed moving -->
<LinearLayout style="@style/StatsSmallItemContainer">
<TextView
android:id="@+id/stats_moving_speed_label"
style="@style/StatsSmallLabel" />
<LinearLayout style="@style/StatsSmallValueContainer">
<TextView
android:id="@+id/stats_moving_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_moving_speed_unit"
style="@style/StatsUnit" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/stats_elevation_separator"
style="@style/StatsHorizontalLine" />
<LinearLayout
android:id="@+id/stats_elevation_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout style="@style/StatsSmallGroupContainer">
<!-- This is a placeholder -->
</LinearLayout>
<!-- Elevation -->
<LinearLayout style="@style/StatsSmallGroupContainer">
<TextView
style="@style/StatsSmallLabel"
android:text="@string/stats_elevation" />
<TableLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<!-- Current elevation -->
<TableRow
android:id="@+id/stats_elevation"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
android:id="@+id/stats_elevation_current_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_elevation_current_unit"
style="@style/StatsUnit" />
</LinearLayout>
</TableRow>
<!-- Minimal elevation -->
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
android:id="@+id/stats_elevation_min_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_elevation_min_unit"
style="@style/StatsUnit" />
</LinearLayout>
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
style="@style/StatsSmallValue"
android:visibility="invisible" />
<TextView
style="@style/StatsSmallLabel"
android:layout_gravity="bottom"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/stats_min" />
</LinearLayout>
</TableRow>
<!-- Maximal elevation -->
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
android:id="@+id/stats_elevation_max_value"
style="@style/StatsSmallValue" />
<TextView
android:id="@+id/stats_elevation_max_unit"
style="@style/StatsUnit" />
</LinearLayout>
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
style="@style/StatsSmallValue"
android:visibility="invisible" />
<TextView
style="@style/StatsSmallLabel"
android:layout_gravity="bottom"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/stats_max" />
</LinearLayout>
</TableRow>
</TableLayout>
</LinearLayout>
</LinearLayout>
<!-- Horizontal line -->
<View
android:id="@+id/stats_coordinate_separator"
style="@style/StatsHorizontalLine" />
<!-- Latitude and longitude -->
<LinearLayout
android:id="@+id/stats_coordinate_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout style="@style/StatsSmallItemContainer">
<TextView
style="@style/StatsSmallLabel"
android:text="@string/stats_latitude" />
<TextView
android:id="@+id/stats_latitude_value"
style="@style/StatsSmallValue" />
</LinearLayout>
<LinearLayout style="@style/StatsSmallItemContainer">
<TextView
style="@style/StatsSmallLabel"
android:text="@string/stats_longitude" />
<TextView
android:id="@+id/stats_longitude_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/stats_sensor_separator"
style="@style/StatsHorizontalLine" />
<!-- Sensor data: heart rate-->
<LinearLayout
android:id="@+id/stats_sensor_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout style="@style/StatsLargeItemContainer">
<TextView
style="@style/StatsLargeLabel"
android:text="@string/sensor_state_heart_rate" />
<LinearLayout style="@style/StatsLargeValueContainer">
<TextView
android:id="@+id/stats_sensor_heart_rate_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown" />
<TextView
style="@style/StatsUnit"
android:text="@string/sensor_unit_beats_per_minute" />
</LinearLayout>
</LinearLayout>
<LinearLayout style="@style/StatsSmallGroupContainer">
<TextView
style="@style/StatsSmallLabel"
android:text="@string/settings_sensor_bluetooth_sensor" />
<TextView
android:id="@+id/stats_sensor_heart_rate_sensor_value"
style="@style/StatsTinyValue"
android:value="@string/value_unknown" />
</LinearLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

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