Compare commits

..

56 Commits

Author SHA1 Message Date
Dennis Guse bbb2f874ee Release v3.3.1 2020-01-03 18:11:57 +01:00
Dennis Guse 9af3759b07 Bugfix: app name (release) not shown.
Introduced in 6cc7271a41.
2020-01-03 17:54:15 +01:00
Dennis Guse 1f6fe6bf0c Android 10: on uninstall ask the user if the app data should be kept. 2020-01-03 17:50:07 +01:00
Dennis Guse 7b291e85d7 Renamed DeleteActivity to TrackDeleteActivity. 2020-01-03 17:04:51 +01:00
Dennis Guse 0f72066bc0 Add IntentChooser for dashboards. 2020-01-02 19:56:24 +01:00
Dennis Guse 70e0e7b216 Add README for data API. 2020-01-02 19:54:19 +01:00
Peter Storch 7e50bb1b6d Merge pull request #114 from pstorch/79_osm_plugin
integrate dashboard plugin
2020-01-02 17:56:07 +01:00
Peter Storch a3eb107068 integrate dashboard plugin mechanism
To be able to integrate the OSMDashboard https://github.com/OpenTracksApp/OSMDashboard,
which addresses #79. Also other Dashboards will be possible.
2020-01-02 17:45:01 +01:00
Dennis Guse 6133ef121a Release v3.3.0 2020-01-01 14:46:28 +01:00
Dennis Guse c6d24e9534 Pictures: removed MediaScanner. 2020-01-01 14:44:23 +01:00
Dennis Guse 441d3f721f Debug version: use a black logo. 2020-01-01 14:39:16 +01:00
Dennis Guse 3c212e4bcc Bugfix: for taking pictures; provide access to external/pictures. 2020-01-01 14:28:58 +01:00
Dennis Guse 50eb71bc1f Cleanup. 2020-01-01 14:16:02 +01:00
Dennis Guse 0806821b5f Fixed import/export message to show Storage Access Framework selected path. 2019-12-29 23:33:10 +01:00
Dennis Guse 36d0f17953 Removed french Congo translation (identical to values-fr). 2019-12-29 21:57:39 +01:00
Dennis Guse 700adb7972 Export dialog: removed "from folder" text. 2019-12-29 21:56:48 +01:00
Dennis Guse a156369f8b Import dialog: removed "from folder" text. 2019-12-29 21:32:01 +01:00
Dennis Guse c0f6dc511b Removed values-ar-rXB/strings.xml as it was for testing only. 2019-12-29 21:28:36 +01:00
Dennis Guse 63e1461eba Removed values-en-rXA/strings.xml as it was for testing only. 2019-12-29 20:59:30 +01:00
Dennis Guse 804e5f10c8 Removed values-bn/strings.xml as translations were invalid. 2019-12-29 20:53:37 +01:00
Dennis Guse 6cc7271a41 Build: debug version uses different icon and appLabel. 2019-12-29 20:46:46 +01:00
Dennis Guse f66194189b Removed translation description from values/strings.xml 2019-12-29 20:15:37 +01:00
Dennis Guse 5d97d6d492 Use Android 7.0 localization: merge latin american and present en-rGB as default for en. 2019-12-29 19:13:26 +01:00
Dennis Guse f3a308e669 BREAKING CHANGE: pictures are now stored in getExternalFilesDir(); no migration takes place! 2019-12-29 15:43:06 +01:00
Dennis Guse a230504925 Export & import via Storage Access Framework. 2019-12-29 15:41:28 +01:00
Dennis Guse 90e19e282c Upgraded tests to Junit4. 2019-12-29 15:31:53 +01:00
Dennis Guse b585bfe44c Moved to Github organization: OpenTracksApp. 2019-12-27 23:54:29 +01:00
Dennis Guse 475e04eeb4 Add PlantUML model for data structures. 2019-12-27 18:07:29 +01:00
Dennis Guse f89b2b0efb Removed TripStatists min/max latitude/longitude.
This also removed: search does not account for closer tracks (to current position).
2019-12-27 18:01:11 +01:00
Dennis Guse b94f3c176b Include unit in variable names. 2019-12-27 18:01:10 +01:00
Dennis Guse d1d729d287 Removed unused ContentProvider.getFirstTrackPointId(). 2019-12-27 18:01:07 +01:00
Dennis Guse e23bbcc405 Removed unnecessary Track.startId and Track.stopId. 2019-12-27 18:01:05 +01:00
Dennis Guse 4129985b0f Renamed SensorDataSetLocation to TrackPoint (similar to SQLite table). 2019-12-27 18:00:51 +01:00
Dennis Guse 5cb7b623d9 Removed TripStatistics from Waypoints. 2019-12-26 21:15:42 +01:00
Dennis Guse 3c52efadd6 Removed unused Waypoint.TripStatistics. 2019-12-26 21:15:41 +01:00
Dennis Guse 73b4977051 Removed unused Waypoint.startId and Waypoint.stopId. 2019-12-26 21:15:41 +01:00
Dennis Guse 422c2c02ce Assume GPS provides always time (removed workaround). 2019-12-26 21:15:41 +01:00
Dennis Guse d692329975 Settings: hide advanced recording settings.
And use switches rather than checkboxes.
2019-12-25 23:22:20 +01:00
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
179 changed files with 2192 additions and 17992 deletions
+5 -2
View File
@@ -30,10 +30,10 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
## Features:
* __Tracking:__ track your sport and outdoor activities
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
* __Heart rate:__ supports bluetooth LE sensors
* __Heart rate:__ supports Bluetooth LE sensors
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
* __No Internet access:__ Internet is not used
* __Voice annoucements__
* __Voice announcements__
* __No advertising__
* __Privacy:__
* _does not contain any in-app analytics_
@@ -47,6 +47,9 @@ _Please note:_
_Tracks_ does not contain a _map_ (as this would required Internet access).
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
## Custom Dashboards
As of v3.3.1 OpenTracks enables to use custom dashboards.
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
## Screenshots
<div>
+30
View File
@@ -0,0 +1,30 @@
# OpenTracks Data API
## Reason
OpenTracks by itself only provides basic features for data visualization and also lacks functionality for analytics.
By itself OpenTracks provides a basic dashboard that can show summary of a track (while recording as well as afterwards).
Although this might be (mostly) sufficient, it has some limitations.
This include limited configurability (e.g., ordering of shown elements) and the lack of Internet access.
In fact, the later is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
_NOTE:_ for showing a track on a map a workaround was implemented in OpenTracks.
Data could be exported as KMZ and presented in 3rd-party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)).
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
## Implementation
OpenTracks by default does not expose any data to 3rd-party applications.
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary) as well as the `TrackPoint`s.
For each, one URI is provided.
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
Limitations:
* only tracks selected by the user is exposed,
* `Waypoints` are not exposed,
* access is only granted temporarily and automatically revoked (see `FLAG_GRANT_READ_URI_PERMISSION`),
* no write access is impossible, and
* neither track recordings can be started, paused, or stopped.
All required information can be found in `IntentDashboardUtils.java`.
A _reference implementation_ is the [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard).
+6 -5
View File
@@ -24,12 +24,13 @@ Application id: de.dennisguse.opentracks.playstore
Requirement: `gem install fastlane`
1. Update store meta data (e.g., description and screenshots)
`fastlane supply --version_code $(git rev-list HEAD --count master) --skip_upload_apk`
(requires API key)
2. Build signed apk
1. Build signed apk
(requires keystore)
3. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
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)
+11 -1
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
@@ -54,6 +54,11 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
manifestPlaceholders = [
applicationLogo: "@drawable/ic_logo_color_24dp",
applicationName: "@string/app_name"
]
//Useful for debugging.
//versionCode getVersionCode()
@@ -69,6 +74,10 @@ android {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
manifestPlaceholders = [
applicationLogo: "@drawable/ic_logo_black_24dp",
applicationName: "OpenTracks (Debug)"
]
}
release {
@@ -100,4 +109,5 @@ dependencies {
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.mockito:mockito-core:3.0.0'
implementation 'com.google.android.material:material:1.0.0'
}
+52
View File
@@ -0,0 +1,52 @@
@startuml
class Track {
-id
-name
-description
-category
-numberOfPoints
-icon
}
Track o-- TrackPoint
Track o-- Waypoint
Track "1" - "1" TripStatistics
class TripStatistics {
- startTime
- stopTime
- totalDistance
- totalTime
- movingTime
- maxSpeed
- totalElevationGain
}
class TrackPoint extends android.location.Location {
- id
- trackId
- longitude
- latitude
- time
- altitude
- accuracy
- speed
- bearing
- sensor_heartrate
- sensor_cadence
- sensor_power
}
class Waypoint {
- id
- trackId
- name
- description
- category
- icon
- length
- duration
- location
- tripStatistics
- photoUrl
}
@enduml
@@ -0,0 +1,12 @@
v3.2.11: OpenTracks
Bug:
- Max speed is identical to avg speed
Bugfix:
- Crash on heart rate sensor selection
Developer:
- Replaced TabHost by TabsLayout
- Removed special first waypoint
- Removed duplicate translations
@@ -0,0 +1,13 @@
v3.3.0: OpenTracks
Breaking changes:
- Pictures are now stored in app-private storage (do not show up other picture apps)
NOTE: Pictures taking with previous version cannot be shown
- Localization for Spanish (Latin America) only works on Android 7.0 or later
Changes:
- Import/Export via Storage Access Framework (avoids READ/WRITE_EXTERNAL_STORAGE permission)
Developer:
- Cleaned internal data structures
- Use separate icons/app names for debug version
@@ -0,0 +1,12 @@
v3.3.1: OpenTracks
Changes:
- Android 10: on uninstall allow to keep app data
- Introduced Dashboard API: map icon can be used to open current track in external app
(requires installation of another app like OSMDashboard)
Bugfix:
- App label was broken (introduced in v3.3.0)
Developer:
- Documentation for Dashboard API can be found in README_DEVELOPER.md
@@ -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.
@@ -18,8 +18,8 @@ package de.dennisguse.opentracks;
import android.location.Location;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
@@ -60,16 +60,16 @@ public class TrackStubUtils {
*
* @return a track stub.
*/
public static SensorDataSetLocation createSensorDataSetLocation() {
public static TrackPoint createSensorDataSetLocation() {
return createSensorDataSetLocation(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE);
}
/**
* Creates a {@link SensorDataSetLocation} stub with specified values.
* Creates a {@link TrackPoint} stub with specified values.
*
* @return a SensorDataSetLocation stub.
*/
private static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, double altitude) {
private static TrackPoint createSensorDataSetLocation(double latitude, double longitude, double altitude) {
// Initial Location
Location loc = new Location(LOCATION_PROVIDER);
loc.setLatitude(latitude);
@@ -81,6 +81,6 @@ public class TrackStubUtils {
loc.setBearing(INITIAL_BEARING);
SensorDataSet sd = new SensorDataSet(Float.NaN, Float.NaN);
return new SensorDataSetLocation(loc, sd);
return new TrackPoint(loc, sd);
}
}
@@ -27,6 +27,9 @@ import org.junit.Before;
import org.junit.Test;
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}.
@@ -35,7 +35,12 @@ import java.util.ArrayList;
import java.util.List;
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 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 TEST_DESC = "Test Desc!";
private static final String TEST_DESC_NEW = "Test Desc new!";
private static final double INITIAL_LATITUDE = 37.0;
private static final double INITIAL_LONGITUDE = -57.0;
private static final double ALTITUDE_INTERVAL = 2.5;
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils;
private ContentProviderUtils contentProviderUtils;
@Mock
private Cursor cursorMock;
@@ -67,8 +69,8 @@ public class CustomContentProviderUtilsTest {
@Before
public void setUp() {
providerUtils = new ContentProviderUtils(context);
providerUtils.deleteAllTracks(context);
contentProviderUtils = new ContentProviderUtils(context);
contentProviderUtils.deleteAllTracks(context);
}
@Test
@@ -132,9 +134,9 @@ public class CustomContentProviderUtilsTest {
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
long lastPointId = initializeTrack(trackId, numPoints);
((ContentProviderUtils) providerUtils).setDefaultCursorBatchSize(batchSize);
((ContentProviderUtils) contentProviderUtils).setDefaultCursorBatchSize(batchSize);
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()) {
Location loc = it.next();
Assert.assertNotNull(loc);
@@ -153,8 +155,8 @@ public class CustomContentProviderUtilsTest {
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
providerUtils.insertTrack(track);
track = providerUtils.getTrack(id);
contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
Location[] locations = new Location[numPoints];
@@ -166,12 +168,12 @@ public class CustomContentProviderUtilsTest {
loc.setAltitude(i * 2.5);
locations[i] = loc;
}
providerUtils.bulkInsertTrackPoint(locations, numPoints, id);
contentProviderUtils.bulkInsertTrackPoint(locations, numPoints, id);
// Load all inserted locations.
long lastPointId = -1;
int counter = 0;
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
try (LocationIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
while (it.hasNext()) {
it.next();
lastPointId = it.getLocationId();
@@ -208,7 +210,7 @@ public class CustomContentProviderUtilsTest {
String name = NAME_PREFIX + trackId;
when(cursorMock.getString(columnIndex++)).thenReturn(name);
Track track = providerUtils.createTrack(cursorMock);
Track track = contentProviderUtils.createTrack(cursorMock);
Assert.assertEquals(trackId, track.getId());
Assert.assertEquals(name, track.getName());
}
@@ -220,10 +222,10 @@ public class CustomContentProviderUtilsTest {
public void testDeleteAllTracks() {
// Insert track, points and waypoint at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
Waypoint waypoint = new Waypoint();
providerUtils.insertWaypoint(waypoint);
contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(1, tracksCursor.getCount());
@@ -232,7 +234,7 @@ public class CustomContentProviderUtilsTest {
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
// Delete all.
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
// Check whether all have been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(0, tracksCursor.getCount());
@@ -249,15 +251,15 @@ public class CustomContentProviderUtilsTest {
public void testDeleteTrack() {
// Insert three tracks, points of two tracks and way point of one track.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
providerUtils.insertTrack(track);
insertTrackWithLocations(getTrack(trackId + 1, 10));
insertTrackWithLocations(getTrack(trackId + 2, 10));
contentProviderUtils.insertTrack(track);
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 1, 10));
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 2, 10));
Waypoint waypoint = new Waypoint();
waypoint.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint);
contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
@@ -267,7 +269,7 @@ public class CustomContentProviderUtilsTest {
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
// Delete one track.
providerUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, trackId);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount());
@@ -282,10 +284,10 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testGetAllTracks() {
int initialTrackNumber = providerUtils.getAllTracks().size();
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
List<Track> allTracks = providerUtils.getAllTracks();
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
List<Track> allTracks = contentProviderUtils.getAllTracks();
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
}
@@ -296,8 +298,8 @@ public class CustomContentProviderUtilsTest {
@Test
public void testGetLastTrack() {
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
Assert.assertEquals(trackId, providerUtils.getLastTrack().getId());
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
}
/**
@@ -306,8 +308,8 @@ public class CustomContentProviderUtilsTest {
@Test
public void testGetTrack() {
long trackId = System.currentTimeMillis();
providerUtils.insertTrack(getTrack(trackId, 0));
Assert.assertNotNull(providerUtils.getTrack(trackId));
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
}
/**
@@ -316,15 +318,15 @@ public class CustomContentProviderUtilsTest {
@Test
public void testUpdateTrack() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 0);
Track track = TestDataUtil.getTrack(trackId, 0);
String nameOld = "name1";
String nameNew = "name2";
track.setName(nameOld);
providerUtils.insertTrack(track);
Assert.assertEquals(nameOld, providerUtils.getTrack(trackId).getName());
contentProviderUtils.insertTrack(track);
Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
track.setName(nameNew);
providerUtils.updateTrack(track);
Assert.assertEquals(nameNew, providerUtils.getTrack(trackId).getName());
contentProviderUtils.updateTrack(track);
Assert.assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
}
/**
@@ -333,8 +335,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testCreateContentValues_waypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
// Bottom
long startTime = 1000L;
// AverageSpeed
@@ -351,12 +352,13 @@ public class CustomContentProviderUtilsTest {
statistics.setMinElevation(1200.0);
statistics.setMaxGrade(15.0);
statistics.setMinGrade(minGrade);
statistics.setBounds(-10000, 20000, 30000, -40000);
track.setTripStatistics(statistics);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC);
waypoint.setType(WaypointType.STATISTICS);
waypoint.setTripStatistics(statistics);
Location location = new Location("test");
location.setLatitude(22);
@@ -364,7 +366,7 @@ public class CustomContentProviderUtilsTest {
location.setAccuracy((float) 1 / 100.0f);
location.setAltitude(2.5);
waypoint.setLocation(location);
providerUtils.insertWaypoint(waypoint);
contentProviderUtils.insertWaypoint(waypoint);
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
@@ -374,8 +376,6 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
Assert.assertEquals(22 * 1000000, contentValues.get(WaypointsColumns.LONGITUDE));
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
Assert.assertEquals(startTime, contentValues.get(WaypointsColumns.STARTTIME));
Assert.assertEquals(minGrade, contentValues.get(WaypointsColumns.MINGRADE));
}
/**
@@ -406,7 +406,7 @@ public class CustomContentProviderUtilsTest {
long trackId = 11L;
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
Waypoint waypoint = providerUtils.createWaypoint(cursorMock);
Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
Assert.assertEquals(id, waypoint.getId());
Assert.assertEquals(name, waypoint.getName());
Assert.assertEquals(trackId, waypoint.getTrackId());
@@ -414,50 +414,36 @@ public class CustomContentProviderUtilsTest {
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* when there is only one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_onlyOneWayPoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint1 = new Waypoint();
waypoint1.setDescription(TEST_DESC);
waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
providerUtils.insertWaypoint(waypoint1);
contentProviderUtils.insertWaypoint(waypoint1);
// Delete
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
contentProviderUtils.deleteWaypoint(context, 1);
@Override
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));
Assert.assertNull(contentProviderUtils.getWaypoint(1));
}
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* when there is more than one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_hasNextWayPoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
TripStatistics statistics = new TripStatistics();
statistics.setStartTime(1000L);
@@ -471,124 +457,79 @@ public class CustomContentProviderUtilsTest {
statistics.setMinElevation(1200.0);
statistics.setMaxGrade(15.0);
statistics.setMinGrade(-25.0);
statistics.setBounds(-10000, 20000, 30000, -40000);
track.setTripStatistics(statistics);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint1 = new Waypoint();
waypoint1.setDescription(Long.toString(trackId));
waypoint1.setDescription(MOCK_DESC);
waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setTripStatistics(statistics);
long waypoint1Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint waypoint2 = new Waypoint();
waypoint2.setDescription(Long.toString(trackId));
waypoint2.setDescription(MOCK_DESC);
waypoint2.setTrackId(trackId);
waypoint2.setType(WaypointType.STATISTICS);
waypoint2.setTripStatistics(statistics);
long waypoint2Id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
// Delete
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
@Override
public String generateWaypointDescription(TripStatistics tripStatistics) {
return CustomContentProviderUtilsTest.MOCK_DESC;
}
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
@Override
public String generateTrackDescription(Track aTrack, boolean html) {
return null;
}
};
Assert.assertNotNull(providerUtils.getWaypoint(waypoint1Id));
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
Assert.assertEquals(CustomContentProviderUtilsTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
.getDescription());
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
}
/**
* Tests the method {@link ContentProviderUtils#getFirstWaypointId(long)}.
*/
@Test
public void testGetFirstWaypointId() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint();
waypoint1.setTrackId(trackId);
long waypoint1id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint1));
Waypoint waypoint2 = new Waypoint();
waypoint2.setTrackId(trackId);
long waypoint2id = ContentUris.parseId(providerUtils.insertWaypoint(waypoint2));
Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1));
Assert.assertEquals(waypoint1id, providerUtils.getFirstWaypointId(trackId));
Assert.assertEquals(waypoint2id, providerUtils.getLastWaypoint(trackId, WaypointType.WAYPOINT).getId());
}
/**
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long, WaypointType)}.
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}.
*/
@Test
public void testGetNextWaypointNumber() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint();
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setTrackId(trackId);
Waypoint waypoint2 = new Waypoint();
waypoint2.setType(WaypointType.WAYPOINT);
waypoint2.setTrackId(trackId);
Waypoint waypoint3 = new Waypoint();
waypoint3.setType(WaypointType.STATISTICS);
waypoint3.setTrackId(trackId);
Waypoint waypoint4 = new Waypoint();
waypoint4.setType(WaypointType.WAYPOINT);
waypoint4.setTrackId(trackId);
providerUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint2);
providerUtils.insertWaypoint(waypoint3);
providerUtils.insertWaypoint(waypoint4);
contentProviderUtils.insertWaypoint(waypoint1);
contentProviderUtils.insertWaypoint(waypoint2);
contentProviderUtils.insertWaypoint(waypoint3);
contentProviderUtils.insertWaypoint(waypoint4);
Assert.assertEquals(2, providerUtils.getNextWaypointNumber(trackId, WaypointType.STATISTICS));
Assert.assertEquals(3, providerUtils.getNextWaypointNumber(trackId, WaypointType.WAYPOINT));
Assert.assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
}
/**
* Tests the method
* {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}.
* {@link ContentProviderUtils#getLastWaypoint(long)}.
*/
@Test
public void testGetLastStatisticsWaypoint() {
public void testGetLastWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Waypoint waypoint1 = new Waypoint();
waypoint1.setTrackId(trackId);
waypoint1.setType(WaypointType.STATISTICS);
waypoint1.setDescription("Desc1");
Waypoint waypoint2 = new Waypoint();
waypoint2.setTrackId(trackId);
waypoint2.setType(WaypointType.STATISTICS);
waypoint2.setDescription("Desc2");
Waypoint waypoint3 = new Waypoint();
waypoint3.setTrackId(trackId);
waypoint3.setType(WaypointType.WAYPOINT);
waypoint3.setDescription("Desc3");
providerUtils.insertWaypoint(waypoint1);
providerUtils.insertWaypoint(waypoint2);
providerUtils.insertWaypoint(waypoint3);
contentProviderUtils.insertWaypoint(waypoint1);
contentProviderUtils.insertWaypoint(waypoint2);
contentProviderUtils.insertWaypoint(waypoint3);
Assert.assertEquals("Desc2", providerUtils.getLastWaypoint(trackId, WaypointType.STATISTICS).getDescription());
Assert.assertEquals("Desc3", contentProviderUtils.getLastWaypoint(trackId).getDescription());
}
/**
@@ -598,15 +539,15 @@ public class CustomContentProviderUtilsTest {
@Test
public void testInsertAndGetWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(waypointId).getDescription());
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
@@ -615,20 +556,20 @@ public class CustomContentProviderUtilsTest {
@Test
public void testUpdateWaypoint() {
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
// Insert at first.
Waypoint waypoint = new Waypoint();
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(providerUtils.insertWaypoint(waypoint));
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
// Update
waypoint = providerUtils.getWaypoint(waypointId);
waypoint = contentProviderUtils.getWaypoint(waypointId);
waypoint.setDescription(TEST_DESC_NEW);
providerUtils.updateWaypoint(waypoint);
contentProviderUtils.updateWaypoint(waypoint);
Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(waypointId).getDescription());
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
@@ -638,13 +579,13 @@ public class CustomContentProviderUtilsTest {
public void testBulkInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
Assert.assertEquals(20, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
Assert.assertEquals(28, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
@@ -699,7 +640,7 @@ public class CustomContentProviderUtilsTest {
byte[] sensor = "Sensor state".getBytes();
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) latitude, location.getLatitude(), 0.01);
Assert.assertEquals(time, location.getTime(), 0.01);
@@ -714,37 +655,11 @@ public class CustomContentProviderUtilsTest {
public void testInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
providerUtils.insertTrackPoint(createLocation(22), trackId);
Assert.assertEquals(11, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
* Tests the method {@link ContentProviderUtils#getFirstTrackPointId(long)}.
*/
@Test
public void testGetFirstTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Assert.assertNotEquals(-1L, providerUtils.getFirstTrackPointId(trackId));
}
/**
* Tests the method {@link ContentProviderUtils#getLastTrackPointId(long)}.
*/
@Test
public void testGetLastTrackPointId() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
insertTrackWithLocations(track);
Assert.assertTrue(providerUtils.getFirstTrackPointId(trackId) < providerUtils.getLastTrackPointId(trackId));
contentProviderUtils.insertTrackPoint(TestDataUtil.createLocation(22), trackId);
Assert.assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
@@ -754,10 +669,10 @@ public class CustomContentProviderUtilsTest {
public void testGetLastValidTrackPoint() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
Track track = TestDataUtil.getTrack(trackId, 10);
insertTrackWithLocations(track);
Location lastLocation = providerUtils.getLastValidTrackPoint(trackId);
Location lastLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
checkLocation(9, lastLocation);
}
@@ -768,15 +683,15 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointCursor_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
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());
}
@@ -787,15 +702,15 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointCursor_asc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
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());
}
@@ -806,17 +721,17 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointLocationIterator_desc() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
long startTrackPointId = trackpointIds[9];
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next();
@@ -833,17 +748,17 @@ public class CustomContentProviderUtilsTest {
public void testGetTrackPointLocationIterator_asc() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track track = getTrack(trackId, 10);
providerUtils.insertTrack(track);
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(providerUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
long startTrackPointId = trackpointIds[0];
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(locationIterator.hasNext());
Location location = locationIterator.next();
@@ -854,50 +769,17 @@ public class CustomContentProviderUtilsTest {
Assert.assertFalse(locationIterator.hasNext());
}
/**
* Simulates a track which is used for testing.
*
* @param id the id of the track
* @param numPoints the location number in the track
* @return the simulated track
*/
private Track getTrack(long id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
for (int i = 0; i < numPoints; i++) {
track.addLocation(createLocation(i));
}
return track;
}
/**
* Creates a location.
*
* @param i the index to set the value of location.
* @return created location
*/
private Location createLocation(int i) {
Location loc = new Location("test");
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * ALTITUDE_INTERVAL);
return loc;
}
/**
* Checks the value of a location.
*
* @param i the index of this location which created in the method {@link 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
*/
private void checkLocation(int i, Location location) {
Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
Assert.assertEquals(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
Assert.assertEquals(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
Assert.assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
}
/**
@@ -906,7 +788,7 @@ public class CustomContentProviderUtilsTest {
* @param track track to be inserted
*/
private void insertTrackWithLocations(Track track) {
providerUtils.insertTrack(track);
providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
contentProviderUtils.insertTrack(track);
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
}
}
@@ -27,29 +27,30 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.StringUtils;
/**
* Tests for {@link DescriptionGeneratorImpl}.
* Tests for {@link DescriptionGenerator}.
*
* @author Jimmy Shih
*/
@RunWith(AndroidJUnit4.class)
public class DescriptionGeneratorImplTest {
public class DescriptionGeneratorTest {
private static final long START_TIME = 1288721514000L;
private DescriptionGeneratorImpl descriptionGenerator;
private DescriptionGenerator descriptionGenerator;
private Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
descriptionGenerator = new DescriptionGeneratorImpl(ApplicationProvider.getApplicationContext());
descriptionGenerator = new DescriptionGenerator(ApplicationProvider.getApplicationContext());
}
/**
* Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, boolean)}.
* Tests {@link DescriptionGenerator#generateTrackDescription(Track, boolean)}.
*/
@Test
public void testGenerateTrackDescription() {
@@ -68,7 +69,7 @@ public class DescriptionGeneratorImplTest {
track.setTripStatistics(stats);
track.setCategory("hiking");
String expected = //"Created by"
"<a href='https://github.com/dennisguse/opentracks'>OpenTracks</a><p>"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
+ "Name: -<br>"
+ "Activity type: hiking<br>"
+ "Description: -<br>"
@@ -91,44 +92,9 @@ public class DescriptionGeneratorImplTest {
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
/**
* Tests {@link DescriptionGeneratorImpl#generateWaypointDescription(TripStatistics)}.
*/
@Test
public void testGenerateWaypointDescription() {
Waypoint waypoint = new Waypoint();
TripStatistics stats = new TripStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setMaxElevation(550);
stats.setMinElevation(-500);
stats.setTotalElevationGain(6000);
stats.setMaxGrade(0.42);
stats.setMinGrade(0.11);
stats.setStartTime(START_TIME);
waypoint.setTripStatistics(stats);
String expected = "Total distance: 20.00 km (12.4 mi)\n"
+ "Total time: 10:00\n"
+ "Moving time: 05:00\n"
+ "Average speed: 120.00 km/h (74.6 mi/h)\n"
+ "Average moving speed: 240.00 km/h (149.1 mi/h)\n"
+ "Max speed: 360.00 km/h (223.7 mi/h)\n"
+ "Average pace: 0:30 min/km (0:48 min/mi)\n"
+ "Average moving pace: 0:15 min/km (0:24 min/mi)\n"
+ "Fastest pace: 0:10 min/km (0:16 min/mi)\n"
+ "Max elevation: 550 m (1804 ft)\n"
+ "Min elevation: -500 m (-1640 ft)\n"
+ "Elevation gain: 6000 m (19685 ft)\n"
+ "Max grade: 42 %\n"
+ "Min grade: 11 %\n"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "\n";
Assert.assertEquals(expected, descriptionGenerator.generateWaypointDescription(waypoint.getTripStatistics()));
}
/**
* Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteDistance() {
@@ -138,7 +104,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeTime(long, StringBuilder, int, String)}.
*/
@Test
public void testWriteTime() {
@@ -148,7 +114,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteSpeed() {
@@ -158,7 +124,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeElevation(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteElevation() {
@@ -168,7 +134,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}.
*/
@Test
public void testWritePace() {
@@ -178,7 +144,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)}.
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)}.
*/
@Test
public void testWriteGrade() {
@@ -188,7 +154,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with a NaN.
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with a NaN.
*/
@Test
public void testWriteGrade_nan() {
@@ -198,7 +164,7 @@ public class DescriptionGeneratorImplTest {
}
/**
* Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, int, String)} with an infinite number.
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with an infinite number.
*/
@Test
public void testWriteGrade_infinite() {
@@ -36,6 +36,8 @@ import java.util.List;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
@@ -80,10 +82,6 @@ public class SearchEngineTest {
stats.setStopTime(NOW);
}
int latitude = (int) ((HERE.getLatitude() + distance) * 1E6);
int longitude = (int) ((HERE.getLongitude() + distance) * 1E6);
stats.setBounds(latitude, longitude, latitude, longitude);
Uri uri = providerUtils.insertTrack(track);
return ContentUris.parseId(uri);
}
@@ -209,20 +207,6 @@ public class SearchEngineTest {
assertWaypointResult(descriptionWaypointId, results.get(2));
}
@Test
public void testSearchTrackDistance() {
// All results match text, but they're at difference distances from the user.
long farFarAwayId = insertTrack("aa", 0.3);
long nearId = insertTrack("ab", 0.1);
long farId = insertTrack("ac", 0.2);
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
// Distance order.
assertTrackResults(results, nearId, farId, farFarAwayId);
}
@Test
public void testSearchWaypointDistance() {
// All results match text, but they're at difference distances from the user.
@@ -0,0 +1,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;
}
}
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith;
import de.dennisguse.opentracks.TrackStubUtils;
import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -72,7 +72,7 @@ public class ChartFragmentTest {
*/
@Test
public void testFillDataPoint_sensorIncorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
@@ -96,7 +96,7 @@ public class ChartFragmentTest {
*/
@Test
public void testFillDataPoint_sensorCorrect() {
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
// No input.
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
@@ -123,17 +123,17 @@ public class ChartFragmentTest {
// By distance.
chartFragment.setChartByDistance(true);
// Resets last location and writes first location.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
// The second is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals(0.0, point[0], 0.01);
// The third location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation3.setLatitude(23);
point = fillDataPointTestHelper(sensorDataSetLocation3);
@@ -145,7 +145,7 @@ public class ChartFragmentTest {
Assert.assertEquals(distance1, point[0], 0.01);
// The fourth location is a new location, and use metric.
SensorDataSetLocation sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation4.setLatitude(24);
point = fillDataPointTestHelper(sensorDataSetLocation4);
@@ -167,12 +167,12 @@ public class ChartFragmentTest {
chartFragment.setMetricUnits(false);
// The first is a same location, just different time.
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
// The second location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setLatitude(23);
point = fillDataPointTestHelper(sensorDataSetLocation2);
@@ -186,7 +186,7 @@ public class ChartFragmentTest {
Assert.assertEquals(distance1, point[0], 0.01);
// The third location is a new location, and use imperial.
SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation3.setLatitude(24);
point = fillDataPointTestHelper(sensorDataSetLocation3);
@@ -206,11 +206,11 @@ public class ChartFragmentTest {
public void testFillDataPoint_time() {
// By time
chartFragment.setChartByDistance(false);
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[0], 0.01);
long timeSpan = 222;
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((double) timeSpan, point[0], 0.01);
@@ -221,7 +221,7 @@ public class ChartFragmentTest {
*/
@Test
public void testFillDataPoint_elevation() {
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
/*
* At first, clear old points of elevation, so give true to the second parameter.
@@ -233,7 +233,7 @@ public class ChartFragmentTest {
/*
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
*/
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = fillDataPointTestHelper(sensorDataSetLocation2);
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
@@ -251,7 +251,7 @@ public class ChartFragmentTest {
* At first, clear old points of speed, so give true to the second parameter.
* It will not be filled in to the speed buffer.
*/
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(128.5f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
@@ -260,7 +260,7 @@ public class ChartFragmentTest {
* Tests the logic when both metricUnits and reportSpeed are true.
* This location will be filled into speed buffer.
*/
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
/*
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
@@ -280,12 +280,12 @@ public class ChartFragmentTest {
chartFragment.setMetricUnits(false);
// First data point is not added to the speed buffer
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
/*
* Add a time span here to make sure the second point and the speed is valid.
@@ -306,12 +306,12 @@ public class ChartFragmentTest {
chartFragment.setReportSpeed(false);
// First data point is not added to the speed buffer
SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
/*
* Add a time span here to make sure the second point and the speed is valid.
@@ -330,7 +330,7 @@ public class ChartFragmentTest {
public void testFillDataPoint_pace_zeroSpeed() {
// Setups reportSpeed to false.
chartFragment.setReportSpeed(false);
SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
sensorDataSetLocation.setSpeed(0);
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1], 0.01);
@@ -33,10 +33,10 @@ import java.util.Locale;
import java.util.SimpleTimeZone;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.WaypointsColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
@@ -101,8 +101,6 @@ public abstract class AbstractTestFileTrackImporter {
*/
protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) {
when(contentProviderUtils.bulkInsertTrackPoint(location != null ? (Location[]) any() : (Location[]) any(), eq(1), eq(trackId))).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(trackId)).thenReturn(trackPointId);
when(contentProviderUtils.getLastTrackPointId(trackId)).thenReturn(trackPointId);
}
/**
@@ -128,7 +126,5 @@ public abstract class AbstractTestFileTrackImporter {
if (time != -1L) {
Assert.assertEquals(time, track.getTripStatistics().getStartTime());
}
Assert.assertNotSame(-1, track.getStartId());
Assert.assertNotSame(-1, track.getStopId());
}
}
@@ -0,0 +1,219 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.location.Location;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.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.InputStream;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
@@ -208,8 +208,6 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
// For the following, use StubReturn since we don't care whether they are invoked or not.
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
@@ -27,7 +27,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
@@ -15,14 +15,19 @@
*/
package de.dennisguse.opentracks.services;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests the {@link AdaptiveLocationListenerPolicy}.
*
* @author youtaol
*/
public class AdaptiveLocationListenerPolicyTest extends TestCase {
@RunWith(JUnit4.class)
public class AdaptiveLocationListenerPolicyTest {
private static final long MIN = 1000;
private static final long MAX = 3000;
@@ -31,12 +36,11 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
private static final long NEW_IDLE_TIME_NORMAL = 5000;
private static final long NEW_IDLE_TIME_SMALL = 2000;
private static final long NEW_IDLE_TIME_LESS_THAN_MIN = 500;
private AdaptiveLocationListenerPolicy adocationListenerPolicy;
private AdaptiveLocationListenerPolicy adaptiveLocationListenerPolicy;
@Override
protected void setUp() throws Exception {
super.setUp();
adocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE);
@Before
public void setUp() {
adaptiveLocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE);
}
/**
@@ -48,27 +52,27 @@ public class AdaptiveLocationListenerPolicyTest extends TestCase {
* <li>The newIdleTime is smaller than the smallest interval unit.</li>
* </ul>
*/
@Test
public void testGetDesiredPollingInterval() {
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG);
assertEquals(MAX, adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG);
Assert.assertEquals(MAX, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the
// nearest second.
assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000,
adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
Assert.assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
Assert.assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN);
assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN);
Assert.assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
}
/**
* Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance_m()}.
*/
@Test
public void testGetMinDistance() {
assertEquals(MIN_DISTANCE, adocationListenerPolicy.getMinDistance_m());
Assert.assertEquals(MIN_DISTANCE, adaptiveLocationListenerPolicy.getMinDistance_m());
}
}
@@ -44,10 +44,8 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -56,7 +54,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
*
* @author Bartlomiej Niechwiej
* <p>
* ATTENTION: This tests deletes all stored tracks in the database.
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
@@ -69,7 +67,7 @@ public class TrackRecordingServiceTest {
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils providerUtils;
private ContentProviderUtils contentProviderUtils;
private final long trackId = Math.abs(new Random().nextLong());
@@ -84,14 +82,14 @@ public class TrackRecordingServiceTest {
};
customContentProvider.attachInfo(context, null);
providerUtils = new ContentProviderUtils(context);
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().apply();
// Ensure that the database is empty before every test
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
}
@After
@@ -103,7 +101,7 @@ public class TrackRecordingServiceTest {
}
// Ensure that the database is empty after every test
providerUtils.deleteAllTracks(context);
contentProviderUtils.deleteAllTracks(context);
}
@SmallTest
@@ -123,7 +121,7 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testRecording_noTracks() throws Exception {
List<Track> tracks = providerUtils.getAllTracks();
List<Track> tracks = contentProviderUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
Intent startIntent = createStartIntent(context);
@@ -203,8 +201,8 @@ public class TrackRecordingServiceTest {
private void addTrack(Track track, boolean isRecording) {
Assert.assertTrue(track.getId() >= 0);
providerUtils.insertTrack(track);
Assert.assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
contentProviderUtils.insertTrack(track);
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
}
@@ -231,7 +229,7 @@ public class TrackRecordingServiceTest {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertEquals(-1L, waypointId);
}
@@ -244,16 +242,14 @@ public class TrackRecordingServiceTest {
insertLocation(service);
long trackId = service.getRecordingTrackId();
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
long waypointId = service.insertWaypoint(null, null, null, null);
Assert.assertNotEquals(-1L, waypointId);
Waypoint wpt = providerUtils.getWaypoint(waypointId);
Waypoint wpt = contentProviderUtils.getWaypoint(waypointId);
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
Assert.assertEquals(WaypointType.WAYPOINT, wpt.getType());
Assert.assertEquals(trackId, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
Assert.assertNull(wpt.getTripStatistics());
service.endCurrentTrack();
}
@@ -27,8 +27,7 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -219,7 +218,7 @@ public class TrackRecordingServiceTestLooper {
service.insertTrackPoint(location);
if (i % 7 == 0) {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
service.insertWaypoint(null, null, null, null);
}
}
@@ -15,7 +15,11 @@
*/
package de.dennisguse.opentracks.stats;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link TripStatistics}.
@@ -23,26 +27,17 @@ import junit.framework.TestCase;
*
* @author Rodrigo Damazio
*/
public class TripStatisticsTest extends TestCase {
@RunWith(JUnit4.class)
public class TripStatisticsTest {
private TripStatistics statistics;
@Override
protected void setUp() throws Exception {
super.setUp();
@Before
public void setUp() {
statistics = new TripStatistics();
}
public void testSetBounds() {
// This is not a trivial setter, conversion happens in it
statistics.setBounds(12345, -34567, 56789, -98765);
assertEquals(12345, statistics.getLeft());
assertEquals(-34567, statistics.getTop());
assertEquals(56789, statistics.getRight());
assertEquals(-98765, statistics.getBottom());
}
@Test
public void testMerge() {
TripStatistics statistics2 = new TripStatistics();
statistics.setStartTime(1000L); // Resulting start time
@@ -68,38 +63,32 @@ public class TripStatisticsTest extends TestCase {
statistics2.setMaxGrade(35.0); // Resulting max grade
statistics2.setMinGrade(0.0);
// Resulting bounds: -10000, 35000, 30000, -40000
statistics.setBounds(-10000, 20000, 30000, -40000);
statistics2.setBounds(-5000, 35000, 0, 20000);
statistics.merge(statistics2);
assertEquals(1000L, statistics.getStartTime());
assertEquals(4000L, statistics.getStopTime());
assertEquals(2500L, statistics.getTotalTime());
assertEquals(1300L, statistics.getMovingTime());
assertEquals(1100.0, statistics.getTotalDistance());
assertEquals(900.0, statistics.getTotalElevationGain());
assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed());
assertEquals(-10000, statistics.getLeft());
assertEquals(30000, statistics.getRight());
assertEquals(35000, statistics.getTop());
assertEquals(-40000, statistics.getBottom());
assertEquals(1200.0, statistics.getMinElevation());
assertEquals(3575.0, statistics.getMaxElevation());
assertEquals(-25.0, statistics.getMinGrade());
assertEquals(35.0, statistics.getMaxGrade());
Assert.assertEquals(1000L, statistics.getStartTime());
Assert.assertEquals(4000L, statistics.getStopTime());
Assert.assertEquals(2500L, statistics.getTotalTime());
Assert.assertEquals(1300L, statistics.getMovingTime());
Assert.assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
Assert.assertEquals(900.0, statistics.getTotalElevationGain(), 0.001);
Assert.assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed(), 0.001);
Assert.assertEquals(1200.0, statistics.getMinElevation(), 0.001);
Assert.assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
Assert.assertEquals(-25.0, statistics.getMinGrade(), 0.001);
Assert.assertEquals(35.0, statistics.getMaxGrade(), 0.001);
}
@Test
public void testGetAverageSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setTotalTime(50000); // in milliseconds
assertEquals(20.0, statistics.getAverageSpeed());
Assert.assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
}
@Test
public void testGetAverageMovingSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setMovingTime(20000); // in milliseconds
assertEquals(50.0, statistics.getAverageMovingSpeed());
Assert.assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
}
}
@@ -15,9 +15,10 @@
*/
package de.dennisguse.opentracks.util;
import android.os.Environment;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.File;
@@ -26,83 +27,83 @@ import java.io.File;
*
* @author Rodrigo Damazio
*/
public class FileUtilsTest extends TestCase {
/**
* Tests {@link FileUtils#getPath(String...)}.
*/
public void testBuildExternalDirectoryPath() {
String expectedName = Environment.getExternalStorageDirectory() + File.separator + FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator + "c";
String dirName = FileUtils.getPath("a", "b", "c");
assertEquals(expectedName, dirName);
}
@RunWith(JUnit4.class)
public class FileUtilsTest {
/**
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file is new.
*/
@Test
public void testBuildUniqueFileName_new() {
String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext");
assertEquals("Filename.ext", filename);
Assert.assertEquals("Filename.ext", filename);
}
/**
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file exists already.
*/
@Test
public void testBuildUniqueFileName_exist() {
// Expect "/default.prop" to exist on the phone/emulator
String filename = FileUtils.buildUniqueFileName(new File("/"), "default", "prop");
assertEquals("default(1).prop", filename);
Assert.assertEquals("default(1).prop", filename);
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with special characters.
* Verifies that they are sanitized.
*/
@Test
public void testSanitizeFileName() {
String name = "Swim\10ming-^across:/the/ pacific (ocean).";
String expected = "Swim_ming-^across_the_ pacific (ocean).";
assertEquals(expected, FileUtils.sanitizeFileName(name));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
@Test
public void testSanitizeFileNameWithSuffix() {
String name = "FileName.jpeg";
assertEquals(name, FileUtils.sanitizeFileName(name));
Assert.assertEquals(name, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
* Verifies that they are allowed.
*/
@Test
public void testSanitizeFileName_i18n() {
String name = "您好-привет";
String expected = "您好-привет";
assertEquals(expected, FileUtils.sanitizeFileName(name));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32 characters.
* Verifies that they are allowed.
*/
@Test
public void testSanitizeFileName_special_characters() {
String name = "$%'-_@~`!(){}^#&+,;=[] ";
String expected = "$%'-_@~`!(){}^#&+,;=[] ";
assertEquals(expected, FileUtils.sanitizeFileName(name));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped characters in a row.
* Verifies that they are collapsed into one underscore.
*/
@Test
public void testSanitizeFileName_collapse() {
String name = "hello//there";
String expected = "hello_there";
assertEquals(expected, FileUtils.sanitizeFileName(name));
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
* Tests {@link FileUtils#truncateFileName(File, String, String)}.
* Verifies the a long file name is truncated.
*/
@Test
public void testTruncateFileName() {
File directory = new File("/dir1/dir2/");
String suffix = ".gpx";
@@ -114,8 +115,8 @@ public class FileUtilsTest extends TestCase {
String truncated = FileUtils.truncateFileName(directory, nameString, suffix);
for (int i = 0; i < truncated.length(); i++) {
assertEquals('a', truncated.charAt(i));
Assert.assertEquals('a', truncated.charAt(i));
}
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
Assert.assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
}
}
+6 -8
View File
@@ -16,22 +16,22 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks"
android:installLocation="auto"
android:versionCode="3195"
android:versionName="v3.2.10"> <!-- For FDroid. -->
android:versionCode="3251"
android:versionName="v3.3.1"> <!-- For FDroid. -->
<!-- Permission to initialize services -->
<application
android:name=".Startup"
android:allowBackup="false"
android:hasFragileUserData="true"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_logo_color_24dp"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:icon="${applicationLogo}"
android:label="${applicationName}"
android:theme="@style/ThemeCustom">
<!-- Activities -->
<activity android:name=".AboutActivity" />
<activity android:name=".DeleteActivity" />
<activity android:name=".TrackDeleteActivity" />
<activity
android:name=".HelpActivity"
@@ -152,6 +152,4 @@ limitations under the License.
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
@@ -76,8 +76,8 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
if (stopRecording) {
getTrackRecordingServiceConnection().stopRecording(this, false);
}
Intent intent = IntentUtils.newIntent(this, DeleteActivity.class);
intent.putExtra(DeleteActivity.EXTRA_TRACK_IDS, trackIds);
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
startActivityForResult(intent, DELETE_REQUEST_CODE);
}
@@ -88,7 +88,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection();
/**
* Called after {@link DeleteActivity} returns its result.
* Called after {@link TrackDeleteActivity} returns its result.
*/
abstract protected void onDeleted();
}
@@ -30,7 +30,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private final long[] trackIds;
private final Context context;
private DeleteActivity deleteActivity;
private TrackDeleteActivity deleteActivity;
// true if the AsyncTask has completed
private boolean completed;
@@ -41,7 +41,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* @param trackIds the track ids to delete. To delete all, set to size 1 with
* trackIds[0] == -1L
*/
public DeleteAsyncTask(DeleteActivity deleteActivity, long[] trackIds) {
public DeleteAsyncTask(TrackDeleteActivity deleteActivity, long[] trackIds) {
this.deleteActivity = deleteActivity;
this.trackIds = trackIds;
context = deleteActivity.getApplicationContext();
@@ -53,7 +53,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
*
* @param deleteActivity the current activity, can be null
*/
public void setActivity(DeleteActivity deleteActivity) {
public void setActivity(TrackDeleteActivity deleteActivity) {
this.deleteActivity = deleteActivity;
if (completed && deleteActivity != null) {
deleteActivity.onAsyncTaskCompleted();
@@ -31,7 +31,7 @@ import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
@@ -71,12 +71,9 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor);
if (!currentMarker.isTripStatistics()) {
markerIds.add(currentMarker.getId());
if (currentMarker.getId() == markerId) {
markerIndex = markerIds.size() - 1;
}
markerIds.add(currentMarker.getId());
if (currentMarker.getId() == markerId) {
markerIndex = markerIds.size() - 1;
}
cursor.moveToNext();
@@ -26,9 +26,7 @@ import android.widget.Button;
import android.widget.EditText;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
/**
@@ -48,9 +46,6 @@ public class MarkerEditActivity extends AbstractActivity {
private Waypoint waypoint;
// UI elements
private View statisticsSection;
private EditText statisticsName;
private View waypointSection;
private EditText waypointName;
private AutoCompleteTextView waypointMarkerType;
private EditText waypointDescription;
@@ -65,10 +60,6 @@ public class MarkerEditActivity extends AbstractActivity {
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
// 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);
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);
@@ -95,7 +86,7 @@ public class MarkerEditActivity extends AbstractActivity {
@Override
protected void onStop() {
super.onStop();
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@Override
@@ -124,9 +115,7 @@ public class MarkerEditActivity extends AbstractActivity {
});
if (newMarker) {
statisticsSection.setVisibility(View.GONE);
waypointSection.setVisibility(View.VISIBLE);
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
if (nextWaypointNumber == -1) {
nextWaypointNumber = 0;
}
@@ -141,16 +130,9 @@ public class MarkerEditActivity extends AbstractActivity {
finish();
return;
}
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
statisticsSection.setVisibility(statistics ? View.VISIBLE : View.GONE);
waypointSection.setVisibility(statistics ? View.GONE : View.VISIBLE);
if (statistics) {
statisticsName.setText(waypoint.getName());
} else {
waypointName.setText(waypoint.getName());
waypointMarkerType.setText(waypoint.getCategory());
waypointDescription.setText(waypoint.getDescription());
}
waypointName.setText(waypoint.getName());
waypointMarkerType.setText(waypoint.getCategory());
waypointDescription.setText(waypoint.getDescription());
}
}
@@ -158,29 +140,21 @@ public class MarkerEditActivity extends AbstractActivity {
* Adds a marker.
*/
private void addMarker() {
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(
WaypointType.WAYPOINT,
false,
trackRecordingServiceConnection.addMarker(this,
waypointName.getText().toString(),
waypointMarkerType.getText().toString(),
waypointDescription.getText().toString(),
null,
null);
trackRecordingServiceConnection.addMarker(this, waypointCreationRequest);
}
/**
* Saves a marker.
*/
private void saveMarker() {
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
if (statistics) {
waypoint.setName(statisticsName.getText().toString());
} else {
waypoint.setName(waypointName.getText().toString());
waypoint.setCategory(waypointMarkerType.getText().toString());
waypoint.setDescription(waypointDescription.getText().toString());
}
waypoint.setName(waypointName.getText().toString());
waypoint.setCategory(waypointMarkerType.getText().toString());
waypoint.setDescription(waypointDescription.getText().toString());
new ContentProviderUtils(this).updateWaypoint(waypoint);
}
}
@@ -38,8 +38,8 @@ import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.WaypointsColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.util.ActivityUtils;
@@ -126,17 +126,14 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null;
final long trackFirstWaypointId = trackId != -1 ? contentProviderUtils.getFirstWaypointId(trackId) : -1;
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
listView = findViewById(R.id.marker_list);
listView.setEmptyView(findViewById(R.id.marker_list_empty));
@@ -181,8 +178,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
if (track != null) {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + "!=?",
new String[]{String.valueOf(track.getId()), String.valueOf(trackFirstWaypointId)}, null);
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(track.getId())}, null);
} else {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.STARTTIME + " IS NULL", null, null);
@@ -44,8 +44,8 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.SearchEngine;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
@@ -245,7 +245,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@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;
}
}
}
@@ -23,14 +23,13 @@ import android.content.Intent;
import android.os.Bundle;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.R;
/**
* An activity for delete tracks.
*
* @author Jimmy Shih
*/
public class DeleteActivity extends Activity {
public class TrackDeleteActivity extends Activity {
public static final String EXTRA_TRACK_IDS = "track_ids";
@@ -31,20 +31,21 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
import de.dennisguse.opentracks.fragments.ChartTimeFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
@@ -52,7 +53,6 @@ import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.StatsFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -86,7 +86,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackDataHub trackDataHub;
private TabHost tabHost;
private ViewPager pager;
private TrackController trackController;
// From intent
@@ -106,12 +106,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
trackController.update(isRecording(), recordingTrackPaused);
if (hasPhoto && photoUri != null) {
hasPhoto = false;
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString());
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, waypointCreationRequest);
if (markerId != -1L) {
//TODO: Make configurable.
FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri);
}
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, null, null, null, photoUri.toString());
}
}
});
@@ -202,28 +197,46 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
trackDataHub = TrackDataHub.newInstance(this);
tabHost = findViewById(R.id.tackdetail_tabhost);
tabHost.setup();
FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
@Override
public int getCount() {
return 3;
}
ViewPager viewPager = findViewById(R.id.pager);
TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager);
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
TabSpec 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);
@NonNull
@Override
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) {
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
}
// Set the background after all three tabs are added
for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) {
tabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.tab_indicator);
pager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
}
trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener);
@@ -291,14 +304,15 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop();
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
if (photoUri != null) {
outState.putParcelable(PHOTO_URI_KEY, photoUri);
}
@@ -364,10 +378,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
startActivity(intent);
return true;
case R.id.track_detail_insert_photo:
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
return false;
}
createWaypointWithPicture();
return true;
case R.id.track_detail_menu_show_on_map:
@@ -31,7 +31,7 @@ import android.widget.Spinner;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -175,7 +175,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
@Override
protected void onStop() {
super.onStop();
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@Override
@@ -51,7 +51,7 @@ import androidx.loader.content.Loader;
import java.util.Locale;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.ITrackRecordingService;
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)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
trackRecordingServiceConnection.startAndBind();
trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
}
}
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
@@ -344,7 +344,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(this);
}
@Override
@@ -406,14 +406,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Invoke trackRecordingService
if (startGps) {
trackRecordingServiceConnection.startAndBind();
trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run();
} else {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) {
trackRecordingService.stopGps();
}
trackRecordingServiceConnection.unbindAndStop();
trackRecordingServiceConnection.unbindAndStop(this);
}
// Update menu after starting or stopping gps
@@ -498,7 +498,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
*/
private void startRecording() {
startNewRecording = true;
trackRecordingServiceConnection.startAndBind();
trackRecordingServiceConnection.startAndBind(this);
/*
* If the binding has happened, then invoke the callback to start a new recording.
@@ -41,7 +41,7 @@ import java.util.List;
import de.dennisguse.opentracks.MarkerDetailActivity;
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.util.IntentUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
@@ -555,7 +555,7 @@ public class ChartView extends View {
canvas.save();
float x = getX(getWaypointXValue(waypoint));
canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
canvas.translate(x - (float) (markerWidth * WAYPOINT_X_ANCHOR), topBorder + spacer);
canvas.translate(x - (markerWidth * WAYPOINT_X_ANCHOR), topBorder + spacer);
waypointMarker.draw(canvas);
canvas.restore();
@@ -35,7 +35,12 @@ import java.util.NoSuchElementException;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.android.ContentResolverWrapper;
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.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.FileUtils;
@@ -51,11 +56,6 @@ public class ContentProviderUtils {
private static final String TAG = ContentProviderUtils.class.getSimpleName();
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.
*/
@@ -64,7 +64,12 @@ public class ContentProviderUtils {
/**
* 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 int defaultCursorBatchSize = 2000;
@@ -112,10 +117,6 @@ public class ContentProviderUtils {
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME);
int minLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLAT);
int maxLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLAT);
int minLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLON);
int maxLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLON);
int maxSpeedIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED);
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
@@ -138,12 +139,6 @@ public class ContentProviderUtils {
if (!cursor.isNull(categoryIndex)) {
track.setCategory(cursor.getString(categoryIndex));
}
if (!cursor.isNull(startIdIndex)) {
track.setStartId(cursor.getLong(startIdIndex));
}
if (!cursor.isNull(stopIdIndex)) {
track.setStopId(cursor.getLong(stopIdIndex));
}
if (!cursor.isNull(startTimeIndex)) {
tripStatistics.setStartTime(cursor.getLong(startTimeIndex));
}
@@ -162,13 +157,6 @@ public class ContentProviderUtils {
if (!cursor.isNull(movingTimeIndex)) {
tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex));
}
if (!cursor.isNull(minLatIndex) && !cursor.isNull(maxLatIndex) && !cursor.isNull(minLonIndex) && !cursor.isNull(maxLonIndex)) {
int bottom = cursor.getInt(minLatIndex);
int top = cursor.getInt(maxLatIndex);
int left = cursor.getInt(minLonIndex);
int right = cursor.getInt(maxLonIndex);
tripStatistics.setBounds(left, top, right, bottom);
}
if (!cursor.isNull(maxSpeedIndex)) {
tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex));
}
@@ -202,7 +190,7 @@ public class ContentProviderUtils {
// Delete tracks last since it triggers a database vaccum call
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
File dir = FileUtils.getPhotoDir();
File dir = FileUtils.getPhotoDir(context);
deleteDirectoryRecurse(context, dir);
}
@@ -220,23 +208,19 @@ public class ContentProviderUtils {
}
/**
* Deletes track points and waypoints of a track. Assumes
* {@link TracksColumns#STARTID}, {@link TracksColumns#STOPID}, and
* {@link TracksColumns#NUMPOINTS} will be updated by the caller.
* Deletes track points and waypoints of a track.
* Assumes {@link TracksColumns#NUMPOINTS} will be updated by the caller.
*
* @param trackId the track id
*/
private void deleteTrackPointsAndWaypoints(Context context, long trackId) {
Track track = getTrack(trackId);
if (track != null) {
String where = TrackPointsColumns._ID + ">=? AND " + TrackPointsColumns._ID + "<=?";
String[] selectionArgs = new String[]{
Long.toString(track.getStartId()), Long.toString(track.getStopId())};
contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs);
}
String where = TrackPointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?",
new String[]{Long.toString(trackId)});
deleteDirectoryRecurse(context, FileUtils.getPhotoDir(trackId));
deleteDirectoryRecurse(context, FileUtils.getPhotoDir(context, trackId));
}
/**
@@ -245,14 +229,11 @@ public class ContentProviderUtils {
* @param dir the directory
*/
private void deleteDirectoryRecurse(Context context, File dir) {
if (FileUtils.isDirectory(dir)) {
if (dir.exists() && dir.isDirectory()) {
for (File child : dir.listFiles()) {
deleteDirectoryRecurse(context, child);
}
}
if (dir.exists()) {
dir.delete();
FileUtils.updateMediaScanner(context, Uri.fromFile(dir));
}
}
@@ -350,18 +331,12 @@ public class ContentProviderUtils {
values.put(TracksColumns.NAME, track.getName());
values.put(TracksColumns.DESCRIPTION, track.getDescription());
values.put(TracksColumns.CATEGORY, track.getCategory());
values.put(TracksColumns.STARTID, track.getStartId());
values.put(TracksColumns.STOPID, track.getStopId());
values.put(TracksColumns.STARTTIME, tripStatistics.getStartTime());
values.put(TracksColumns.STOPTIME, tripStatistics.getStopTime());
values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints());
values.put(TracksColumns.TOTALDISTANCE, tripStatistics.getTotalDistance());
values.put(TracksColumns.TOTALTIME, tripStatistics.getTotalTime());
values.put(TracksColumns.MOVINGTIME, tripStatistics.getMovingTime());
values.put(TracksColumns.MINLAT, tripStatistics.getBottom());
values.put(TracksColumns.MAXLAT, tripStatistics.getTop());
values.put(TracksColumns.MINLON, tripStatistics.getLeft());
values.put(TracksColumns.MAXLON, tripStatistics.getRight());
values.put(TracksColumns.AVGSPEED, tripStatistics.getAverageSpeed());
values.put(TracksColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed());
values.put(TracksColumns.MAXSPEED, tripStatistics.getMaxSpeed());
@@ -399,12 +374,8 @@ public class ContentProviderUtils {
int categoryIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY);
int iconIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON);
int trackIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID);
int typeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TYPE);
int lengthIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH);
int durationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION);
int startTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTTIME);
int startIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTID);
int stopIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STOPID);
int longitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE);
int latitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE);
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
@@ -412,15 +383,6 @@ public class ContentProviderUtils {
int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY);
int speedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED);
int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING);
int totalDistanceIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALDISTANCE);
int totalTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALTIME);
int movingTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MOVINGTIME);
int maxSpeedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXSPEED);
int minElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINELEVATION);
int maxElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXELEVATION);
int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN);
int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE);
int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE);
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
Waypoint waypoint = new Waypoint();
@@ -443,21 +405,12 @@ public class ContentProviderUtils {
if (!cursor.isNull(trackIdIndex)) {
waypoint.setTrackId(cursor.getLong(trackIdIndex));
}
if (!cursor.isNull(typeIndex)) {
waypoint.setType(WaypointType.values()[cursor.getInt(typeIndex)]);
}
if (!cursor.isNull(lengthIndex)) {
waypoint.setLength(cursor.getFloat(lengthIndex));
}
if (!cursor.isNull(durationIndex)) {
waypoint.setDuration(cursor.getLong(durationIndex));
}
if (!cursor.isNull(startIdIndex)) {
waypoint.setStartId(cursor.getLong(startIdIndex));
}
if (!cursor.isNull(stopIdIndex)) {
waypoint.setStopId(cursor.getLong(stopIdIndex));
}
Location location = new Location("");
if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) {
@@ -481,52 +434,6 @@ public class ContentProviderUtils {
}
waypoint.setLocation(location);
TripStatistics tripStatistics = new TripStatistics();
boolean hasTripStatistics = false;
if (!cursor.isNull(startTimeIndex)) {
tripStatistics.setStartTime(cursor.getLong(startTimeIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(totalDistanceIndex)) {
tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(totalTimeIndex)) {
tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(movingTimeIndex)) {
tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(maxSpeedIndex)) {
tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(minElevationIndex)) {
tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(maxElevationIndex)) {
tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(elevationGainIndex)) {
tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(minGradeIndex)) {
tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(maxGradeIndex)) {
tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex));
hasTripStatistics = true;
}
if (hasTripStatistics) {
waypoint.setTripStatistics(tripStatistics);
}
if (!cursor.isNull(photoUrlIndex)) {
waypoint.setPhotoUrl(cursor.getString(photoUrlIndex));
}
@@ -538,33 +445,17 @@ public class ContentProviderUtils {
* If deleting a statistics waypoint, this will also correct the next statistics waypoint after the deleted one to reflect the deletion.
* The generator is used to update the next statistics waypoint.
*
* @param waypointId the waypoint id
* @param descriptionGenerator the description generator. Can be null for waypoint marker
* @param waypointId the waypoint id
*/
public void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator) {
public void deleteWaypoint(Context context, long 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()) {
Uri uri = waypoint.getPhotoURI();
File file = new File(uri.getPath());
if (file.exists()) {
File parent = file.getParentFile();
file.delete();
FileUtils.updateMediaScanner(context, uri);
if (parent.listFiles().length == 0) {
parent.delete();
}
@@ -574,36 +465,16 @@ public class ContentProviderUtils {
}
/**
* Gets the first waypoint id for a track.
* The first waypoint is special as it contains the stats for the track.
* Returns -1L if it doesn't exist.
* Gets the last waypoint for a type. Returns null 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.
*
* @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) {
return null;
}
String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId), Integer.toString(waypointType.ordinal())};
String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
try (Cursor cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1)) {
if (cursor != null && cursor.moveToFirst()) {
@@ -617,21 +488,18 @@ public class ContentProviderUtils {
* Gets the next waypoint number for a type.
* Returns -1 if not able to get the next waypoint number.
*
* @param trackId the track id
* @param waypointType the waypoint type
* @param trackId the track id
*/
public int getNextWaypointNumber(long trackId, WaypointType waypointType) {
public int getNextWaypointNumber(long trackId) {
if (trackId < 0) {
return -1;
}
String[] projection = {WaypointsColumns._ID};
String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId), Integer.toString(waypointType.ordinal())};
String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) {
if (cursor != null) {
int count = 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 cursor.getCount();
}
}
return -1;
@@ -695,6 +563,19 @@ public class ContentProviderUtils {
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.
*
@@ -713,8 +594,7 @@ public class ContentProviderUtils {
cursor.moveToFirst();
int count = cursor.getInt(0);
cursor.close();
// not count the first waypoint
return count > 0 ? count - 1 : 0;
return count;
}
/**
@@ -751,11 +631,8 @@ public class ContentProviderUtils {
values.put(WaypointsColumns.CATEGORY, waypoint.getCategory());
values.put(WaypointsColumns.ICON, waypoint.getIcon());
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId());
values.put(WaypointsColumns.TYPE, waypoint.getType().ordinal());
values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
values.put(WaypointsColumns.STARTID, waypoint.getStartId());
values.put(WaypointsColumns.STOPID, waypoint.getStopId());
Location location = waypoint.getLocation();
if (location != null) {
@@ -776,38 +653,10 @@ public class ContentProviderUtils {
}
}
TripStatistics tripStatistics = waypoint.getTripStatistics();
if (tripStatistics != null) {
values.put(WaypointsColumns.STARTTIME, tripStatistics.getStartTime());
values.put(WaypointsColumns.TOTALDISTANCE, tripStatistics.getTotalDistance());
values.put(WaypointsColumns.TOTALTIME, tripStatistics.getTotalTime());
values.put(WaypointsColumns.MOVINGTIME, tripStatistics.getMovingTime());
values.put(WaypointsColumns.AVGSPEED, tripStatistics.getAverageSpeed());
values.put(WaypointsColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed());
values.put(WaypointsColumns.MAXSPEED, tripStatistics.getMaxSpeed());
values.put(WaypointsColumns.MINELEVATION, tripStatistics.getMinElevation());
values.put(WaypointsColumns.MAXELEVATION, tripStatistics.getMaxElevation());
values.put(WaypointsColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain());
values.put(WaypointsColumns.MINGRADE, tripStatistics.getMinGrade());
values.put(WaypointsColumns.MAXGRADE, tripStatistics.getMaxGrade());
}
values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl());
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.
*
@@ -853,7 +702,7 @@ public class ContentProviderUtils {
* @param cursor the cursor pointing to the location
*/
public Location createTrackPoint(Cursor cursor) {
Location location = new SensorDataSetLocation("");
Location location = new TrackPoint("");
fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location);
return location;
}
@@ -864,6 +713,7 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
@Deprecated
public long getFirstTrackPointId(long trackId) {
if (trackId < 0) {
return -1L;
@@ -886,6 +736,7 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
@Deprecated
public long getLastTrackPointId(long trackId) {
if (trackId < 0) {
return -1L;
@@ -914,8 +765,8 @@ public class ContentProviderUtils {
return -1L;
}
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID
+ ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID
+ "=? AND " + TrackPointsColumns.TIME + "=?)";
+ ") from " + TrackPointsColumns.TABLE_NAME
+ " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId), Long.toString(location.getTime())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
@@ -925,29 +776,13 @@ public class ContentProviderUtils {
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.
* Returns null if it doesn't exist.
*
* @param trackId the track id
*/
@Deprecated
public Location getLastValidTrackPoint(long trackId) {
if (trackId < 0) {
return null;
@@ -1117,12 +952,7 @@ public class ContentProviderUtils {
values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 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();
if (time == 0) {
time = System.currentTimeMillis();
}
values.put(TrackPointsColumns.TIME, time);
values.put(TrackPointsColumns.TIME, location.getTime());
if (location.hasAltitude()) {
values.put(TrackPointsColumns.ALTITUDE, location.getAltitude());
}
@@ -1137,8 +967,8 @@ public class ContentProviderUtils {
}
//SensorData
if (location instanceof SensorDataSetLocation) {
SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) location;
if (location instanceof TrackPoint) {
TrackPoint sensorDataSetLocation = (TrackPoint) location;
SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet();
if (sensorDataSet != null && sensorDataSet.hasHeartRate()) {
values.put(TrackPointsColumns.SENSOR_HEARTRATE, sensorDataSetLocation.getSensorDataSet().getHeartRate());
@@ -1184,8 +1014,8 @@ public class ContentProviderUtils {
if (!cursor.isNull(indexes.bearingIndex)) {
location.setBearing(cursor.getFloat(indexes.bearingIndex));
}
if (location instanceof SensorDataSetLocation) {
SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) location;
if (location instanceof TrackPoint) {
TrackPoint sensorDataSetLocation = (TrackPoint) location;
float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex);
float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex);
@@ -1256,4 +1086,4 @@ public class ContentProviderUtils {
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
}
}
}
}
@@ -34,6 +34,10 @@ import android.util.Log;
import androidx.annotation.NonNull;
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.
*
@@ -56,11 +60,12 @@ public abstract class CustomContentProvider extends ContentProvider {
public CustomContentProvider() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_BY_TRACKID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_BY_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
}
@Override
@@ -128,15 +133,17 @@ public abstract class CustomContentProvider extends ContentProvider {
switch (getUrlType(url)) {
case TRACKPOINTS:
return TrackPointsColumns.CONTENT_TYPE;
case TRACKPOINTS_ID:
case TRACKPOINTS_BY_ID:
return TrackPointsColumns.CONTENT_ITEMTYPE;
case TRACKPOINTS_BY_TRACKID:
return TrackPointsColumns.CONTENT_ITEMTYPE;
case TRACKS:
return TracksColumns.CONTENT_TYPE;
case TRACKS_ID:
case TRACKS_BY_ID:
return TracksColumns.CONTENT_ITEMTYPE;
case WAYPOINTS:
return WaypointsColumns.CONTENT_TYPE;
case WAYPOINTS_ID:
case WAYPOINTS_BY_ID:
return WaypointsColumns.CONTENT_ITEMTYPE;
default:
throw new IllegalArgumentException("Unknown URL " + url);
@@ -192,25 +199,29 @@ public abstract class CustomContentProvider extends ContentProvider {
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
break;
case TRACKPOINTS_ID:
case TRACKPOINTS_BY_ID:
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + url.getPathSegments().get(1));
break;
case TRACKPOINTS_BY_TRACKID:
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + "=" + url.getPathSegments().get(1));
break;
case TRACKS:
queryBuilder.setTables(TracksColumns.TABLE_NAME);
sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
break;
case TRACKS_ID:
case TRACKS_BY_ID:
queryBuilder.setTables(TracksColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
queryBuilder.appendWhere(TracksColumns._ID + "=" + url.getPathSegments().get(1));
break;
case WAYPOINTS:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
sortOrder = sort != null ? sort : WaypointsColumns.DEFAULT_SORT_ORDER;
break;
case WAYPOINTS_ID:
case WAYPOINTS_BY_ID:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + url.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown url " + url);
@@ -222,6 +233,7 @@ public abstract class CustomContentProvider extends ContentProvider {
@Override
public int update(@NonNull Uri url, ContentValues values, String where, String[] selectionArgs) {
// TODO Use SQLiteQueryBuilder
String table;
String whereClause;
switch (getUrlType(url)) {
@@ -229,7 +241,7 @@ public abstract class CustomContentProvider extends ContentProvider {
table = TrackPointsColumns.TABLE_NAME;
whereClause = where;
break;
case TRACKPOINTS_ID:
case TRACKPOINTS_BY_ID:
table = TrackPointsColumns.TABLE_NAME;
whereClause = TrackPointsColumns._ID + "=" + url.getPathSegments().get(1);
if (!TextUtils.isEmpty(where)) {
@@ -240,7 +252,7 @@ public abstract class CustomContentProvider extends ContentProvider {
table = TracksColumns.TABLE_NAME;
whereClause = where;
break;
case TRACKS_ID:
case TRACKS_BY_ID:
table = TracksColumns.TABLE_NAME;
whereClause = TracksColumns._ID + "=" + url.getPathSegments().get(1);
if (!TextUtils.isEmpty(where)) {
@@ -251,7 +263,7 @@ public abstract class CustomContentProvider extends ContentProvider {
table = WaypointsColumns.TABLE_NAME;
whereClause = where;
break;
case WAYPOINTS_ID:
case WAYPOINTS_BY_ID:
table = WaypointsColumns.TABLE_NAME;
whereClause = WaypointsColumns._ID + "=" + url.getPathSegments().get(1);
if (!TextUtils.isEmpty(where)) {
@@ -330,8 +342,7 @@ public abstract class CustomContentProvider extends ContentProvider {
*/
private Uri insertTrack(Uri url, ContentValues contentValues) {
boolean hasStartTime = contentValues.containsKey(TracksColumns.STARTTIME);
boolean hasStartId = contentValues.containsKey(TracksColumns.STARTID);
if (!hasStartTime || !hasStartId) {
if (!hasStartTime) {
throw new IllegalArgumentException("Both start time and start id values are required.");
}
long rowId = db.insert(TracksColumns.TABLE_NAME, TracksColumns._ID, contentValues);
@@ -362,7 +373,13 @@ public abstract class CustomContentProvider extends ContentProvider {
*/
@VisibleForTesting
enum UrlType {
TRACKPOINTS, TRACKPOINTS_ID, TRACKS, TRACKS_ID, WAYPOINTS, WAYPOINTS_ID
TRACKPOINTS,
TRACKPOINTS_BY_ID,
TRACKPOINTS_BY_TRACKID,
TRACKS,
TRACKS_BY_ID,
WAYPOINTS,
WAYPOINTS_BY_ID
}
/**
@@ -25,6 +25,10 @@ import android.util.Log;
import java.util.EnumSet;
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}.
* 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
* 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
* 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.content.data.Track;
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.
@@ -30,12 +51,196 @@ public interface DescriptionGenerator {
* @param track the track
* @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.
* Writes a string to a string builder.
*
* @param tripStatistics the trip statistics
* @param text the string
* @param builder the string builder
* @param resId the resource id containing one string placeholder
* @param lineBreak the line break
*/
String generateWaypointDescription(TripStatistics tripStatistics);
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);
}
}
@@ -3,8 +3,10 @@ package de.dennisguse.opentracks.content;
import android.location.Location;
import android.location.LocationManager;
import de.dennisguse.opentracks.content.data.TrackPoint;
/**
* Creates a new {@link SensorDataSetLocation}.
* Creates a new {@link TrackPoint}.
* An implementation can create new instances or reuse existing instances for optimization.
*/
public class LocationFactory {
@@ -15,6 +17,6 @@ public class LocationFactory {
public static LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory();
public Location createLocation() {
return new SensorDataSetLocation(LocationManager.GPS_PROVIDER);
return new TrackPoint(LocationManager.GPS_PROVIDER);
}
}
@@ -28,6 +28,10 @@ import java.util.Locale;
import java.util.SortedSet;
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.util.LocationUtils;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -243,7 +247,6 @@ public class SearchEngine {
score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory());
TripStatistics statistics = track.getTripStatistics();
score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude());
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
score *= getTimeBoost(query, meanTimestamp);
@@ -21,6 +21,8 @@ import java.io.IOException;
import java.util.Arrays;
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.exporter.TrackExporter;
@@ -79,7 +81,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
}
builder.deleteCharAt(builder.lastIndexOf(TRACKID_DELIMITER));
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TracksColumns.TABLE_NAME + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
String mime = getTypeMime(uri);
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
@@ -30,6 +30,8 @@ import java.util.EnumSet;
import java.util.Set;
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.PreferencesUtils;
@@ -18,6 +18,9 @@ package de.dennisguse.opentracks.content;
import android.location.Location;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
/**
* Listener for track data changes.
*
@@ -1,84 +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);
@Deprecated // TODO Do not create per
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.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.data;
import android.location.Location;
@@ -35,8 +35,6 @@ public class Track {
private String name = "";
private String description = "";
private String category = "";
private long startId = -1L;
private long stopId = -1L;
/*
* The number of location points (present even if the points themselves are not loaded)
@@ -84,22 +82,6 @@ public class Track {
this.category = category;
}
public long getStartId() {
return startId;
}
public void setStartId(long startId) {
this.startId = startId;
}
public long getStopId() {
return stopId;
}
public void setStopId(long stopId) {
this.stopId = stopId;
}
public int getNumberOfPoints() {
return numberOfPoints;
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.data;
import android.location.Location;
@@ -24,16 +24,16 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
*
* @author Sandor Dornbush
*/
public class SensorDataSetLocation extends Location {
public class TrackPoint extends Location {
private SensorDataSet sensorDataSet = null;
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
public TrackPoint(Location location, SensorDataSet sensorDataSet) {
super(location);
this.sensorDataSet = sensorDataSet;
}
public SensorDataSetLocation(String provider) {
public TrackPoint(String provider) {
super(provider);
}
@@ -14,11 +14,13 @@
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.data;
import android.net.Uri;
import android.provider.BaseColumns;
import de.dennisguse.opentracks.content.ContentProviderUtils;
/**
* Constants for the track points table.
*
@@ -27,7 +29,7 @@ import android.provider.BaseColumns;
public interface TrackPointsColumns extends BaseColumns {
String TABLE_NAME = "trackpoints";
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/trackpoints");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
String DEFAULT_SORT_ORDER = "_id";
@@ -14,11 +14,13 @@
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.data;
import android.net.Uri;
import android.provider.BaseColumns;
import de.dennisguse.opentracks.content.ContentProviderUtils;
/**
* Constants for the tracks table.
*
@@ -27,7 +29,7 @@ import android.provider.BaseColumns;
public interface TracksColumns extends BaseColumns {
String TABLE_NAME = "tracks";
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/tracks");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
String DEFAULT_SORT_ORDER = "_id";
@@ -36,22 +38,28 @@ public interface TracksColumns extends BaseColumns {
String NAME = "name"; // track name
String DESCRIPTION = "description"; // track description
String CATEGORY = "category"; // track activity type
String STARTID = "startid"; // first track point id
String STOPID = "stopid"; // last track point id
@Deprecated
String STARTID = "startid"; // first track point id // TODO UNUSED
@Deprecated
String STOPID = "stopid"; // last track point id // TODO UNUSED
String STARTTIME = "starttime"; // track start time
String STOPTIME = "stoptime"; // track stop time
String NUMPOINTS = "numpoints"; // number of track points
String TOTALDISTANCE = "totaldistance"; // total distance
String TOTALTIME = "totaltime"; // total time
String MOVINGTIME = "movingtime"; // moving time
String MINLAT = "minlat"; // minimum latitude
String MAXLAT = "maxlat"; // maximum latitude
String MINLON = "minlon"; // minimum longitude
String MAXLON = "maxlon"; // maximum longitude
String AVGSPEED = "avgspeed"; // average speed
// average moving speed
String AVGMOVINGSPEED = "avgmovingspeed";
@Deprecated
String MINLAT = "minlat"; // minimum latitude // TODO UNUSED
@Deprecated
String MAXLAT = "maxlat"; // maximum latitude // TODO UNUSED
@Deprecated
String MINLON = "minlon"; // minimum longitude //TODO UNUSED
@Deprecated
String MAXLON = "maxlon"; // maximum longitude //TODO UNUSED
String AVGSPEED = "avgspeed"; // average speed
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
String MAXSPEED = "maxspeed"; // maximum speed
String MINELEVATION = "minelevation"; // minimum elevation
String MAXELEVATION = "maxelevation"; // maximum elevation
@@ -14,13 +14,11 @@
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.data;
import android.location.Location;
import android.net.Uri;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* A waypoint.
*
@@ -35,31 +33,23 @@ public final class Waypoint {
private String category = "";
private String icon = "";
private long trackId = -1L;
private WaypointType type = WaypointType.WAYPOINT;
private double length = 0.0;
private long duration = 0;
private long startId = -1L;
private long stopId = -1L;
private Location location = null;
private TripStatistics tripStatistics = null;
private String photoUrl = "";
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, Location location, String photoUrl) {
this.name = name;
this.description = description;
this.category = category;
this.icon = icon;
this.trackId = trackId;
this.type = type;
this.length = length;
this.duration = duration;
this.startId = startId;
this.stopId = stopId;
this.location = location;
this.tripStatistics = tripStatistics;
this.photoUrl = photoUrl;
}
@@ -111,14 +101,6 @@ public final class Waypoint {
this.trackId = trackId;
}
public WaypointType getType() {
return type;
}
public void setType(WaypointType type) {
this.type = type;
}
public double getLength() {
return length;
}
@@ -135,22 +117,6 @@ public final class Waypoint {
this.duration = duration;
}
public long getStartId() {
return startId;
}
public void setStartId(long startId) {
this.startId = startId;
}
public long getStopId() {
return stopId;
}
public void setStopId(long stopId) {
this.stopId = stopId;
}
public Location getLocation() {
return location;
}
@@ -159,18 +125,6 @@ public final class Waypoint {
this.location = location;
}
public boolean isTripStatistics() {
return tripStatistics != null;
}
public TripStatistics getTripStatistics() {
return tripStatistics;
}
public void setTripStatistics(TripStatistics tripStatistics) {
this.tripStatistics = tripStatistics;
}
public String getPhotoUrl() {
return photoUrl;
}
@@ -186,9 +140,4 @@ public final class Waypoint {
public boolean hasPhoto() {
return photoUrl != null && !"".equals(photoUrl);
}
public enum WaypointType {
WAYPOINT,
STATISTICS //Always the first waypoint; only one can exist.
}
}
@@ -14,11 +14,13 @@
* the License.
*/
package de.dennisguse.opentracks.content;
package de.dennisguse.opentracks.content.data;
import android.net.Uri;
import android.provider.BaseColumns;
import de.dennisguse.opentracks.content.ContentProviderUtils;
/**
* Constants for waypoints table.
*
@@ -27,7 +29,7 @@ import android.provider.BaseColumns;
public interface WaypointsColumns extends BaseColumns {
String TABLE_NAME = "waypoints";
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/waypoints");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
String DEFAULT_SORT_ORDER = "_id";
@@ -38,12 +40,16 @@ public interface WaypointsColumns extends BaseColumns {
String CATEGORY = "category"; // waypoint category
String ICON = "icon"; // waypoint icon
String TRACKID = "trackid"; // track id
String TYPE = "type"; // type
@Deprecated
String TYPE = "type"; // type // TODO UNUSED
String LENGTH = "length"; // length of the track (without smoothing)
String DURATION = "duration"; // total duration of the track (not from last waypoint)
String STARTTIME = "starttime"; // start time of the trip statistics
String STARTID = "startid"; // start track point id
String STOPID = "stopid"; // stop track point id
@Deprecated
String STARTID = "startid"; // start track point id // TODO UNUSED
@Deprecated
String STOPID = "stopid"; // stop track point id // TODO UNUSED
String LONGITUDE = "longitude"; // longitude
String LATITUDE = "latitude"; // latitude
@@ -53,17 +59,29 @@ public interface WaypointsColumns extends BaseColumns {
String SPEED = "speed"; // speed
String BEARING = "bearing"; // bearing
@Deprecated
String TOTALDISTANCE = "totaldistance"; // total distance
@Deprecated
String TOTALTIME = "totaltime"; // total time
@Deprecated
String MOVINGTIME = "movingtime"; // moving time
@Deprecated
String AVGSPEED = "avgspeed"; // average speed
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
String MAXSPEED = "maxspeed"; // max speed
String MINELEVATION = "minelevation"; // min elevation
String MAXELEVATION = "maxelevation"; // max elevation
String ELEVATIONGAIN = "elevationgain"; // elevation gain
String MINGRADE = "mingrade"; // min grade
String MAXGRADE = "maxgrade"; // max grade
@Deprecated
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed // TODO UNUSED
@Deprecated
String MAXSPEED = "maxspeed"; // max speed // TODO UNUSED
@Deprecated
String MINELEVATION = "minelevation"; // min elevation // TODO UNUSED
@Deprecated
String MAXELEVATION = "maxelevation"; // max elevation // TODO UNUSED
@Deprecated
String ELEVATIONGAIN = "elevationgain"; // elevation gain // TODO UNUSED
@Deprecated
String MINGRADE = "mingrade"; // min grade // TODO UNUSED
@Deprecated
String MAXGRADE = "maxgrade"; // max grade // TODO UNUSED
String PHOTOURL = "photoUrl"; // url for the photo
@@ -36,12 +36,12 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
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.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
@@ -424,8 +424,8 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
double heartRate = Double.NaN;
double cadence = Double.NaN;
double power = Double.NaN;
if (location instanceof SensorDataSetLocation && ((SensorDataSetLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet();
if (location instanceof TrackPoint && ((TrackPoint) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((TrackPoint) location).getSensorDataSet();
if (sensorDataSet.hasHeartRate()) {
heartRate = sensorDataSet.getHeartRate();
}
@@ -28,7 +28,6 @@ import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.util.DialogUtils;
/**
@@ -83,8 +82,7 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
public void run() {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
for (long markerId : markerIds) {
contentProviderUtils.deleteWaypoint(fragmentActivity,
markerId, new DescriptionGeneratorImpl(fragmentActivity));
contentProviderUtils.deleteWaypoint(fragmentActivity, markerId);
}
caller.onDeleteMarkerDone();
}
@@ -31,7 +31,6 @@ import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
/**
* A DialogFragment to select a file type, gpx, kml, etc.
@@ -73,7 +72,7 @@ public class FileTypeDialogFragment extends DialogFragment {
for (int i = 0; i < choices.length; i++) {
TrackFileFormat trackFileFormat = trackFileFormats[i];
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
choices[i] = getString(optionId, trackFileFormatUpperCase, FileUtils.getPathDisplayName(trackFileFormat.getExtension()));
choices[i] = getString(optionId, trackFileFormatUpperCase);
}
return new AlertDialog.Builder(getActivity())
.setNegativeButton(R.string.generic_cancel, new OnClickListener() {
@@ -41,7 +41,7 @@ import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.MarkerEditActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
@@ -37,11 +37,11 @@ import java.util.EnumSet;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
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.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -239,7 +239,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public void onStop() {
super.onStop();
if (trackRecordingServiceConnection != null) {
trackRecordingServiceConnection.unbind();
trackRecordingServiceConnection.unbind(getContext());
}
trackRecordingServiceConnection = null;
}
@@ -6,7 +6,7 @@ import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
@@ -16,21 +16,20 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.FragmentActivity;
import java.io.File;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -39,7 +38,6 @@ import de.dennisguse.opentracks.util.FileUtils;
/**
* An activity for saving tracks to the external storage.
* If saving a specific track, option to save it to a temp directory and play the track afterward.
*
* @author Rodrigo Damazio
*/
@@ -53,34 +51,34 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
private String directoryDisplayName;
private ExportAsyncTask exportAsyncTask;
private ProgressDialog progressDialog;
private int processedTrackCount;
private int totalTrackCount;
private Uri directoryUri;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
} else {
fileTypeDialogStart();
}
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
} else {
if (resultCode == Activity.RESULT_OK) {
directoryUri = resultData.getData();
fileTypeDialogStart();
} else {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
finish();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void fileTypeDialogStart() {
@@ -89,21 +87,14 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
@Override
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
if (!FileUtils.isExternalStorageWriteable()) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
finish();
return;
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
DocumentFile exportDirectory = pickedDirectory.findFile(FileUtils.EXPORT_DIR);
if (exportDirectory == null) {
exportDirectory = pickedDirectory.createDirectory(FileUtils.EXPORT_DIR);
}
File directory = new File(FileUtils.getPath(trackFileFormat.getExtension()));
if (!FileUtils.ensureDirectoryExists(directory)) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
finish();
return;
}
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, directory);
directoryDisplayName = FileUtils.getPath(exportDirectory);
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, exportDirectory);
exportAsyncTask.execute();
}
@@ -141,8 +132,7 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
} else {
iconId = R.drawable.ic_dialog_error_24dp;
titleId = R.string.generic_error_title;
message = getString(R.string.export_external_storage_error, processedTrackCount, totalTracks,
directoryDisplayName);
message = getString(R.string.export_external_storage_error, processedTrackCount, totalTracks, directoryDisplayName);
}
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true)
.setIcon(iconId).setMessage(message)
@@ -22,16 +22,16 @@ import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import java.io.File;
import androidx.documentfile.provider.DocumentFile;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
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.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
@@ -45,7 +45,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = ExportAsyncTask.class.getSimpleName();
private final TrackFileFormat trackFileFormat;
private final File directory;
private final DocumentFile directory;
private final Context context;
private final ContentProviderUtils contentProviderUtils;
private ExportActivity exportActivity;
@@ -63,7 +63,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* @param trackFileFormat the track file format
* @param directory the directory to write the file
*/
public ExportAsyncTask(ExportActivity exportActivity, TrackFileFormat trackFileFormat, File directory) {
public ExportAsyncTask(ExportActivity exportActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
this.exportActivity = exportActivity;
this.trackFileFormat = trackFileFormat;
this.directory = directory;
@@ -156,13 +156,13 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
};
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, tracks, trackExporterListener);
Track track = tracks[0];
String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension());
File file = new File(directory, fileName);
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
if (trackExporter.writeTrack(context, fileOutputStream)) {
String fileName = track.getId() + "." + trackFileFormat.getExtension();
DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), fileName);
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
if (trackExporter.writeTrack(context, outputStream)) {
return true;
} else {
if (!file.delete()) {
@@ -28,9 +28,9 @@ import java.io.OutputStream;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.LocationFactory;
import de.dennisguse.opentracks.content.LocationIterator;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.LocationUtils;
/**
@@ -99,7 +99,6 @@ public class FileTrackExporter implements TrackExporter {
boolean hasWaypoints = false;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
if (cursor != null && cursor.moveToFirst()) {
// Intentionally skip first waypoint (contains statistics).
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
throw new InterruptedException();
@@ -109,9 +108,7 @@ public class FileTrackExporter implements TrackExporter {
hasWaypoints = true;
}
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
if (!waypoint.isTripStatistics()) {
trackWriter.writeWaypoint(waypoint);
}
trackWriter.writeWaypoint(waypoint);
cursor.moveToNext();
}
@@ -217,7 +214,7 @@ public class FileTrackExporter implements TrackExporter {
@Override
public Location createLocation() {
if (currentLocation == null) {
currentLocation = new SensorDataSetLocation("");
currentLocation = new TrackPoint("");
}
return currentLocation;
}
@@ -23,8 +23,8 @@ import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Locale;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.StringUtils;
/**
@@ -119,12 +119,9 @@ public class GpxTrackWriter implements TrackWriter {
if (location.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
}
printWriter.println(
"<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
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("</wpt>");
}
@@ -28,11 +28,9 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.StringUtils;
@@ -43,19 +41,17 @@ import de.dennisguse.opentracks.util.StringUtils;
*/
public class KmlTrackWriter implements TrackWriter {
private static final String WAYPOINT_STYLE = "waypoint";
private static final String STATISTICS_STYLE = "statistics";
public static final String WAYPOINT_STYLE = "waypoint";
private static final String START_STYLE = "start";
private static final String END_STYLE = "end";
private static final String TRACK_STYLE = "track";
private static final String SCHEMA_ID = "schema";
private static final String SENSOR_TYPE_CADENCE = "cadence";
private static final String SENSOR_TYPE_HEART_RATE = "heart_rate";
private static final String SENSOR_TYPE_POWER = "power";
public static final String SENSOR_TYPE_CADENCE = "cadence";
public static final String SENSOR_TYPE_HEART_RATE = "heart_rate";
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 STATISTICS_ICON = "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
private static final String END_ICON = "http://maps.google.com/mapfiles/kml/paddle/red-circle.png";
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
@@ -88,7 +84,7 @@ public class KmlTrackWriter implements TrackWriter {
this.exportTrackDetail = exportTrackDetail;
this.exportSensorData = exportSensorData;
this.exportPhotos = exportPhotos;
this.descriptionGenerator = new DescriptionGeneratorImpl(context);
this.descriptionGenerator = new DescriptionGenerator(context);
this.contentProviderUtils = new ContentProviderUtils(context);
}
@@ -125,7 +121,6 @@ public class KmlTrackWriter implements TrackWriter {
writeTrackStyle();
writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1);
writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1);
writePlacemarkerStyle(STATISTICS_STYLE, STATISTICS_ICON, 20, 2);
writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2);
printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">");
@@ -167,13 +162,11 @@ public class KmlTrackWriter implements TrackWriter {
@Override
public void writeWaypoint(Waypoint waypoint) {
if (printWriter != null && exportTrackDetail) {
String styleName = waypoint.getType() == WaypointType.STATISTICS ? STATISTICS_STYLE : WAYPOINT_STYLE;
if (waypoint.hasPhoto() && exportPhotos) {
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 {
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) {
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
printWriter.println("<icon>" + StringUtils.formatCData(track.getIcon()) + "</icon>");
}
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
@@ -270,8 +264,8 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<gx:coord>" + getCoordinates(location, " ") + "</gx:coord>");
if (exportSensorData && location instanceof SensorDataSetLocation) {
SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet();
if (exportSensorData && location instanceof TrackPoint) {
SensorDataSet sensorDataSet = ((TrackPoint) location).getSensorDataSet();
if (sensorDataSet != null) {
if (sensorDataSet.hasHeartRate()) {
heartRateList.add(sensorDataSet.getHeartRate());
@@ -32,8 +32,8 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.FileUtils;
/**
@@ -19,8 +19,8 @@ import android.location.Location;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
/**
* Interface for writing tracks to a file. The expected sequence of calls is:
@@ -39,14 +39,11 @@ import javax.xml.parsers.SAXParserFactory;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.LocationFactory;
import de.dennisguse.opentracks.content.LocationIterator;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.LocationUtils;
@@ -71,12 +68,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
private final ContentProviderUtils contentProviderUtils;
private final int recordingDistanceInterval;
private final List<Long> trackIds;
private final List<Waypoint> waypoints;
private final List<Long> trackIds = new ArrayList<>();
private final List<Waypoint> waypoints = new ArrayList<>();
// The current element content
//TODO Should be made private and getter be used by child classes.
protected String content;
protected String icon;
protected String name;
protected String description;
protected String category;
@@ -104,8 +102,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
this.importTrackId = importTrackId;
this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
trackIds = new ArrayList<>();
waypoints = new ArrayList<>();
}
@Override
@@ -177,6 +173,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
return;
}
}
if (location == null) {
if (!locationIterator.hasNext()) {
// No more track points. Ignore the rest of the waypoints.
@@ -186,9 +183,11 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval);
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
}
if (waypoint.getLocation().getTime() > location.getTime()) {
location = null;
} else if (waypoint.getLocation().getTime() < location.getTime()) {
Log.w(TAG, "Ignoring waypoint: current location was after waypoint.");
waypoint = null;
} else {
// The waypoint location time matches the track point time
@@ -200,28 +199,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// Valid location
if (location.getLatitude() == waypoint.getLocation().getLatitude() && location.getLongitude() == waypoint.getLocation().getLongitude()) {
// Get tripStatistics, description, and icon
TripStatistics tripStatistics;
String waypointDescription;
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
String waypointDescription = waypoint.getDescription();
String icon = context.getString(R.string.marker_waypoint_icon_url);
double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance();
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
// 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, location, waypoint.getPhotoUrl());
contentProviderUtils.insertWaypoint(newWaypoint);
}
@@ -265,7 +249,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
if (category != null) {
trackData.track.setCategory(category);
trackData.track.setIcon(TrackIconUtils.getIconValue(context, category));
//TODO remove when GPX and KML support reading this property.
if (icon == null)
trackData.track.setIcon(TrackIconUtils.getIconValue(context, category));
}
if (icon != null) {
trackData.track.setIcon(icon);
}
if (trackData.tripStatisticsUpdater == null) {
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime);
@@ -274,7 +263,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
trackData.track.setNumberOfPoints(trackData.numberOfLocations);
contentProviderUtils.updateTrack(trackData.track);
insertFirstWaypoint(trackData.track);
}
/**
@@ -292,10 +280,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
/**
* 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
if (time == null) {
return;
@@ -318,7 +304,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (category != null) {
waypoint.setCategory(category);
}
waypoint.setType(type);
if (photoUrl != null) {
waypoint.setPhotoUrl(photoUrl);
@@ -372,11 +357,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*/
protected void insertTrackPoint(Location location) {
insertLocation(location);
if (trackData.track.getStartId() == -1L) {
// Flush the location to set the track start id and the track end id
flushLocations(trackData);
}
}
/**
@@ -397,7 +377,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (importTrackId == -1L) {
return null;
}
File dir = FileUtils.getPhotoDir(importTrackId);
File dir = FileUtils.getPhotoDir(context, importTrackId);
File file = new File(dir, fileName);
return Uri.fromFile(file).toString();
}
@@ -494,31 +474,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
contentProviderUtils.bulkInsertTrackPoint(data.bufferedLocations, data.numBufferedLocations, data.track.getId());
data.numBufferedLocations = 0;
if (data.track.getStartId() == -1L) {
data.track.setStartId(contentProviderUtils.getFirstTrackPointId(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);
}
/**
@@ -535,6 +490,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*
* @author Jimmy Shih
*/
// TODO Why private inner class?
private class TrackData {
// The current track
Track track = new Track();
@@ -25,7 +25,6 @@ import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
/**
* Imports a GPX file.
@@ -184,6 +183,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
* On waypoint end.
*/
private void onWaypointEnd() throws SAXException {
addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS : WaypointType.WAYPOINT);
addWaypoint();
}
}
@@ -16,21 +16,20 @@
package de.dennisguse.opentracks.io.file.importer;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.FragmentActivity;
import java.io.File;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -38,8 +37,7 @@ import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.FileUtils;
/**
* An activity to import files from the external storage. Optionally to import
* one specific file.
* An activity to import files from the external storage.
*
* @author Rodrigo Damazio
*/
@@ -50,70 +48,53 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_RESULT_ID = 1;
private String directoryDisplayName;
private ImportAsyncTask importAsyncTask;
private ProgressDialog progressDialog;
// the path on the external storage to import
private String directoryDisplayName;
private int importedTrackCount;
private int totalTrackCount;
private Uri directoryUri;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
} else {
fileTypeDialogStart();
}
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
Toast.makeText(this, R.string.external_storage_not_readable, Toast.LENGTH_LONG).show();
} else {
if (resultCode == Activity.RESULT_OK) {
directoryUri = resultData.getData();
fileTypeDialogStart();
} else {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
finish();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void fileTypeDialogStart() {
FileTypeDialogFragment.showDialog(getSupportFragmentManager(), R.string.import_selection_title, R.string.import_selection_option);
}
@Override
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
if (!FileUtils.isExternalStorageAvailable()) {
Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show();
finish();
return;
}
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
String directoryPath = FileUtils.getPath(trackFileFormat.getExtension());
if (!FileUtils.isDirectory(new File(directoryPath))) {
Toast.makeText(this, getString(R.string.import_no_directory, directoryDisplayName), Toast.LENGTH_LONG)
.show();
finish();
return;
}
directoryDisplayName = FileUtils.getPath(pickedDirectory);
//TODO (still needed?): getLastNonConfiguration instance returned ExportAsyncTask before
// Object retained = getLastNonConfigurationInstance();
// if (retained instanceof ImportAsyncTask) {
// importAsyncTask = (ImportAsyncTask) retained;
// importAsyncTask.setActivity(this);
// } else {
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, directoryPath);
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, pickedDirectory);
importAsyncTask.execute();
// }
}
@Override
@@ -154,8 +135,7 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
} else {
iconId = R.drawable.ic_dialog_error_24dp;
titleId = R.string.generic_error_title;
message = getString(
R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
message = getString(R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
}
return new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId)
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
@@ -22,14 +22,15 @@ import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import androidx.documentfile.provider.DocumentFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
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.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -44,7 +45,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = ImportAsyncTask.class.getSimpleName();
private final TrackFileFormat trackFileFormat;
private final String path;
private final DocumentFile directory;
// TODO Can be removed, or?
private Context context;
// TODO Use weak reference
@@ -66,12 +67,12 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
*
* @param importActivity the activity currently associated with this AsyncTask
* @param trackFileFormat the track file format
* @param path path to import GPX files
* @param directory path to import GPX files
*/
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, String path) {
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
this.importActivity = importActivity;
this.trackFileFormat = trackFileFormat;
this.path = path;
this.directory = directory;
context = importActivity.getApplicationContext();
completed = false;
@@ -99,39 +100,26 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
}
@Override
protected Boolean doInBackground(Void... params) {
try {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY); // TODO Should be set to previous level at the end of this method.
// Get the wake lock if not recording or paused
boolean isRecording = PreferencesUtils.isRecording(importActivity);
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
}
/**
* Gets a list of files.
* If importAll is true, returns a list of the files under the path directory.
* If importAll is false, returns a list containing just the path file.
*/
private static List<DocumentFile> getFiles(DocumentFile file, TrackFileFormat trackFileFormat) {
List<DocumentFile> files = new ArrayList<>();
List<File> files = getFiles();
totalTrackCount = files.size();
if (totalTrackCount == 0) {
return true;
}
for (int i = 0; i < totalTrackCount; i++) {
if (isCancelled()) {
// If cancelled, return true to show the number of files imported
return true;
for (DocumentFile candidate : file.listFiles()) {
if (!candidate.isDirectory()) {
String extension = FileUtils.getExtension(candidate.getName());
if (trackFileFormat.getExtension().equals(extension)) {
files.add(candidate);
}
if (importFile(files.get(i))) {
importTrackCount++;
}
publishProgress(i + 1, totalTrackCount);
}
return true;
} finally {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
} else {
files.addAll(getFiles(candidate, trackFileFormat));
}
}
return files;
}
@Override
@@ -159,16 +147,49 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
}
@Override
protected Boolean doInBackground(Void... params) {
try {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY); // TODO Should be set to previous level at the end of this method.
// Get the wake lock if not recording or paused
boolean isRecording = PreferencesUtils.isRecording(importActivity);
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
}
List<DocumentFile> files = getFiles(directory, trackFileFormat);
totalTrackCount = files.size();
for (int i = 0; i < totalTrackCount; i++) {
if (isCancelled()) {
// If cancelled, return true to show the number of files imported
return true;
}
if (importFile(files.get(i))) {
importTrackCount++;
}
publishProgress(i + 1, totalTrackCount);
}
return true;
} finally {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
}
/**
* Imports a file.
*
* @param file the file
*/
private boolean importFile(final File file) {
private boolean importFile(final DocumentFile file) {
TrackImporter trackImporter;
if (trackFileFormat == TrackFileFormat.GPX) {
trackImporter = new GpxFileTrackImporter(importActivity);
} else { //KML or KMZ
} else {
//KML or KMZ
String extension = FileUtils.getExtension(file.getName());
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
trackImporter = new KmlFileTrackImporter(importActivity, -1L);
@@ -181,37 +202,12 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
}
try (FileInputStream fileInputStream = new FileInputStream(file)) {
trackId = trackImporter.importFile(fileInputStream);
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
trackId = trackImporter.importFile(inputStream);
return trackId != -1L;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return false;
}
}
/**
* Gets a list of files.
* If importAll is true, returns a list of the files under the path directory.
* If importAll is false, returns a list containing just the path file.
*/
private List<File> getFiles() {
List<File> files = new ArrayList<>();
File file = new File(path);
File[] candidates = file.listFiles();
if (candidates == null) {
return files;
}
for (File candidate : candidates) {
if (!FileUtils.isDirectory(candidate)) {
String extension = FileUtils.getExtension(candidate.getName());
if (trackFileFormat.getExtension().equals(extension)) {
files.add(candidate);
}
}
}
return files;
}
}
@@ -29,9 +29,9 @@ import org.xml.sax.SAXException;
import java.util.ArrayList;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
/**
* 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 CADENCE = "cadence";
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 WAYPOINT_STYLE = "#" + KmlTrackWriter.WAYPOINT_STYLE;
private static final String TAG_COORDINATES = "coordinates";
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_MULTI_TRACK = "gx:MultiTrack";
private static final String TAG_GX_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData";
@@ -140,6 +136,10 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (content != null) {
description = content.trim();
}
} else if (localName.equals(TAG_ICON)) {
if (content != null) {
icon = content.trim();
}
} else if (localName.equals(TAG_VALUE)) {
if (content != null) {
category = content.trim();
@@ -168,6 +168,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private void onWaypointStart() {
// Reset all Placemark variables
name = null;
icon = null;
description = null;
category = null;
photoUrl = null;
@@ -182,29 +183,16 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
* On waypoint end.
*/
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;
}
if (photoUrl != null) {
Uri uri = Uri.parse(photoUrl);
photoUrl = getPhotoUrl(uri.getLastPathSegment());
}
addWaypoint(type);
addWaypoint();
}
/**
@@ -260,7 +248,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (!hasSensorData) {
insertTrackPoint(location);
} else {
SensorDataSetLocation sensorDataSetLocation = new SensorDataSetLocation(location, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime()));
TrackPoint sensorDataSetLocation = new TrackPoint(location, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime()));
insertTrackPoint(sensorDataSetLocation);
}
}
@@ -315,13 +303,13 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
}
switch (sensorType) {
case POWER:
case KmlTrackWriter.SENSOR_TYPE_POWER:
powerList.add(value);
break;
case HEART_RATE:
case KmlTrackWriter.SENSOR_TYPE_HEART_RATE:
heartRateList.add(value);
break;
case CADENCE:
case KmlTrackWriter.SENSOR_TYPE_CADENCE:
cadenceList.add(value);
break;
default:
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import java.io.ByteArrayInputStream;
@@ -68,7 +67,7 @@ public class KmzTrackImporter implements TrackImporter {
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (Thread.interrupted()) {
Log.d(TAG, "Thread interrupted");
cleanImport(trackId);
cleanImport(context, trackId);
return -1L;
}
String fileName = zipEntry.getName();
@@ -76,7 +75,7 @@ public class KmzTrackImporter implements TrackImporter {
trackId = parseKml(zipInputStream);
if (trackId == -1L) {
Log.d(TAG, "Unable to parse kml in kmz");
cleanImport(trackId);
cleanImport(context, trackId);
return -1L;
}
} else {
@@ -90,7 +89,7 @@ public class KmzTrackImporter implements TrackImporter {
return trackId;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
cleanImport(trackId);
cleanImport(context, trackId);
return -1L;
}
}
@@ -100,20 +99,19 @@ public class KmzTrackImporter implements TrackImporter {
*
* @param trackId the trackId
*/
private void cleanImport(long trackId) {
private void cleanImport(Context context, long trackId) {
if (PreferencesUtils.isRecording(trackId)) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
contentProviderUtils.deleteTrack(context, trackId);
}
if (importTrackId != -1L) {
File dir = FileUtils.getPhotoDir(importTrackId);
if (FileUtils.isDirectory(dir)) {
File dir = FileUtils.getPhotoDir(context, importTrackId);
if (dir.exists() && dir.isDirectory()) {
for (File file : dir.listFiles()) {
file.delete();
}
dir.delete();
FileUtils.updateMediaScanner(context, Uri.fromFile(dir));
}
}
}
@@ -159,8 +157,7 @@ public class KmzTrackImporter implements TrackImporter {
return;
}
File dir = FileUtils.getPhotoDir(importTrackId);
FileUtils.ensureDirectoryExists(dir);
File dir = FileUtils.getPhotoDir(context, importTrackId);
File file = new File(dir, fileName);
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
@@ -169,8 +166,6 @@ public class KmzTrackImporter implements TrackImporter {
while ((count = zipInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, count);
}
FileUtils.updateMediaScanner(context, Uri.fromFile(file));
}
}
}
@@ -19,7 +19,6 @@ import android.location.Location;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
@@ -83,10 +82,9 @@ public interface ITrackRecordingService {
/**
* 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
*/
long insertWaypoint(WaypointCreationRequest request);
long insertWaypoint(String name, String category, String description, String photoUrl);
/**
* ONLY FOR TESTING
@@ -46,10 +46,9 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.CustomContentProvider;
import de.dennisguse.opentracks.content.LocationFactory;
import de.dennisguse.opentracks.content.LocationIterator;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
@@ -292,50 +291,38 @@ public class TrackRecordingService extends Service {
/**
* Inserts a waypoint.
*
* @param waypointCreationRequest the waypoint creation request
* @return the waypoint id
*/
public long insertWaypoint(WaypointCreationRequest waypointCreationRequest) {
public long insertWaypoint(String name, String category, String description, String photoUrl) {
if (!isRecording() || isPaused()) {
return -1L;
}
String name = waypointCreationRequest.getName();
if (name == null) {
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId, waypointCreationRequest.getType());
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId);
if (nextWaypointNumber == -1) {
nextWaypointNumber = 0;
nextWaypointNumber = 1;
}
name = getString(R.string.marker_name_format, nextWaypointNumber);
name = getString(R.string.marker_name_format, nextWaypointNumber + 1);
}
String category = waypointCreationRequest.getCategory() != null ? waypointCreationRequest.getCategory() : "";
String description = waypointCreationRequest.getDescription() != null ? waypointCreationRequest.getDescription() : "";
String icon = getString(R.string.marker_waypoint_icon_url);
double length;
long duration;
Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (location != null && trackTripStatisticsUpdater != null) {
TripStatistics stats = trackTripStatisticsUpdater.getTripStatistics();
length = stats.getTotalDistance();
duration = stats.getTotalTime();
} else {
if (!waypointCreationRequest.isTrackStatistics()) {
return -1L;
}
// For track statistics, make it an impossible location
location = new Location("");
location.setLatitude(100);
location.setLongitude(180);
length = 0.0;
duration = 0L;
if (location == null) {
Log.i(TAG, "Could not create a waypoint as location is unknown.");
return -1L;
}
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
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, waypointCreationRequest.getType(), length, duration, -1L, -1L, location, null, photoUrl);
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, location, photoUrl);
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
return Long.parseLong(uri.getLastPathSegment());
}
@@ -371,9 +358,6 @@ public class TrackRecordingService extends Service {
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
contentProviderUtils.updateTrack(track);
//TODO Do not use insertWaypoint
insertWaypoint(WaypointCreationRequest.DEFAULT_START_TRACK);
startRecording(true);
return trackId;
}
@@ -479,7 +463,7 @@ public class TrackRecordingService extends Service {
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(trackId));
// Update the recording track time
updateRecordingTrack(track, contentProviderUtils.getLastTrackPointId(trackId), false);
updateRecordingTrack(track, false);
}
String trackName = TrackNameUtils.getTrackName(this, trackId, track.getTripStatistics().getStartTime());
@@ -628,7 +612,7 @@ public class TrackRecordingService extends Service {
SensorDataSet sensorDataSet = getSensorDataSet();
if (sensorDataSet != null) {
location = new SensorDataSetLocation(location, sensorDataSet);
location = new TrackPoint(location, sensorDataSet);
}
// Always insert the first segment location
@@ -703,9 +687,8 @@ public class TrackRecordingService extends Service {
try {
Uri uri = contentProviderUtils.insertTrackPoint(location, track.getId());
long trackPointId = Long.parseLong(uri.getLastPathSegment());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
updateRecordingTrack(track, LocationUtils.isValidLocation(location));
} catch (SQLiteException e) {
/*
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
@@ -721,16 +704,9 @@ public class TrackRecordingService extends Service {
* Increase the number of points if it is a new and valid track point.
*
* @param track the track
* @param lastTrackPointId the last track point id
* @param increaseNumberOfPoints true to increase the number of points
*/
private void updateRecordingTrack(Track track, long lastTrackPointId, boolean increaseNumberOfPoints) {
if (lastTrackPointId >= 0) {
if (track.getStartId() < 0) {
track.setStartId(lastTrackPointId);
}
track.setStopId(lastTrackPointId);
}
private void updateRecordingTrack(Track track, boolean increaseNumberOfPoints) {
if (increaseNumberOfPoints) {
track.setNumberOfPoints(track.getNumberOfPoints() + 1);
}
@@ -892,8 +868,8 @@ public class TrackRecordingService extends Service {
}
@Override
public long insertWaypoint(WaypointCreationRequest waypointCreationRequest) {
return trackRecordingService.insertWaypoint(waypointCreationRequest);
public long insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
}
@VisibleForTesting
@@ -31,7 +31,6 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackEditActivity;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.ServiceUtils;
@@ -48,9 +47,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
@Deprecated
//TODO Always the context of the owner of a service connection (i.e., let it be passed to every call).
private final Context context;
private final Runnable callback;
private ITrackRecordingService trackRecordingService;
@@ -62,29 +58,28 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* @param callback the callback to invoke when the service binding changes
*/
public TrackRecordingServiceConnection(Context context, Runnable callback) {
this.context = context;
this.callback = callback;
}
/**
* Starts and binds the service.
*/
public void startAndBind() {
bindService(true);
public void startAndBind(Context context) {
bindService(context, true);
}
/**
* Binds the service if it is started.
*/
public void bindIfStarted() {
bindService(false);
public void bindIfStarted(Context context) {
bindService(context, false);
}
/**
* Unbinds and stops the service.
*/
public void unbindAndStop() {
unbind();
public void unbindAndStop(Context context) {
unbind(context);
context.stopService(new Intent(context, TrackRecordingService.class));
}
@@ -111,7 +106,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/**
* Unbinds the service (but leave it running).
*/
public void unbind() {
public void unbind(Context context) {
try {
context.unbindService(this);
} catch (IllegalArgumentException e) {
@@ -148,7 +143,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
*
* @param startIfNeeded start the service if needed
*/
private void bindService(boolean startIfNeeded) {
private void bindService(Context context, boolean startIfNeeded) {
if (trackRecordingService != null) {
// Service is already started and bound.
return;
@@ -175,7 +170,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* @param context the context
*/
public void startConnection(@NonNull Context context) {
bindIfStarted();
bindIfStarted(context);
if (!ServiceUtils.isTrackRecordingServiceRunning(context)) {
resetRecordingState(context);
}
@@ -216,13 +211,13 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
*
* @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();
if (trackRecordingService == null) {
Log.d(TAG, "Unable to add marker, no track recording service");
} else {
try {
long markerId = trackRecordingService.insertWaypoint(waypointCreationRequest);
long markerId = trackRecordingService.insertWaypoint(name, category, description, photoUrl);
if (markerId != -1L) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return markerId;
@@ -266,6 +261,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
Log.e(TAG, "Unable to stop recording.", e);
}
}
unbindAndStop();
unbindAndStop(context);
}
}
@@ -40,7 +40,7 @@ public class BluetoothLeAdapter extends BaseAdapter {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
//TODO Check if there is a better way to achieve identical look and feel to ListPreference.
//Use material design single choice; for old style use: android.R.layout.select_dialog_singlechoice
currentView = inflater.inflate(androidx.appcompat.R.layout.select_dialog_singlechoice_material, parent);
currentView = inflater.inflate(androidx.appcompat.R.layout.select_dialog_singlechoice_material, null);
}
Device device = devices.get(position);
@@ -26,10 +26,6 @@ import androidx.annotation.NonNull;
*/
public class TripStatistics {
// The min and max latitude seen in this trip.
private final ExtremityMonitor latitudeExtremities = new ExtremityMonitor();
// The min and max longitude seen in this trip.
private final ExtremityMonitor longitudeExtremities = new ExtremityMonitor();
// The min and max elevation (meters) seen on this trip.
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
// The min and max grade seen on this trip.
@@ -39,16 +35,16 @@ public class TripStatistics {
private long startTime = -1L;
// The trip stop time. This is the system time, might not match the GPS time.
private long stopTime = -1L;
// The total trip distance (meters).
private double totalDistance;
// The total time (ms). Updated when new points are received, may be stale.
private long totalTime;
// The total moving time (ms). Based on when we believe the user is traveling.
private long movingTime;
private double totalDistance_m;
// Updated when new points are received, may be stale.
private long totalTime_ms;
// Based on when we believe the user is traveling.
private long movingTime_ms;
// The maximum speed (meters/second) that we believe is valid.
private double maxSpeed;
private double maxSpeed_mps;
// The total elevation gained (meters).
private double totalElevationGain;
private double totalElevationGain_m;
public TripStatistics() {
}
@@ -61,14 +57,12 @@ public class TripStatistics {
public TripStatistics(TripStatistics other) {
startTime = other.startTime;
stopTime = other.stopTime;
totalDistance = other.totalDistance;
totalTime = other.totalTime;
movingTime = other.movingTime;
latitudeExtremities.set(other.latitudeExtremities.getMin(), other.latitudeExtremities.getMax());
longitudeExtremities.set(other.longitudeExtremities.getMin(), other.longitudeExtremities.getMax());
maxSpeed = other.maxSpeed;
totalDistance_m = other.totalDistance_m;
totalTime_ms = other.totalTime_ms;
movingTime_ms = other.movingTime_ms;
maxSpeed_mps = other.maxSpeed_mps;
elevationExtremities.set(other.elevationExtremities.getMin(), other.elevationExtremities.getMax());
totalElevationGain = other.totalElevationGain;
totalElevationGain_m = other.totalElevationGain_m;
gradeExtremities.set(other.gradeExtremities.getMin(), other.gradeExtremities.getMax());
}
@@ -81,23 +75,15 @@ public class TripStatistics {
public void merge(TripStatistics other) {
startTime = Math.min(startTime, other.startTime);
stopTime = Math.max(stopTime, other.stopTime);
totalDistance += other.totalDistance;
totalTime += other.totalTime;
movingTime += other.movingTime;
if (other.latitudeExtremities.hasData()) {
latitudeExtremities.update(other.latitudeExtremities.getMin());
latitudeExtremities.update(other.latitudeExtremities.getMax());
}
if (other.longitudeExtremities.hasData()) {
longitudeExtremities.update(other.longitudeExtremities.getMin());
longitudeExtremities.update(other.longitudeExtremities.getMax());
}
maxSpeed = Math.max(maxSpeed, other.maxSpeed);
totalDistance_m += other.totalDistance_m;
totalTime_ms += other.totalTime_ms;
movingTime_ms += other.movingTime_ms;
maxSpeed_mps = Math.max(maxSpeed_mps, other.maxSpeed_mps);
if (other.elevationExtremities.hasData()) {
elevationExtremities.update(other.elevationExtremities.getMin());
elevationExtremities.update(other.elevationExtremities.getMax());
}
totalElevationGain += other.totalElevationGain;
totalElevationGain_m += other.totalElevationGain_m;
if (other.gradeExtremities.hasData()) {
gradeExtremities.update(other.gradeExtremities.getMin());
gradeExtremities.update(other.gradeExtremities.getMax());
@@ -111,54 +97,28 @@ public class TripStatistics {
return startTime;
}
/**
* Sets the trip start time.
*
* @param startTime the trip start time in milliseconds since the epoch
*/
public void setStartTime(long startTime) {
this.startTime = startTime;
}
/**
* Gets the trip stop time. The number of milliseconds since epoch.
*/
public long getStopTime() {
return stopTime;
}
/**
* Sets the trip stop time.
*
* @param stopTime the stop time in milliseconds since the epoch
*/
public void setStopTime(long stopTime) {
this.stopTime = stopTime;
}
/**
* Gets the total distance the user traveled in meters.
*/
public double getTotalDistance() {
return totalDistance;
return totalDistance_m;
}
/**
* Sets the total trip distance.
*
* @param totalDistance the trip distance in meters
*/
public void setTotalDistance(double totalDistance) {
this.totalDistance = totalDistance;
public void setTotalDistance(double totalDistance_m) {
this.totalDistance_m = totalDistance_m;
}
/**
* Adds to the current total distance.
*
* @param distance the distance to add in meters
*/
public void addTotalDistance(double distance) {
totalDistance += distance;
public void addTotalDistance(double distance_m) {
totalDistance_m += distance_m;
}
/**
@@ -167,144 +127,23 @@ public class TripStatistics {
* If you need to calculate the proper total time, use {@link #getStartTime} with the current time.
*/
public long getTotalTime() {
return totalTime;
return totalTime_ms;
}
/**
* Sets the trip total time.
*
* @param totalTime the trip total time in milliseconds
*/
public void setTotalTime(long totalTime) {
this.totalTime = totalTime;
public void setTotalTime(long totalTime_ms) {
this.totalTime_ms = totalTime_ms;
}
/**
* Gets the moving time in milliseconds.
*/
public long getMovingTime() {
return movingTime;
return movingTime_ms;
}
/**
* Sets the trip total moving time.
*
* @param movingTime the trip total moving time in milliseconds
*/
public void setMovingTime(long movingTime) {
this.movingTime = movingTime;
public void setMovingTime(long movingTime_ms) {
this.movingTime_ms = movingTime_ms;
}
/**
* Adds to the trip total moving time.
*
* @param time the time in milliseconds
*/
public void addMovingTime(long time) {
movingTime += time;
}
/**
* Gets the topmost position (highest latitude) of the track, in signed degrees.
*/
public double getTopDegrees() {
return latitudeExtremities.getMax();
}
/**
* Gets the topmost position (highest latitude) of the track, in signed millions of degrees.
*/
public int getTop() {
return (int) (latitudeExtremities.getMax() * 1E6);
}
/**
* Gets the bottommost position (lowest latitude) of the track, in signed
* degrees.
*/
public double getBottomDegrees() {
return latitudeExtremities.getMin();
}
/**
* Gets the bottommost position (lowest latitude) of the track, in signed millions of degrees.
*/
public int getBottom() {
return (int) (latitudeExtremities.getMin() * 1E6);
}
/**
* Gets the leftmost position (lowest longitude) of the track, in signed degrees.
*/
public double getLeftDegrees() {
return longitudeExtremities.getMin();
}
/**
* Gets the leftmost position (lowest longitude) of the track, in signed millions of degrees.
*/
public int getLeft() {
return (int) (longitudeExtremities.getMin() * 1E6);
}
/**
* Gets the rightmost position (highest longitude) of the track, in signed degrees.
*/
public double getRightDegrees() {
return longitudeExtremities.getMax();
}
/**
* Gets the rightmost position (highest longitude) of the track, in signed millions of degrees.
*/
public int getRight() {
return (int) (longitudeExtremities.getMax() * 1E6);
}
/**
* Gets the mean latitude position of the track, in signed degrees.
*/
public double getMeanLatitude() {
return (getBottomDegrees() + getTopDegrees()) / 2.0;
}
/**
* Gets the mean longitude position of the track, in signed degrees.
*/
public double getMeanLongitude() {
return (getLeftDegrees() + getRightDegrees()) / 2.0;
}
/**
* Sets the bounding box for this trip. The unit for all parameters is signed
* millions of degree (degrees * 1E6).
*
* @param leftE6 the leftmost longitude reached
* @param topE6 the topmost latitude reached
* @param rightE6 the rightmost longitude reached
* @param bottomE6 the bottommost latitude reached
*/
public void setBounds(int leftE6, int topE6, int rightE6, int bottomE6) {
latitudeExtremities.set(bottomE6 / 1E6, topE6 / 1E6);
longitudeExtremities.set(leftE6 / 1E6, rightE6 / 1E6);
}
/**
* Updates a new latitude value.
*
* @param latitude the latitude value in signed decimal degrees
*/
public void updateLatitudeExtremities(double latitude) {
latitudeExtremities.update(latitude);
}
/**
* Updates a new longitude value.
*
* @param longitude the longitude value in signed decimal degrees
*/
public void updateLongitudeExtremities(double longitude) {
longitudeExtremities.update(longitude);
public void addMovingTime(long time_ms) {
movingTime_ms += time_ms;
}
/**
@@ -312,27 +151,27 @@ public class TripStatistics {
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
*/
public double getAverageSpeed() {
if (totalTime == 0L) {
if (totalTime_ms == 0L) {
return 0.0;
}
return totalDistance / ((double) totalTime / 1000.0);
return totalDistance_m / ((double) totalTime_ms / 1000.0);
}
/**
* Gets the average moving speed in meters/second.
*/
public double getAverageMovingSpeed() {
if (movingTime == 0L) {
if (movingTime_ms == 0L) {
return 0.0;
}
return totalDistance / ((double) movingTime / 1000.0);
return totalDistance_m / ((double) movingTime_ms / 1000.0);
}
/**
* Gets the maximum speed in meters/second.
*/
public double getMaxSpeed() {
return Math.max(maxSpeed, getAverageMovingSpeed());
return Math.max(maxSpeed_mps, getAverageMovingSpeed());
}
/**
@@ -341,7 +180,7 @@ public class TripStatistics {
* @param maxSpeed the maximum speed in meters/second
*/
public void setMaxSpeed(double maxSpeed) {
this.maxSpeed = maxSpeed;
this.maxSpeed_mps = maxSpeed;
}
/**
@@ -372,65 +211,44 @@ public class TripStatistics {
/**
* Sets the maximum elevation.
*
* @param elevation the maximum elevation in meters
* @param elevation_m the maximum elevation in meters
*/
public void setMaxElevation(double elevation) {
elevationExtremities.setMax(elevation);
public void setMaxElevation(double elevation_m) {
elevationExtremities.setMax(elevation_m);
}
/**
* Updates a new elevation.
*
* @param elevation the elevation value in meters
* @param elevation_m the elevation value in meters
*/
public void updateElevationExtremities(double elevation) {
elevationExtremities.update(elevation);
public void updateElevationExtremities(double elevation_m) {
elevationExtremities.update(elevation_m);
}
/**
* Gets the total elevation gain in meters. This is calculated as the sum of all positive differences in the smoothed elevation.
*/
public double getTotalElevationGain() {
return totalElevationGain;
return totalElevationGain_m;
}
/**
* Sets the total elevation gain.
*
* @param totalElevationGain the elevation gain in meters
*/
public void setTotalElevationGain(double totalElevationGain) {
this.totalElevationGain = totalElevationGain;
public void setTotalElevationGain(double totalElevationGain_m) {
this.totalElevationGain_m = totalElevationGain_m;
}
/**
* Adds to the total elevation gain.
*
* @param gain the elevation gain in meters
*/
public void addTotalElevationGain(double gain) {
totalElevationGain += gain;
public void addTotalElevationGain(double gain_m) {
totalElevationGain_m += gain_m;
}
/**
* Gets the minimum grade for this trip.
*/
public double getMinGrade() {
return gradeExtremities.getMin();
}
/**
* Sets the minimum grade.
*
* @param grade the grade as a fraction (-1.0 would mean vertical downwards)
*/
public void setMinGrade(double grade) {
gradeExtremities.setMin(grade);
}
/**
* Gets the maximum grade for this trip.
*/
public double getMaxGrade() {
return gradeExtremities.getMax();
}
@@ -444,11 +262,6 @@ public class TripStatistics {
gradeExtremities.setMax(grade);
}
/**
* Updates a new grade value.
*
* @param grade the grade value as a fraction
*/
public void updateGradeExtremities(double grade) {
gradeExtremities.update(grade);
}
@@ -458,9 +271,7 @@ public class TripStatistics {
public String toString() {
return "TripStatistics { Start Time: " + getStartTime() + "; Stop Time: " + getStopTime()
+ "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime()
+ "; Moving Time: " + getMovingTime() + "; Min Latitude: " + getBottomDegrees()
+ "; Max Latitude: " + getTopDegrees() + "; Min Longitude: " + getLeftDegrees()
+ "; Max Longitude: " + getRightDegrees() + "; Max Speed: " + getMaxSpeed()
+ "; Moving Time: " + getMovingTime() + "; Max Speed: " + getMaxSpeed()
+ "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation()
+ "; Elevation Gain: " + getTotalElevationGain() + "; Min Grade: " + getMinGrade()
+ "; Max Grade: " + getMaxGrade() + "}";
@@ -138,9 +138,6 @@ public class TripStatisticsUpdater {
return;
}
currentSegment.updateLatitudeExtremities(location.getLatitude());
currentSegment.updateLongitudeExtremities(location.getLongitude());
//TODO Use Barometer to compute elevation gain.
double elevationDifference = location.hasAltitude() ? updateElevation(location.getAltitude()) : 0.0;
@@ -16,10 +16,10 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import androidx.documentfile.provider.DocumentFile;
import java.io.File;
import de.dennisguse.opentracks.BuildConfig;
@@ -36,94 +36,36 @@ public class FileUtils {
*/
static final String FILEPROVIDER = BuildConfig.APPLICATION_ID + ".fileprovider";
/**
* Our external storage directory.
*/
static final String SDCARD_TOP_DIR = "OpenTracks";
private static final String TRACK_PREFIX = "OpenTracks";
public static final String EXPORT_DIR = "OpenTracks";
/**
* The maximum FAT32 path length. See the FAT32 spec at
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
*/
static final int MAX_FAT32_PATH_LENGTH = 260;
/**
* The pictures directory under the app's external storage directory.
*/
private static final String PICTURES_DIR = "pictures";
private FileUtils() {
}
/**
* Returns true if the external storage is available.
*/
public static boolean isExternalStorageAvailable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
public static File getPhotoDir(Context context) {
return context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
/**
* Returns true if the external storage is writable.
*/
public static boolean isExternalStorageWriteable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
public static File getPhotoDir(Context context, long trackId) {
File photoDirectory = new File(getPhotoDir(context), "" + trackId);
photoDirectory.mkdirs();
return photoDirectory;
}
/**
* Returns true if the directory exists.
*
* @param dir the directory
*/
public static boolean isDirectory(File dir) {
return dir.exists() && dir.isDirectory();
}
/**
* Ensures the directory exists by creating it and its parents if necessary.
*
* @return whether the directory exists (either already existed or was
* successfully created)
*/
public static boolean ensureDirectoryExists(File dir) {
if (isDirectory(dir)) {
return true;
public static String getPath(DocumentFile file) {
if (file == null) {
return "";
}
return dir.mkdirs();
}
public static File getPhotoDir() {
return new File(getPath(PICTURES_DIR));
}
public static File getPhotoDir(long trackId) {
return new File(getPath(PICTURES_DIR, TRACK_PREFIX + trackId));
}
/**
* Gets the display name for a path on the external storage.
*
* @param components the components
*/
public static String getPathDisplayName(String... components) {
StringBuilder dirNameBuilder = new StringBuilder();
dirNameBuilder.append(File.separatorChar);
dirNameBuilder.append(SDCARD_TOP_DIR);
for (String component : components) {
dirNameBuilder.append(File.separatorChar);
dirNameBuilder.append(component);
if (file.getParentFile() == null) {
return file.getName();
}
return dirNameBuilder.toString();
}
/**
* Gets a path on the external storage.
*
* @param components the components
*/
public static String getPath(String... components) {
return Environment.getExternalStorageDirectory() + getPathDisplayName(components);
return getPath(file.getParentFile()) + File.pathSeparatorChar + file.getName();
}
/**
@@ -165,12 +107,6 @@ public class FileUtils {
return fileName.substring(index + 1);
}
public static void updateMediaScanner(Context context, Uri uri) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(uri);
context.sendBroadcast(mediaScanIntent);
}
/**
* Builds a filename with the given base and the given extension, possibly adding a suffix to ensure the file doesn't exist.
*
@@ -0,0 +1,50 @@
package de.dennisguse.opentracks.util;
import android.content.ClipData;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import java.util.ArrayList;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
/**
* Create an {@link Intent} to request showing a Dashboard.
* The receiving {@link android.app.Activity} gets temporary access to the {@link TracksColumns} and the {@link TrackPointsColumns} (incl. update).
*/
public class IntentDashboardUtils {
public static final String ACTION_DASHBOARD = "Intent.OpenTracks-Dashboard";
public static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
private IntentDashboardUtils() {
}
public static boolean startDashboardPlugin(Context context, long trackId) {
ArrayList<Uri> uris = new ArrayList<>();
uris.add(0, ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId));
uris.add(1, ContentUris.withAppendedId(TrackPointsColumns.CONTENT_URI, trackId));
Intent intent = new Intent(ACTION_DASHBOARD);
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ClipData clipData = ClipData.newRawUri(null, uris.get(0));
clipData.addItem(new ClipData.Item(uris.get(1)));
intent.setClipData(clipData);
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(Intent.createChooser(intent, null));
return true;
}
return false;
}
}
@@ -34,10 +34,11 @@ import java.util.Date;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.ShareContentProvider;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
/**
@@ -77,7 +78,7 @@ public class IntentUtils {
String trackDescription = "";
if (trackIds.length == 1) {
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;
@@ -126,6 +127,8 @@ public class IntentUtils {
}
}
/**
* Send intent to show tracks on a map (needs an another app).
*
@@ -137,10 +140,16 @@ public class IntentUtils {
return;
}
if (IntentDashboardUtils.startDashboardPlugin(context, trackIds[0])) {
return;
}
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_WITH_TRACKDETAIL);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
@@ -157,8 +166,7 @@ public class IntentUtils {
* @param trackId the track id
*/
public static Pair<Intent, Uri> createTakePictureIntent(Context context, long trackId) {
File dir = FileUtils.getPhotoDir(trackId);
FileUtils.ensureDirectoryExists(dir);
File dir = FileUtils.getPhotoDir(context, trackId);
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, JPEG_EXTENSION));
@@ -22,7 +22,7 @@ import java.util.ArrayList;
import java.util.List;
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.
@@ -164,6 +164,7 @@ public class LocationUtils {
* @param location the location to test
* @return true if the location is a valid location.
*/
//TODO Still needed?
public static boolean isValidLocation(Location location) {
return location != null
&& Math.abs(location.getLatitude()) <= 90
@@ -154,33 +154,33 @@ public class StringUtils {
* Get the formatted distance with unit.
*
* @param context the context
* @param distance the distance
* @param distance_m the distance
* @param metricUnits true to use metric unit
* @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) {
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
public static Pair<String, String> getDistanceParts(Context context, double distance_m, boolean metricUnits) {
if (Double.isNaN(distance_m) || Double.isInfinite(distance_m)) {
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
}
int unitId;
if (metricUnits) {
if (distance > 500.0) {
distance *= UnitConversions.M_TO_KM;
if (distance_m > 500.0) {
distance_m *= UnitConversions.M_TO_KM;
unitId = R.string.unit_kilometer;
} else {
unitId = R.string.unit_meter;
}
} else {
if (distance * UnitConversions.M_TO_MI > 0.5) {
distance *= UnitConversions.M_TO_MI;
if (distance_m * UnitConversions.M_TO_MI > 0.5) {
distance_m *= UnitConversions.M_TO_MI;
unitId = R.string.unit_mile;
} else {
distance *= UnitConversions.M_TO_FT;
distance_m *= UnitConversions.M_TO_FT;
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 unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
if (!Double.isNaN(elevation) && !Double.isInfinite(elevation)) {
if (metricUnits) {
value = StringUtils.formatDecimal(elevation);
} else {
value = StringUtils.formatDecimal(elevation * UnitConversions.M_TO_FT);
if (!metricUnits) {
elevation *= UnitConversions.M_TO_FT;
}
value = StringUtils.formatDecimal(elevation, 0);
}
return new Pair<>(value, unit);
}
@@ -48,7 +48,7 @@ public class TrackIconUtils {
private static final String UNKNOWN = "UNKNOWN";
private static final String WALK = "WALK";
private static final int ACTIVITY_UNKNOWN_LOGO = R.drawable.ic_logo_24dp;
private static final int ACTIVITY_UNKNOWN_LOGO = R.drawable.ic_logo_white_24dp;
private static final int[] AIRPLANE_LIST = new int[]{R.string.activity_type_airplane, R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane};
private static final int[] BIKE_LIST = new int[]{R.string.activity_type_biking, R.string.activity_type_cycling, R.string.activity_type_dirt_bike, R.string.activity_type_motor_bike, R.string.activity_type_mountain_biking, R.string.activity_type_road_biking, R.string.activity_type_track_cycling};
@@ -19,8 +19,8 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
/**
* Utilities for updating track.
@@ -4,6 +4,6 @@
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:fillColor="#FFFFFFFF"
android:fillColor="#000000"
android:pathData="M82.489,12.834C83.196,10.418 82.043,9.556 79.926,10.917L10.087,55.828c-2.117,1.361 -1.795,2.628 0.714,2.815L39.806,60.807c2.51,0.188 5.291,2.267 6.18,4.621l10.282,27.208c0.89,2.354 2.195,2.304 2.902,-0.111z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:fillColor="#FFFFFF"
android:pathData="M82.489,12.834C83.196,10.418 82.043,9.556 79.926,10.917L10.087,55.828c-2.117,1.361 -1.795,2.628 0.714,2.815L39.806,60.807c2.51,0.188 5.291,2.267 6.18,4.621l10.282,27.208c0.89,2.354 2.195,2.304 2.902,-0.111z" />
</vector>
-16
View File
@@ -33,22 +33,6 @@ limitations under the License.
android:text="@string/generic_name" />
<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_height="wrap_content"
android:orientation="vertical">
-17
View File
@@ -214,21 +214,6 @@ limitations under the License.
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
style="@style/StatsSmallValue"
android:visibility="invisible" />
<TextView
android:id="@+id/stats_elevation_current_label"
style="@style/StatsSmallLabel"
android:paddingStart="8dp"
android:paddingEnd="8dp" />
</LinearLayout>
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
@@ -242,7 +227,6 @@ limitations under the License.
android:id="@+id/stats_elevation_current_unit"
style="@style/StatsUnit" />
</LinearLayout>
</TableRow>
<!-- Minimal elevation -->
@@ -278,7 +262,6 @@ limitations under the License.
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/stats_min" />
</LinearLayout>
</TableRow>
+10 -6
View File
@@ -14,20 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<TabHost
android:id="@+id/tackdetail_tabhost"
<com.google.android.material.tabs.TabLayout
android:id="@+id/track_detail_activity_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/holo_orange_dark" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/track_detail_activity_view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<include layout="@layout/track_details_tab" />
</TabHost>
android:layout_weight="1" />
<include layout="@layout/track_controller" />
</LinearLayout>
-41
View File
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</merge>
-453
View File
@@ -1,453 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_type_airplane">‏‮airplane‬‏</string>
<string name="activity_type_atv">‏‮ATV‬‏</string>
<string name="activity_type_biking">‏‮biking‬‏</string>
<string name="activity_type_blimp">‏‮blimp‬‏</string>
<string name="activity_type_boat">‏‮boat‬‏</string>
<string name="activity_type_climbing">‏‮climbing‬‏</string>
<string name="activity_type_commercial_airplane">‏‮commercial‬‏ ‏‮airplane‬‏</string>
<string name="activity_type_cross_country_skiing">‏‮cross‬‏-‏‮country‬‏ ‏‮skiing‬‏</string>
<string name="activity_type_cycling">‏‮cycling‬‏</string>
<string name="activity_type_dirt_bike">‏‮dirt‬‏ ‏‮bike‬‏</string>
<string name="activity_type_donkey_back_riding">‏‮donkey‬‏ ‏‮back‬‏ ‏‮riding‬‏</string>
<string name="activity_type_driving">‏‮driving‬‏</string>
<string name="activity_type_driving_bus">‏‮driving‬‏ ‏‮bus‬‏</string>
<string name="activity_type_driving_car">‏‮driving‬‏ ‏‮car‬‏</string>
<string name="activity_type_ferry">‏‮ferry‬‏</string>
<string name="activity_type_frisbee">‏‮frisbee‬‏</string>
<string name="activity_type_gliding">‏‮gliding‬‏</string>
<string name="activity_type_hang_gliding">‏‮hang‬‏ ‏‮gliding‬‏</string>
<string name="activity_type_helicopter">‏‮helicopter‬‏</string>
<string name="activity_type_hiking">‏‮hiking‬‏</string>
<string name="activity_type_horse_back_riding">‏‮horse‬‏ ‏‮back‬‏ ‏‮riding‬‏</string>
<string name="activity_type_hot_air_balloon">‏‮hot‬‏ ‏‮air‬‏ ‏‮balloon‬‏</string>
<string name="activity_type_ice_sailing">‏‮ice‬‏ ‏‮sailing‬‏</string>
<string name="activity_type_inline_skating">‏‮inline‬‏ ‏‮skating‬‏</string>
<string name="activity_type_kayaking">‏‮kayaking‬‏</string>
<string name="activity_type_kite_surfing">‏‮kite‬‏ ‏‮surfing‬‏</string>
<string name="activity_type_land_sailing">‏‮land‬‏ ‏‮sailing‬‏</string>
<string name="activity_type_mixed_type">‏‮mixed‬‏ ‏‮type‬‏</string>
<string name="activity_type_motor_bike">‏‮motor‬‏ ‏‮bike‬‏</string>
<string name="activity_type_motor_boating">‏‮motor‬‏ ‏‮boating‬‏</string>
<string name="activity_type_mountain_biking">‏‮mountain‬‏ ‏‮biking‬‏</string>
<string name="activity_type_off_trail_hiking">‏‮off‬‏ ‏‮trail‬‏ ‏‮hiking‬‏</string>
<string name="activity_type_other">‏‮other‬‏</string>
<string name="activity_type_paddling">‏‮paddling‬‏</string>
<string name="activity_type_para_gliding">‏‮para‬‏ ‏‮gliding‬‏</string>
<string name="activity_type_rc_airplane">‏‮RC‬‏ ‏‮airplane‬‏</string>
<string name="activity_type_rc_boat">‏‮RC‬‏ ‏‮boat‬‏</string>
<string name="activity_type_rc_helicopter">‏‮RC‬‏ ‏‮helicopter‬‏</string>
<string name="activity_type_riding">‏‮riding‬‏</string>
<string name="activity_type_road_biking">‏‮road‬‏ ‏‮biking‬‏</string>
<string name="activity_type_roller_skiing">‏‮roller‬‏ ‏‮skiing‬‏</string>
<string name="activity_type_rowing">‏‮rowing‬‏</string>
<string name="activity_type_running">‏‮running‬‏</string>
<string name="activity_type_sailing">‏‮sailing‬‏</string>
<string name="activity_type_seaplane">‏‮seaplane‬‏</string>
<string name="activity_type_skate_boarding">‏‮skate‬‏ ‏‮boarding‬‏</string>
<string name="activity_type_skating">‏‮skating‬‏</string>
<string name="activity_type_skiing">‏‮skiing‬‏</string>
<string name="activity_type_sky_jumping">‏‮sky‬‏ ‏‮jumping‬‏</string>
<string name="activity_type_sled">‏‮sled‬‏</string>
<string name="activity_type_snow_boarding">‏‮snow‬‏ ‏‮boarding‬‏</string>
<string name="activity_type_snow_shoeing">‏‮snow‬‏ ‏‮shoeing‬‏</string>
<string name="activity_type_speed_walking">‏‮speed‬‏ ‏‮walking‬‏</string>
<string name="activity_type_street_running">‏‮street‬‏ ‏‮running‬‏</string>
<string name="activity_type_surfing">‏‮surfing‬‏</string>
<string name="activity_type_track_cycling">‏‮track‬‏ ‏‮cycling‬‏</string>
<string name="activity_type_track_running">‏‮track‬‏ ‏‮running‬‏</string>
<string name="activity_type_trail_hiking">‏‮trail‬‏ ‏‮hiking‬‏</string>
<string name="activity_type_trail_running">‏‮trail‬‏ ‏‮running‬‏</string>
<string name="activity_type_train">‏‮train‬‏</string>
<string name="activity_type_ultimate_frisbee">‏‮ultimate‬‏ ‏‮frisbee‬‏</string>
<string name="activity_type_unknown">‏‮unknown‬‏</string>
<string name="activity_type_wakeboarding">‏‮wakeboarding‬‏</string>
<string name="activity_type_walking">‏‮walking‬‏</string>
<string name="activity_type_water_skiing">‏‮water‬‏ ‏‮skiing‬‏</string>
<string name="activity_type_wind_surfing">‏‮wind‬‏ ‏‮surfing‬‏</string>
<string name="description_activity_type">‏‮Activity‬‏ ‏‮type‬‏: %1$s</string>
<string name="description_average_moving_pace_in_minute">‏‮Average‬‏ ‏‮moving‬‏ ‏‮pace‬‏: %1$s ‏‮min‬‏/‏‮km‬‏ (%2$s ‏‮min‬‏/‏‮mi‬‏)</string>
<string name="description_average_moving_speed">‏‮Average‬‏ ‏‮moving‬‏ ‏‮speed‬‏: %1$.2f ‏‮km‬‏/‏‮h‬‏ (%2$.1f ‏‮mi‬‏/‏‮h‬‏)</string>
<string name="description_average_pace_in_minute">‏‮Average‬‏ ‏‮pace‬‏: %1$s ‏‮min‬‏/‏‮km‬‏ (%2$s ‏‮min‬‏/‏‮mi‬‏)</string>
<string name="description_average_speed">‏‮Average‬‏ ‏‮speed‬‏: %1$.2f ‏‮km‬‏/‏‮h‬‏ (%2$.1f ‏‮mi‬‏/‏‮h‬‏)</string>
<string name="description_elevation_gain">‏‮Elevation‬‏ ‏‮gain‬‏: %1$d ‏‮m‬‏ (%2$d ‏‮ft‬‏)</string>
<string name="description_elevation_imperial">‏‮Elevation‬‏ (‏‮ft‬‏)</string>
<string name="description_elevation_metric">‏‮Elevation‬‏ (‏‮m‬‏)</string>
<string name="description_fastest_pace_in_minute">‏‮Fastest‬‏ ‏‮pace‬‏: %1$s ‏‮min‬‏/‏‮km‬‏ (%2$s ‏‮min‬‏/‏‮mi‬‏)</string>
<string name="description_location_accuracy">‏‮Accuracy‬‏ (‏‮m‬‏)</string>
<string name="description_location_altitude">‏‮Altitude‬‏ (‏‮m‬‏)</string>
<string name="description_location_bearing">‏‮Bearing‬‏ (‏‮deg‬‏)</string>
<string name="description_location_latitude">‏‮Latitude‬‏ (‏‮deg‬‏)</string>
<string name="description_location_longitude">‏‮Longitude‬‏ (‏‮deg‬‏)</string>
<string name="description_location_speed">‏‮Speed‬‏ (‏‮m‬‏/‏‮s‬‏)</string>
<string name="description_max_elevation">‏‮Max‬‏ ‏‮elevation‬‏: %1$d ‏‮m‬‏ (%2$d ‏‮ft‬‏)</string>
<string name="description_max_grade">‏‮Max‬‏ ‏‮grade‬‏: %1$d %%</string>
<string name="description_max_speed">‏‮Max‬‏ ‏‮speed‬‏: %1$.2f ‏‮km‬‏/‏‮h‬‏ (%2$.1f ‏‮mi‬‏/‏‮h‬‏)</string>
<string name="description_min_elevation">‏‮Min‬‏ ‏‮elevation‬‏: %1$d ‏‮m‬‏ (%2$d ‏‮ft‬‏)</string>
<string name="description_min_grade">‏‮Min‬‏ ‏‮grade‬‏: %1$d %%</string>
<string name="description_moving_time">‏‮Moving‬‏ ‏‮time‬‏: %1$s</string>
<string name="description_pace_imperial">‏‮Pace‬‏ (‏‮min‬‏/‏‮mi‬‏)</string>
<string name="description_pace_metric">‏‮Pace‬‏ (‏‮min‬‏/‏‮km‬‏)</string>
<string name="description_recorded_time">‏‮Recorded‬‏: %1$s</string>
<string name="description_sensor_cadence">‏‮Cadence‬‏ (‏‮rpm‬‏)</string>
<string name="description_sensor_heart_rate">‏‮Heart‬‏ ‏‮rate‬‏ (‏‮bpm‬‏)</string>
<string name="description_sensor_power">‏‮Power‬‏ (‏‮W‬‏)</string>
<string name="description_speed_imperial">‏‮Speed‬‏ (‏‮mi‬‏/‏‮hr‬‏)</string>
<string name="description_speed_metric">‏‮Speed‬‏ (‏‮km‬‏/‏‮hr‬‏)</string>
<string name="description_time">‏‮Time‬‏</string>
<string name="description_total_distance">‏‮Total‬‏ ‏‮distance‬‏: %1$.2f ‏‮km‬‏ (%2$.1f ‏‮mi‬‏)</string>
<string name="description_total_time">‏‮Total‬‏ ‏‮time‬‏: %1$s</string>
<string name="description_track_point">‏‮Point‬‏</string>
<string name="description_track_segment">‏‮Segment‬‏</string>
<string name="export_all_option">‏‮All‬‏ ‏‮as‬‏ %1$s ‏‮to‬‏ %2$s</string>
<string name="export_all_title">‏‮Export‬‏ ‏‮to‬‏ ‏‮external‬‏ ‏‮storage‬‏</string>
<string name="export_external_storage">‏‮External‬‏ ‏‮storage‬‏</string>
<string name="export_external_storage_error">‏‮Exported‬‏ %1$d ‏‮of‬‏ %2$s ‏‮to‬‏ %3$s</string>
<string name="export_external_storage_option">‏‮as‬‏ %1$s ‏‮to‬‏ %2$s</string>
<string name="export_external_storage_progress_message">‏‮Exporting‬‏ ‏‮to‬‏ %1$s…</string>
<string name="export_external_storage_success">‏‮Exported‬‏ %1$s ‏‮to‬‏ %2$s</string>
<string name="export_title">‏‮Export‬‏ ‏‮to‬‏…</string>
<string name="external_storage_not_available">‏‮External‬‏ ‏‮storage‬‏ ‏‮not‬‏ ‏‮available‬‏</string>
<string name="external_storage_not_writable">‏‮External‬‏ ‏‮storage‬‏ ‏‮not‬‏ ‏‮writable‬‏</string>
<string name="files_few">%1$d ‏‮files‬‏</string>
<string name="files_many">%1$d ‏‮files‬‏</string>
<string name="files_one">1 ‏‮file‬‏</string>
<string name="files_other">%1$d ‏‮files‬‏</string>
<string name="files_two">2 ‏‮files‬‏</string>
<string name="files_zero">0 ‏‮files‬‏</string>
<string name="generic_add">‏‮Add‬‏</string>
<string name="generic_cancel">‏‮Cancel‬‏</string>
<string name="generic_delete_all_confirm_title">‏‮Delete‬‏ ‏‮all‬‏?</string>
<string name="generic_delete_selected_confirm_title">‏‮Delete‬‏ ‏‮selected‬‏?</string>
<string name="generic_description">‏‮Description‬‏</string>
<string name="generic_description_line">‏‮Description‬‏: %1$s</string>
<string name="generic_error_title">‏‮Error‬‏</string>
<string name="generic_name">‏‮Name‬‏</string>
<string name="generic_name_line">‏‮Name‬‏: %1$s</string>
<string name="generic_no">‏‮No‬‏</string>
<string name="generic_ok">‏‮OK‬‏</string>
<string name="generic_paused">‏‮Paused‬‏</string>
<string name="generic_progress_title">‏‮Please‬‏ ‏‮wait‬‏…</string>
<string name="generic_recording">‏‮Recording‬‏…</string>
<string name="generic_save">‏‮Save‬‏</string>
<string name="generic_success_title">‏‮Success‬‏</string>
<string name="generic_tracks">‏‮Tracks‬‏</string>
<string name="generic_yes">‏‮Yes‬‏</string>
<string name="gps_location_access">‘‏‮Location‬‏ ‏‮access‬‏’</string>
<string name="gps_disabled">‏‮GPS‬‏ ‏‮not‬‏ ‏‮enabled‬‏. ‏‮Tap‬‏ ‏‮here‬‏ ‏‮to‬‏ ‏‮go‬‏ ‏‮to‬‏ %1$s. ‏‮Press‬‏ ‏‮the‬‏ ‏‮back‬‏ ‏‮button‬‏ ‏‮to‬‏ ‏‮return‬‏.</string>
<string name="gps_disabled_location_mode">‏‮Select‬‏ ‏‮high‬‏-‏‮accuracy‬‏ ‏‮location‬‏ ‏‮mode‬‏. ‏‮Tap‬‏ ‏‮here‬‏ ‏‮to‬‏ ‏‮go‬‏ ‏‮to‬‏ %1$s. ‏‮Press‬‏ ‏‮the‬‏ ‏‮back‬‏ ‏‮button‬‏ ‏‮to‬‏ ‏‮return‬‏.</string>
<string name="gps_disabled_my_location">‏‮Current‬‏ ‏‮location‬‏ ‏‮cannot‬‏ ‏‮be‬‏ ‏‮determined‬‏. ‏‮Enable‬‏ ‏‮more‬‏ ‏‮location‬‏ ‏‮sources‬‏ ‏‮at‬‏ %1$s &amp; ‏‮make‬‏ ‏‮sure‬‏ ‏‮the‬‏
‏‮sky‬‏ ‏‮is‬‏ ‏‮visible‬‏. ‏‮Try‬‏ ‏‮again‬‏ ‏‮later‬‏.</string>
<string name="gps_disabled_my_location_location_mode">‏‮Current‬‏ ‏‮location‬‏ ‏‮cannot‬‏ ‏‮be‬‏ ‏‮determined‬‏. ‏‮Select‬‏ ‏‮the‬‏ ‏‮high‬‏-‏‮accuracy‬‏ ‏‮location‬‏ ‏‮mode‬‏ ‏‮at‬‏ %1$s &amp; ‏‮make‬‏
‏‮sure‬‏ ‏‮the‬‏ ‏‮sky‬‏ ‏‮is‬‏ ‏‮visible‬‏. ‏‮Try‬‏ ‏‮again‬‏ ‏‮later‬‏.</string>
<string name="gps_not_found">‏‮No‬‏ ‏‮GPS‬‏ ‏‮is‬‏ ‏‮found‬‏ ‏‮on‬‏ ‏‮the‬‏ ‏‮device‬‏</string>
<string name="gps_starting">‏‮Starting‬‏ ‏‮GPS‬‏</string>
<string name="gps_starting_error">‏‮Unable‬‏ ‏‮to‬‏ ‏‮start‬‏ ‏‮GPS‬‏</string>
<string name="gps_stopping">‏‮Stopping‬‏ ‏‮GPS‬‏</string>
<string name="gps_wait_for_better_signal">‏‮Waiting‬‏ ‏‮for‬‏ ‏‮a‬‏ ‏‮better‬‏ ‏‮GPS‬‏ ‏‮signal‬‏</string>
<string name="gps_wait_for_signal">‏‮Waiting‬‏ ‏‮for‬‏ ‏‮GPS‬‏ ‏‮signal‬‏</string>
<string name="image_application">‏‮Application‬‏</string>
<string name="image_arrow">‏‮Arrow‬‏</string>
<string name="image_marker">‏‮Marker‬‏</string>
<string name="image_marker_count">‏‮Marker‬‏ ‏‮count‬‏</string>
<string name="image_my_location">‏‮My‬‏ ‏‮Location‬‏</string>
<string name="image_pause">‏‮Pause‬‏</string>
<string name="image_photo_marker">‏‮Photo‬‏ ‏‮marker‬‏</string>
<string name="image_record">‏‮Record‬‏</string>
<string name="image_stop">‏‮Stop‬‏</string>
<string name="image_track">‏‮Track‬‏</string>
<string name="import_error">‏‮Imported‬‏ %1$d ‏‮of‬‏ %2$s ‏‮from‬‏ %3$s</string>
<string name="import_no_directory">%1$s ‏‮does‬‏ ‏‮not‬‏ ‏‮exist‬‏</string>
<string name="import_no_file">‏‮There‬‏ ‏‮were‬‏ ‏‮no‬‏ ‏‮files‬‏ ‏‮to‬‏ ‏‮import‬‏. ‏‮Connect‬‏ ‏‮your‬‏ ‏‮device‬‏ ‏‮to‬‏ ‏‮your‬‏ ‏‮computer‬‏ ‏‮and‬‏ ‏‮place‬‏ ‏‮the‬‏ ‏‮files‬‏ ‏‮to‬‏
‏‮import‬‏ ‏‮in‬‏ %1$s.</string>
<string name="import_no_file_title">‏‮Import‬‏ ‏‮files‬‏</string>
<string name="import_progress_message">‏‮Importing‬‏ ‏‮from‬‏ %1$s…</string>
<string name="import_selection_option">‏‮All‬‏ %1$s ‏‮from‬‏ %2$s</string>
<string name="import_selection_title">‏‮Import‬‏ ‏‮from‬‏ ‏‮external‬‏ ‏‮storage‬‏</string>
<string name="import_success">‏‮Imported‬‏ %1$s ‏‮from‬‏ %2$s</string>
<string name="marker_add_error">‏‮Unable‬‏ ‏‮to‬‏ ‏‮insert‬‏ ‏‮a‬‏ ‏‮marker‬‏. ‏‮No‬‏ ‏‮GPS‬‏ ‏‮signal‬‏. ‏‮Try‬‏ ‏‮again‬‏.</string>
<string name="marker_add_success">‏‮A‬‏ ‏‮marker‬‏ ‏‮was‬‏ ‏‮inserted‬‏</string>
<string name="marker_add_canceled">‏‮Canceled‬‏, ‏‮no‬‏ ‏‮marker‬‏ ‏‮added‬‏</string>
<string name="marker_delete_all_confirm_message">‏‮All‬‏ ‏‮markers‬‏ ‏‮for‬‏ ‏‮this‬‏ ‏‮track‬‏ ‏‮will‬‏ ‏‮be‬‏ ‏‮permanently‬‏ ‏‮deleted‬‏ ‏‮from‬‏ ‏‮the‬‏ ‏‮device‬‏</string>
<string name="marker_delete_one_confirm_message">‏‮The‬‏ ‏‮marker‬‏ ‏‮will‬‏ ‏‮be‬‏ ‏‮permanently‬‏ ‏‮deleted‬‏ ‏‮from‬‏ ‏‮the‬‏ ‏‮device‬‏</string>
<string name="marker_delete_one_confirm_title">‏‮Delete‬‏ ‏‮marker‬‏?</string>
<string name="marker_delete_multiple_confirm_message">‏‮The‬‏ ‏‮selected‬‏ ‏‮markers‬‏ ‏‮will‬‏ ‏‮be‬‏ ‏‮permanently‬‏ ‏‮deleted‬‏ ‏‮from‬‏ ‏‮the‬‏ ‏‮device‬‏</string>
<string name="marker_label_end">%1$s (‏‮End‬‏)</string>
<string name="marker_label_start">%1$s (‏‮Start‬‏)</string>
<string name="marker_name_format">‏‮Marker‬‏ %1$d</string>
<string name="marker_split_name_format">‏‮Split‬‏ %1$d</string>
<string name="marker_title">%1$d ‏‮of‬‏ %2$d</string>
<string name="marker_detail_statistics_title">‏‮Since‬‏ ‏‮last‬‏ ‏‮split‬‏ ‏‮marker‬‏:</string>
<string name="marker_edit_marker_type">‏‮Marker‬‏ ‏‮type‬‏ (‏‮e‬‏.‏‮g‬‏. ‏‮waterfall‬‏)</string>
<string name="marker_edit_marker_type_hint">‏‮Marker‬‏ ‏‮type‬‏</string>
<string name="marker_list_empty_message">‏‮You‬‏ ‏‮don‬‏\'‏‮t‬‏ ‏‮have‬‏ ‏‮any‬‏ ‏‮markers‬‏</string>
<string name="menu_aggregated_statistics">‏‮Aggregated‬‏ ‏‮stats‬‏</string>
<string name="menu_delete">‏‮Delete‬‏</string>
<string name="menu_delete_all">‏‮Delete‬‏ ‏‮all‬‏</string>
<string name="menu_edit">‏‮Edit‬‏</string>
<string name="menu_export">‏‮Export‬‏</string>
<string name="menu_export_all">‏‮Export‬‏ ‏‮all‬‏</string>
<string name="menu_feedback">‏‮Feedback‬‏</string>
<string name="menu_help">‏‮Help‬‏</string>
<string name="menu_help_feedback">‏‮Help‬‏ &amp; ‏‮feedback‬‏</string>
<string name="menu_help_forum">‏‮Help‬‏ ‏‮forum‬‏</string>
<string name="menu_import_all">‏‮Import‬‏ ‏‮all‬‏</string>
<string name="menu_insert_marker">‏‮Insert‬‏ ‏‮marker‬‏</string>
<string name="menu_insert_photo">‏‮Insert‬‏ ‏‮photo‬‏</string>
<string name="menu_map">‏‮Map‬‏</string>
<string name="menu_markers">‏‮Markers‬‏</string>
<string name="menu_play">‏‮Play‬‏</string>
<string name="menu_search">‏‮Search‬‏</string>
<string name="menu_select_all">‏‮Select‬‏ ‏‮all‬‏</string>
<string name="menu_sensor_state">‏‮Sensor‬‏ ‏‮state‬‏</string>
<string name="menu_settings">‏‮Settings‬‏</string>
<string name="menu_share">‏‮Share‬‏</string>
<string name="menu_show_on_map">‏‮Show‬‏ ‏‮on‬‏ ‏‮map‬‏</string>
<string name="menu_split_frequency">‏‮Split‬‏ ‏‮frequency‬‏</string>
<string name="menu_start_gps">‏‮Start‬‏ ‏‮GPS‬‏</string>
<string name="menu_stop_gps">‏‮Stop‬‏ ‏‮GPS‬‏</string>
<string name="menu_view_photo">‏‮View‬‏ ‏‮photo‬‏</string>
<string name="menu_voice_frequency">‏‮Voice‬‏ ‏‮frequency‬‏</string>
<string name="app_name">OpenTracks</string>
<string name="recording_service">‏‮Recording‬‏ ‏‮service‬‏</string>
<string name="search_global_description">‏‮Tracks‬‏ ‏‮and‬‏ ‏‮markers‬‏</string>
<string name="search_list_empty_message">‏‮No‬‏ ‏‮tracks‬‏ ‏‮or‬‏ ‏‮markers‬‏ ‏‮matched‬‏ ‏‮your‬‏ ‏‮search‬‏</string>
<string name="search_list_marker_track_location">‏‮In‬‏ ‏‮track‬‏ ‘%1$s’.</string>
<string name="search_voice_prompt">‏‮Speak‬‏ ‏‮the‬‏ ‏‮name‬‏ ‏‮of‬‏ ‏‮a‬‏ ‏‮track‬‏ ‏‮or‬‏ ‏‮marker‬‏</string>
<string name="sensor_state_battery">‏‮Battery‬‏ ‏‮level‬‏</string>
<string name="sensor_state_cadence">‏‮Cadence‬‏</string>
<string name="sensor_state_cadence_value">%1$d ‏‮rpm‬‏</string>
<string name="sensor_state_connected">‏‮Connected‬‏</string>
<string name="sensor_state_connecting">‏‮Connecting‬‏</string>
<string name="sensor_state_disconnected">‏‮Disconnected‬‏</string>
<string name="sensor_state_heart_rate">‏‮Heart‬‏ ‏‮rate‬‏</string>
<string name="sensor_state_heart_rate_value">%1$d ‏‮bpm‬‏</string>
<string name="sensor_state_last_sensor_time">‏‮Last‬‏ ‏‮sensor‬‏ ‏‮time‬‏</string>
<string name="sensor_state_power">‏‮Power‬‏</string>
<string name="sensor_state_power_value">%1$d ‏‮W‬‏</string>
<string name="sensor_state_sending">‏‮Sending‬‏</string>
<string name="settings_not_while_recording">‏‮Not‬‏ ‏‮available‬‏ ‏‮while‬‏ ‏‮recording‬‏ ‏‮a‬‏ ‏‮track‬‏</string>
<string name="settings_show">‏‮Show‬‏</string>
<string name="settings_advanced">‏‮Advanced‬‏</string>
<string name="settings_chart_by_distance">‏‮By‬‏ ‏‮distance‬‏</string>
<string name="settings_chart_by_time">‏‮By‬‏ ‏‮time‬‏</string>
<string name="settings_chart_x_axis">‏‮X‬‏ ‏‮axis‬‏</string>
<string name="settings_recording">‏‮Recording‬‏</string>
<string name="settings_recording_auto_resume_track_timeout_always_summary">‏‮Always‬‏ ‏‮resume‬‏ ‏‮a‬‏ ‏‮recording‬‏ ‏‮after‬‏ ‏‮reboot‬‏</string>
<string name="settings_recording_auto_resume_track_timeout_never_summary">‏‮Never‬‏ ‏‮resume‬‏ ‏‮a‬‏ ‏‮recording‬‏ ‏‮after‬‏ ‏‮reboot‬‏</string>
<string name="settings_recording_auto_resume_track_timeout_summary">‏‮Resume‬‏ ‏‮a‬‏ ‏‮recording‬‏ ‏‮after‬‏ ‏‮reboot‬‏ ‏‮if‬‏ ‏‮the‬‏ ‏‮last‬‏ ‏‮recorded‬‏ ‏‮location‬‏ ‏‮was‬‏ ‏‮within‬‏ ‏‮the‬‏ ‏‮last‬‏ %1$s</string>
<string name="settings_recording_auto_resume_track_timeout_title">‏‮Auto‬‏-‏‮resume‬‏</string>
<string name="settings_recording_default_activity_title">‏‮Default‬‏ ‏‮activity‬‏ ‏‮type‬‏</string>
<string name="settings_recording_location_frequency_summary">%1$s ‏‮between‬‏ ‏‮recorded‬‏ ‏‮locations‬‏</string>
<string name="settings_recording_max_recording_distance_summary">‏‮A‬‏ ‏‮new‬‏ ‏‮segment‬‏ ‏‮is‬‏ ‏‮created‬‏ ‏‮if‬‏ ‏‮the‬‏ ‏‮distance‬‏ ‏‮between‬‏ ‏‮two‬‏ ‏‮locations‬‏ ‏‮is‬‏ ‏‮greater‬‏ ‏‮than‬‏ %1$s</string>
<string name="settings_recording_max_recording_distance_title">‏‮Max‬‏ ‏‮distance‬‏</string>
<string name="settings_recording_min_recording_distance_title">‏‮Recording‬‏ ‏‮distance‬‏ ‏‮interval‬‏</string>
<string name="settings_recording_min_recording_interval_title">‏‮Recording‬‏ ‏‮time‬‏ ‏‮interval‬‏</string>
<string name="settings_recording_min_required_accuracy_summary">‏‮Location‬‏ ‏‮is‬‏ ‏‮ignored‬‏ ‏‮if‬‏ ‏‮its‬‏ ‏‮accuracy‬‏ ‏‮is‬‏ ‏‮less‬‏ ‏‮than‬‏ %1$s</string>
<string name="settings_recording_min_required_accuracy_title">‏‮GPS‬‏ ‏‮accuracy‬‏</string>
<string name="settings_recording_track_name_date_iso_8601_option">‏‮Date‬‏ (‏‮ISO‬‏ 8601)</string>
<string name="settings_recording_track_name_date_local_option">‏‮Date‬‏ (‏‮local‬‏)</string>
<string name="settings_recording_track_name_number_option">‏‮Number‬‏</string>
<string name="settings_recording_track_name_title">‏‮Default‬‏ ‏‮track‬‏ ‏‮name‬‏</string>
<string name="settings_reset">‏‮Reset‬‏</string>
<string name="settings_reset_confirm_message">‏‮All‬‏ ‏‮settings‬‏ ‏‮will‬‏ ‏‮be‬‏ ‏‮reverted‬‏ ‏‮to‬‏ ‏‮the‬‏ ‏‮default‬‏ ‏‮values‬‏.</string>
<string name="settings_reset_confirm_title">‏‮Reset‬‏ ‏‮now‬‏?</string>
<string name="settings_reset_done">‏‮All‬‏ ‏‮settings‬‏ ‏‮have‬‏ ‏‮been‬‏ ‏‮reverted‬‏ ‏‮to‬‏ ‏‮the‬‏ ‏‮default‬‏ ‏‮values‬‏</string>
<string name="settings_sensor">‏‮Sensor‬‏</string>
<string name="settings_sensor_ant_reset_bike_cadence_sensor">‏‮Reset‬‏ ‏‮bike‬‏ ‏‮cadence‬‏</string>
<string name="settings_sensor_ant_reset_combined_bike_sensor">‏‮Reset‬‏ ‏‮combined‬‏ ‏‮bike‬‏</string>
<string name="settings_sensor_ant_reset_heart_rate_monitor">‏‮Reset‬‏ ‏‮heart‬‏ ‏‮rate‬‏</string>
<string name="settings_sensor_ant_reset_speed_distance_monitor">‏‮Reset‬‏ ‏‮foot‬‏ ‏‮pod‬‏</string>
<string name="settings_sensor_bluetooth">‏‮Bluetooth‬‏</string>
<string name="settings_sensor_bluetooth_pairing">‏‮Bluetooth‬‏ ‏‮pairing‬‏</string>
<string name="settings_sensor_bluetooth_pairing_summary">‏‮Open‬‏ ‏‮Android‬‏\'‏‮s‬‏ ‏‮Bluetooth‬‏ ‏‮settings‬‏ ‏‮to‬‏ ‏‮pair‬‏ ‏‮a‬‏ ‏‮sensor‬‏</string>
<string name="settings_sensor_bluetooth_sensor">‏‮Bluetooth‬‏ ‏‮sensor‬‏</string>
<string name="settings_sensor_connected">‏‮Connected‬‏ ‏‮to‬‏ %1$s</string>
<string name="settings_stats_coordinate">‏‮Latitude‬‏/‏‮Longitude‬‏</string>
<string name="settings_stats_grade_elevation">‏‮Grade‬‏/‏‮Elevation‬‏</string>
<string name="settings_stats_rate_title">‏‮Preferred‬‏ ‏‮rate‬‏</string>
<string name="settings_stats_units_imperial">‏‮Imperial‬‏ (‏‮mi‬‏, ‏‮ft‬‏)</string>
<string name="settings_stats_units_metric">‏‮Metric‬‏ (‏‮km‬‏, ‏‮m‬‏)</string>
<string name="settings_stats_units_title">‏‮Preferred‬‏ ‏‮units‬‏</string>
<string name="share_track_emails_hint">‏‮Email‬‏ ‏‮addresses‬‏</string>
<string name="share_track_picker_title">‏‮Select‬‏ ‏‮an‬‏ ‏‮application‬‏</string>
<string name="share_track_share_file">‏‮Share‬‏ ‏‮track‬‏ ‏‮file‬‏</string>
<string name="share_track_share_file_body">‏‮I‬‏ ‏‮think‬‏ ‏‮you‬‏ ‏‮might‬‏ ‏‮be‬‏ ‏‮interested‬‏ ‏‮in‬‏ ‏‮this‬‏ ‏‮track‬‏.\‏‮n‬‏\‏‮nTo‬‏ ‏‮view‬‏ ‏‮this‬‏ ‏‮track‬‏, ‏‮use‬‏ ‏‮Google‬‏ ‏‮Earth‬‏ ‏‮or‬‏ ‏‮other‬‏
‏‮mapping‬‏ ‏‮software‬‏.\‏‮n‬‏\‏‮n‬‏%1$s</string>
<string name="share_track_subject">‏‮I‬‏ ‏‮would‬‏ ‏‮like‬‏ ‏‮to‬‏ ‏‮share‬‏ ‏‮a‬‏ ‏‮track‬‏ ‏‮with‬‏ ‏‮you‬‏</string>
<string name="stats_average_moving_pace">‏‮Avg‬‏ ‏‮moving‬‏ ‏‮pace‬‏</string>
<string name="stats_average_moving_speed">‏‮Avg‬‏ ‏‮moving‬‏ ‏‮speed‬‏</string>
<string name="stats_average_pace">‏‮Avg‬‏ ‏‮pace‬‏</string>
<string name="stats_average_speed">‏‮Avg‬‏ ‏‮speed‬‏</string>
<string name="stats_elevation">‏‮Elevation‬‏</string>
<string name="stats_fastest_pace">‏‮Fastest‬‏ ‏‮pace‬‏</string>
<string name="stats_gain">‏‮Gain‬‏</string>
<string name="stats_grade">‏‮Grade‬‏</string>
<string name="stats_latitude">‏‮Latitude‬‏</string>
<string name="stats_longitude">‏‮Longitude‬‏</string>
<string name="stats_max">‏‮Max‬‏</string>
<string name="stats_max_speed">‏‮Max‬‏ ‏‮speed‬‏</string>
<string name="stats_min">‏‮Min‬‏</string>
<string name="stats_moving_time">‏‮Moving‬‏ ‏‮time‬‏</string>
<string name="stats_pace">‏‮Pace‬‏</string>
<string name="stats_speed">‏‮Speed‬‏</string>
<string name="stats_distance">‏‮Distance‬‏</string>
<string name="stats_total_time">‏‮Total‬‏ ‏‮time‬‏</string>
<string name="track_name_format">‏‮Track‬‏ %1$d</string>
<string name="track_record_notification">‏‮Recording‬‏ ‏‮your‬‏ ‏‮track‬‏…</string>
<string name="track_markers">%1$s ‏‮Markers‬‏</string>
<string name="track_delete_one_confirm_message">‏‮The‬‏ ‏‮track‬‏ ‏‮and‬‏ ‏‮its‬‏ ‏‮markers‬‏ ‏‮will‬‏ ‏‮be‬‏ ‏‮permanently‬‏ ‏‮deleted‬‏ ‏‮from‬‏ ‏‮the‬‏ ‏‮device‬‏.</string>
<string name="track_delete_one_confirm_title">‏‮Delete‬‏ ‏‮track‬‏?</string>
<string name="track_delete_multiple_confirm_message">‏‮The‬‏ ‏‮selected‬‏ ‏‮tracks‬‏ ‏‮and‬‏ ‏‮their‬‏ ‏‮markers‬‏ ‏‮will‬‏ ‏‮be‬‏ ‏‮permanently‬‏ ‏‮deleted‬‏ ‏‮from‬‏ ‏‮the‬‏ ‏‮device‬‏.</string>
<string name="track_delete_progress_message">‏‮Deleting‬‏…</string>
<string name="track_detail_chart_tab">‏‮Chart‬‏</string>
<string name="track_detail_stats_tab">‏‮Stats‬‏</string>
<string name="track_edit_activity_type">‏‮Activity‬‏ ‏‮type‬‏ (‏‮e‬‏.‏‮g‬‏. ‏‮hiking‬‏)</string>
<string name="track_edit_activity_type_hint">‏‮Activity‬‏ ‏‮type‬‏</string>
<string name="track_edit_new_track_title">‏‮New‬‏ ‏‮Track‬‏</string>
<string name="track_list_empty_message">‏‮Start‬‏ ‏‮recording‬‏ ‏‮your‬‏ ‏‮next‬‏ ‏‮adventure‬‏ ‏‮here‬‏</string>
<string name="track_list_record_error">‏‮Unable‬‏ ‏‮to‬‏ ‏‮start‬‏ ‏‮recording‬‏. ‏‮No‬‏ ‏‮GPS‬‏ ‏‮signal‬‏. ‏‮Try‬‏ ‏‮again‬‏.</string>
<string name="track_list_record_success">‏‮Now‬‏ ‏‮recording‬‏ ‏‮a‬‏ ‏‮new‬‏ ‏‮track‬‏</string>
<string name="track_widget_config_title">‏‮Configure‬‏ ‏‮Widget‬‏ ‏‮Items‬‏</string>
<string name="tracks_few">%1$d ‏‮tracks‬‏</string>
<string name="tracks_many">%1$d ‏‮tracks‬‏</string>
<string name="tracks_one">1 ‏‮track‬‏</string>
<string name="tracks_other">%1$d ‏‮tracks‬‏</string>
<string name="tracks_two">2 ‏‮tracks‬‏</string>
<string name="tracks_zero">0 ‏‮tracks‬‏</string>
<string name="unit_feet">‏‮ft‬‏</string>
<string name="unit_kilometer">‏‮km‬‏</string>
<string name="unit_kilometer_per_hour">‏‮km‬‏/‏‮h‬‏</string>
<string name="unit_meter">‏‮m‬‏</string>
<string name="unit_mile">‏‮mi‬‏</string>
<string name="unit_mile_per_hour">‏‮mi‬‏/‏‮h‬‏</string>
<string name="unit_minute_per_kilometer">‏‮min‬‏/‏‮km‬‏</string>
<string name="unit_minute_per_mile">‏‮min‬‏/‏‮mi‬‏</string>
<string name="value_adapt_accuracy">‏‮Adapt‬‏ ‏‮for‬‏ ‏‮accuracy‬‏</string>
<string name="value_adapt_battery_life">‏‮Adapt‬‏ ‏‮for‬‏ ‏‮battery‬‏ ‏‮life‬‏</string>
<string name="value_always">‏‮Always‬‏</string>
<string name="value_float_feet">%1$.2f ‏‮ft‬‏</string>
<string name="value_float_kilometer">%1$.2f ‏‮km‬‏</string>
<string name="value_float_meter">%1$.2f ‏‮m‬‏</string>
<string name="value_float_mile">%1$.2f ‏‮mi‬‏</string>
<string name="value_float_mile_poor_gps">%1$.1f ‏‮mi‬‏ (‏‮poor‬‏ ‏‮GPS‬‏)</string>
<string name="value_integer_feet">%1$d ‏‮ft‬‏</string>
<string name="value_integer_feet_excellent_gps">%1$d ‏‮ft‬‏ (‏‮excellent‬‏ ‏‮GPS‬‏)</string>
<string name="value_integer_feet_recommended">%1$d ‏‮ft‬‏ (‏‮recommended‬‏)</string>
<string name="value_integer_kilobyte">%1$d ‏‮kB‬‏</string>
<string name="value_integer_kilometer">%1$d ‏‮km‬‏</string>
<string name="value_integer_kilometer_hour">%1$d ‏‮km‬‏/‏‮h‬‏</string>
<string name="value_integer_megabyte">%1$d ‏‮MB‬‏</string>
<string name="value_integer_meter">%1$d ‏‮m‬‏</string>
<string name="value_integer_meter_excellent_gps">%1$d ‏‮m‬‏ (‏‮excellent‬‏ ‏‮GPS‬‏)</string>
<string name="value_integer_meter_poor_gps">%1$d ‏‮m‬‏ (‏‮poor‬‏ ‏‮GPS‬‏)</string>
<string name="value_integer_meter_recommended">%1$d ‏‮m‬‏ (‏‮recommended‬‏)</string>
<string name="value_integer_mile">%1$d ‏‮mi‬‏</string>
<string name="value_integer_mile_hour">%1$d ‏‮mi‬‏/‏‮h‬‏</string>
<string name="value_integer_minute">%1$d ‏‮min‬‏</string>
<string name="value_integer_percent">%1$d %%</string>
<string name="value_integer_second">%1$d ‏‮s‬‏</string>
<string name="value_never">‏‮Never‬‏</string>
<string name="value_none">‏‮None‬‏</string>
<string name="value_off">‏‮Off‬‏</string>
<string name="value_smallest">‏‮Smallest‬‏</string>
<string name="value_smallest_recommended">‏‮Smallest‬‏ (‏‮recommended‬‏)</string>
<string name="voice_hours_few">%1$d ‏‮hours‬‏</string>
<string name="voice_hours_many">%1$d ‏‮hours‬‏</string>
<string name="voice_hours_one">1 ‏‮hour‬‏</string>
<string name="voice_hours_other">%1$d ‏‮hours‬‏</string>
<string name="voice_hours_two">2 ‏‮hours‬‏</string>
<string name="voice_hours_zero">0 ‏‮hours‬‏</string>
<string name="voice_minutes_few">%1$d ‏‮minutes‬‏</string>
<string name="voice_minutes_many">%1$d ‏‮minutes‬‏</string>
<string name="voice_minutes_one">1 ‏‮minute‬‏</string>
<string name="voice_minutes_other">%1$d ‏‮minutes‬‏</string>
<string name="voice_minutes_two">2 ‏‮minutes‬‏</string>
<string name="voice_minutes_zero">0 ‏‮minutes‬‏</string>
<string name="voice_pace_per_kilometer">%1$s ‏‮per‬‏ ‏‮kilometer‬‏</string>
<string name="voice_pace_per_mile">%1$s ‏‮per‬‏ ‏‮mile‬‏</string>
<string name="voice_seconds_few">%1$d ‏‮seconds‬‏</string>
<string name="voice_seconds_many">%1$d ‏‮seconds‬‏</string>
<string name="voice_seconds_one">1 ‏‮second‬‏</string>
<string name="voice_seconds_other">%1$d ‏‮seconds‬‏</string>
<string name="voice_seconds_two">2 ‏‮seconds‬‏</string>
<string name="voice_seconds_zero">0 ‏‮seconds‬‏</string>
<string name="voice_speed_kilometers_per_hour_few">%1$.1f ‏‮kilometers‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_kilometers_per_hour_many">%1$.1f ‏‮kilometers‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_kilometers_per_hour_one">1 ‏‮kilometer‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_kilometers_per_hour_other">%1$.1f ‏‮kilometers‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_kilometers_per_hour_two">2 ‏‮kilometers‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_kilometers_per_hour_zero">0 ‏‮kilometers‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_miles_per_hour_few">%1$.1f ‏‮miles‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_miles_per_hour_many">%1$.1f ‏‮miles‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_miles_per_hour_one">1 ‏‮mile‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_miles_per_hour_other">%1$.1f ‏‮miles‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_miles_per_hour_two">2 ‏‮miles‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_speed_miles_per_hour_zero">0 ‏‮miles‬‏ ‏‮per‬‏ ‏‮hour‬‏</string>
<string name="voice_template">‏‮My‬‏ ‏‮Tracks‬‏ %1$s ‏‮in‬‏ %2$s ‏‮at‬‏ %3$s</string>
<string name="voice_total_distance_kilometers_few">‏‮total‬‏ ‏‮distance‬‏ %1$.2f ‏‮kilometers‬‏</string>
<string name="voice_total_distance_kilometers_many">‏‮total‬‏ ‏‮distance‬‏ %1$.2f ‏‮kilometers‬‏</string>
<string name="voice_total_distance_kilometers_one">‏‮total‬‏ ‏‮distance‬‏ 1 ‏‮kilometer‬‏</string>
<string name="voice_total_distance_kilometers_other">‏‮total‬‏ ‏‮distance‬‏ %1$.2f ‏‮kilometers‬‏</string>
<string name="voice_total_distance_kilometers_two">‏‮total‬‏ ‏‮distance‬‏ 2 ‏‮kilometers‬‏</string>
<string name="voice_total_distance_kilometers_zero">‏‮total‬‏ ‏‮distance‬‏ 0 ‏‮kilometers‬‏</string>
<string name="voice_total_distance_miles_few">‏‮total‬‏ ‏‮distance‬‏ %1$.2f ‏‮miles‬‏</string>
<string name="voice_total_distance_miles_many">‏‮total‬‏ ‏‮distance‬‏ %1$.2f ‏‮miles‬‏</string>
<string name="voice_total_distance_miles_one">‏‮total‬‏ ‏‮distance‬‏ 1 ‏‮mile‬‏</string>
<string name="voice_total_distance_miles_other">‏‮total‬‏ ‏‮distance‬‏ %1$.2f ‏‮miles‬‏</string>
<string name="voice_total_distance_miles_two">‏‮total‬‏ ‏‮distance‬‏ 2 ‏‮miles‬‏</string>
<string name="voice_total_distance_miles_zero">‏‮total‬‏ ‏‮distance‬‏ 0 ‏‮miles‬‏</string>
<string name="voice_total_distance_zero">‏‮My‬‏ ‏‮Tracks‬‏ ‏‮total‬‏ ‏‮distance‬‏ ‏‮zero‬‏.</string>
<string name="waypoint_type_atm">‏‮ATM‬‏</string>
<string name="waypoint_type_bank">‏‮bank‬‏</string>
<string name="waypoint_type_bar">‏‮bar‬‏</string>
<string name="waypoint_type_barbecue">‏‮barbecue‬‏</string>
<string name="waypoint_type_building">‏‮building‬‏</string>
<string name="waypoint_type_bus_stop">‏‮bus‬‏ ‏‮stop‬‏</string>
<string name="waypoint_type_cafe">‏‮cafe‬‏</string>
<string name="waypoint_type_campfire">‏‮campfire‬‏</string>
<string name="waypoint_type_campground">‏‮campground‬‏</string>
<string name="waypoint_type_church">‏‮church‬‏</string>
<string name="waypoint_type_coffee_house">‏‮coffee‬‏ ‏‮house‬‏</string>
<string name="waypoint_type_convenience_store">‏‮convenience‬‏ ‏‮store‬‏</string>
<string name="waypoint_type_drinking_water">‏‮drinking‬‏ ‏‮water‬‏</string>
<string name="waypoint_type_falling_rocks">‏‮falling‬‏ ‏‮rocks‬‏</string>
<string name="waypoint_type_ferry">‏‮ferry‬‏</string>
<string name="waypoint_type_fire_place">‏‮fire‬‏ ‏‮place‬‏</string>
<string name="waypoint_type_fire_station">‏‮fire‬‏ ‏‮station‬‏</string>
<string name="waypoint_type_fishing">‏‮fishing‬‏</string>
<string name="waypoint_type_gas_station">‏‮gas‬‏ ‏‮station‬‏</string>
<string name="waypoint_type_golf_course">‏‮golf‬‏ ‏‮course‬‏</string>
<string name="waypoint_type_grocery_store">‏‮grocery‬‏ ‏‮store‬‏</string>
<string name="waypoint_type_hair_dresser">‏‮hair‬‏ ‏‮dresser‬‏</string>
<string name="waypoint_type_harbor">‏‮harbor‬‏</string>
<string name="waypoint_type_hiking_path">‏‮hiking‬‏ ‏‮path‬‏</string>
<string name="waypoint_type_hotel">‏‮hotel‬‏</string>
<string name="waypoint_type_house">‏‮house‬‏</string>
<string name="waypoint_type_information">‏‮information‬‏</string>
<string name="waypoint_type_intersection">‏‮intersection‬‏</string>
<string name="waypoint_type_mail_box">‏‮mail‬‏ ‏‮box‬‏</string>
<string name="waypoint_type_market">‏‮market‬‏</string>
<string name="waypoint_type_meeting_point">‏‮meeting‬‏ ‏‮point‬‏</string>
<string name="waypoint_type_opera">‏‮opera‬‏</string>
<string name="waypoint_type_parking">‏‮parking‬‏</string>
<string name="waypoint_type_police_station">‏‮police‬‏ ‏‮station‬‏</string>
<string name="waypoint_type_post_office">‏‮post‬‏ ‏‮office‬‏</string>
<string name="waypoint_type_pothole">‏‮pothole‬‏</string>
<string name="waypoint_type_railway_crossing">‏‮railway‬‏ ‏‮crossing‬‏</string>
<string name="waypoint_type_restaurant">‏‮restaurant‬‏</string>
<string name="waypoint_type_shopping">‏‮shopping‬‏</string>
<string name="waypoint_type_sight">‏‮sight‬‏</string>
<string name="waypoint_type_store">‏‮store‬‏</string>
<string name="waypoint_type_swimming_pool">‏‮swimming‬‏ ‏‮pool‬‏</string>
<string name="waypoint_type_telephone">‏‮telephone‬‏</string>
<string name="waypoint_type_theater">‏‮theater‬‏</string>
<string name="waypoint_type_toilet">‏‮toilet‬‏</string>
<string name="waypoint_type_tourist_information">‏‮tourist‬‏ ‏‮information‬‏</string>
<string name="waypoint_type_tree">‏‮tree‬‏</string>
<string name="waypoint_type_tunnel">‏‮tunnel‬‏</string>
<string name="waypoint_type_waterfall">‏‮waterfall‬‏</string>
<string name="waypoint_type_water_fountain">‏‮water‬‏ ‏‮fountain‬‏</string>
<string name="waypoint_type_waypoint">‏‮waypoint‬‏</string>
</resources>
+2 -2
View File
@@ -100,7 +100,7 @@
<string name="description_total_time">الوقت الإجمالي: %1$s</string>
<string name="description_track_point">نقطة</string>
<string name="description_track_segment">جزء</string>
<string name="export_all_option">الكل كـ ‏%1$s‏ إلى‏ %2$s</string>
<string name="export_all_option">الكل كـ ‏%1$s‏</string>
<string name="export_all_title">تصدير إلى وحدة التخزين الخارجية</string>
<string name="export_external_storage">وحدة التخزين الخارجية</string>
<string name="export_external_storage_error">تم تصدير %1$d‏ من %2$s‏ إلى %3$s</string>
@@ -160,7 +160,7 @@
<string name="import_no_file">لا توجد ملفات للاستيراد. وصّل جهازك إلى جهاز الكمبيوتر واحفظ الملفات للاستيراد في %1$s.</string>
<string name="import_no_file_title">استيراد الملفات</string>
<string name="import_progress_message">جارٍ الاستيراد من %1$s…</string>
<string name="import_selection_option">كل‏ %1$s‏ من‏ %2$s</string>
<string name="import_selection_option">كل‏ %1$s‏</string>
<string name="import_selection_title">استيراد من وحدة التخزين الخارجية</string>
<string name="import_success">تم استيراد‏ %1$s‏ من‏ %2$s</string>
<string name="marker_add_error">يتعذر إدراج مُحدد. ليس هناك إشارة لنظام تحديد المواقع العالمي (GPS). حاول مرة أخرى.</string>
@@ -100,7 +100,7 @@
<string name="description_total_time">Total time: %1$s</string>
<string name="description_track_point">Point</string>
<string name="description_track_segment">Segment</string>
<string name="export_all_option">All as %1$s to %2$s</string>
<string name="export_all_option">All as %1$s</string>
<string name="export_all_title">Export to external storage</string>
<string name="export_external_storage">External storage</string>
<string name="export_external_storage_error">Exported %1$d of %2$s to %3$s</string>
@@ -160,7 +160,7 @@
<string name="import_no_file">There were no files to import. Connect your device to your computer and place the files to be imported in %1$s.</string>
<string name="import_no_file_title">Import files</string>
<string name="import_progress_message">Importing from %1$s…</string>
<string name="import_selection_option">All %1$s from %2$s</string>
<string name="import_selection_option">All %1$s</string>
<string name="import_selection_title">Import from external storage</string>
<string name="import_success">Imported %1$s from %2$s</string>
<string name="marker_add_error">Unable to insert a marker. No GPS signal. Try again.</string>
@@ -100,7 +100,7 @@
<string name="description_total_time">Tiempo total: %1$s.</string>
<string name="description_track_point">Punto</string>
<string name="description_track_segment">Segmento</string>
<string name="export_all_option">Todo como %1$s a %2$s</string>
<string name="export_all_option">Todo como %1$s</string>
<string name="export_all_title">Exportar a unidad de almacenamiento externa</string>
<string name="export_external_storage">Unidad de almacenamiento externa</string>
<string name="export_external_storage_error">Se exportaron %1$d de %2$s a %3$s.</string>
@@ -160,7 +160,7 @@
<string name="import_no_file">No hay archivos para importar. Conecta la unidad a la computadora y pasa los archivos que deseas importar a %1$s.</string>
<string name="import_no_file_title">Importar archivos</string>
<string name="import_progress_message">Importando desde %1$s…</string>
<string name="import_selection_option">Todos los archivos %1$s de %2$s</string>
<string name="import_selection_option">Todos los archivos %1$s</string>
<string name="import_selection_title">Importar desde la unidad de almacenamiento externa</string>
<string name="import_success">Se importaron %1$s desde %2$s.</string>
<string name="marker_add_error">No se puede insertar un marcador. No se recibe señal de GPS. Vuelve a intentarlo.</string>
+2 -2
View File
@@ -100,7 +100,7 @@
<string name="description_total_time">Общо време: %1$s</string>
<string name="description_track_point">Точка</string>
<string name="description_track_segment">Сегмент</string>
<string name="export_all_option">Всичко като %1$s в/ъв %2$s</string>
<string name="export_all_option">Всичко като %1$s</string>
<string name="export_all_title">Експортиране във външно хранилище</string>
<string name="export_external_storage">Външно хранилище</string>
<string name="export_external_storage_error">Експортирахте %1$d от %2$s в/ъв %3$s.</string>
@@ -161,7 +161,7 @@
да се импортират в/ъв %1$s.</string>
<string name="import_no_file_title">Импортиране на файлове</string>
<string name="import_progress_message">Импортира се от %1$s…</string>
<string name="import_selection_option">Всички %1$s файлове от %2$s</string>
<string name="import_selection_option">Всички %1$s файлове</string>
<string name="import_selection_title">Импортиране от външно хранилище</string>
<string name="import_success">Импортирахте %1$s от %2$s.</string>
<string name="marker_add_error">Маркерът не може да се вмъкне. Няма сигнал за GPS. Опитайте отново.</string>
-453
View File
@@ -1,453 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_type_airplane">fly</string>
<string name="activity_type_atv">ATV</string>
<string name="activity_type_biking">biking</string>
<string name="activity_type_blimp">blimp (luftskip)</string>
<string name="activity_type_boat">boat</string>
<string name="activity_type_climbing">klatring</string>
<string name="activity_type_commercial_airplane">passasjerfly</string>
<string name="activity_type_cross_country_skiing">cross-country skiing</string>
<string name="activity_type_cycling">sykling</string>
<string name="activity_type_dirt_bike">dirt bike</string>
<string name="activity_type_donkey_back_riding">eselridning</string>
<string name="activity_type_driving">driving</string>
<string name="activity_type_driving_bus">busskjøring</string>
<string name="activity_type_driving_car">bilkjøring</string>
<string name="activity_type_ferry">ferge</string>
<string name="activity_type_frisbee">frisbee</string>
<string name="activity_type_gliding">seilflyvning</string>
<string name="activity_type_hang_gliding">hanggliding</string>
<string name="activity_type_helicopter">helikopter</string>
<string name="activity_type_hiking">fotturer</string>
<string name="activity_type_horse_back_riding">hesteridning</string>
<string name="activity_type_hot_air_balloon">varmluftsballong</string>
<string name="activity_type_ice_sailing">isseiling</string>
<string name="activity_type_inline_skating">inline-skøyter</string>
<string name="activity_type_kayaking">kajakkpadling</string>
<string name="activity_type_kite_surfing">kitesurfing</string>
<string name="activity_type_land_sailing">landseiling</string>
<string name="activity_type_mixed_type">blandet type</string>
<string name="activity_type_motor_bike">motorsykkel</string>
<string name="activity_type_motor_boating">motorbåtkjøring</string>
<string name="activity_type_mountain_biking">terrengsykling</string>
<string name="activity_type_off_trail_hiking">fotturer utenfor sti</string>
<string name="activity_type_other">andre</string>
<string name="activity_type_paddling">padling</string>
<string name="activity_type_para_gliding">paragliding</string>
<string name="activity_type_rc_airplane">Radiostyrt fly</string>
<string name="activity_type_rc_boat">Radiostyrt båt</string>
<string name="activity_type_rc_helicopter">Radiostyrt helikopter</string>
<string name="activity_type_riding">ridning</string>
<string name="activity_type_road_biking">sykling</string>
<string name="activity_type_roller_skiing">roller skiing</string>
<string name="activity_type_rowing">roing</string>
<string name="activity_type_running">jogging</string>
<string name="activity_type_sailing">seiling</string>
<string name="activity_type_seaplane">sjøfly</string>
<string name="activity_type_skate_boarding">skateboarding</string>
<string name="activity_type_skating">skøyter</string>
<string name="activity_type_skiing">skisport</string>
<string name="activity_type_sky_jumping">fallskjermhopping</string>
<string name="activity_type_sled">sledekjøring</string>
<string name="activity_type_snow_boarding">snowboardkjøring</string>
<string name="activity_type_snow_shoeing">truger</string>
<string name="activity_type_speed_walking">hurtig gange</string>
<string name="activity_type_street_running">gateløp</string>
<string name="activity_type_surfing">surfing</string>
<string name="activity_type_track_cycling">banesykling</string>
<string name="activity_type_track_running">baneløp</string>
<string name="activity_type_trail_hiking">fottur på sti</string>
<string name="activity_type_trail_running">terrengløp</string>
<string name="activity_type_train">tog</string>
<string name="activity_type_ultimate_frisbee">ultimate frisbee</string>
<string name="activity_type_unknown">ukjent</string>
<string name="activity_type_wakeboarding">wakeboarding</string>
<string name="activity_type_walking">gange</string>
<string name="activity_type_water_skiing">vannski</string>
<string name="activity_type_wind_surfing">vindsurfing</string>
<string name="description_activity_type">Activity type: %1$s</string>
<string name="description_average_moving_pace_in_minute">Average moving pace: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_moving_speed">Average moving speed: %1$.2f km/h (%2$.1f mi/h)</string>
<string name="description_average_pace_in_minute">Average pace: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_speed">Average speed: %1$.2f km/h (%2$.1f mi/h)</string>
<string name="description_elevation_gain">Elevation gain: %1$d m (%2$d ft)</string>
<string name="description_elevation_imperial">Elevation (ft)</string>
<string name="description_elevation_metric">Elevation (m)</string>
<string name="description_fastest_pace_in_minute">Fastest pace: %1$s min/km (%2$s min/mi)</string>
<string name="description_location_accuracy">Accuracy (m)</string>
<string name="description_location_altitude">Altitude (m)</string>
<string name="description_location_bearing">Bearing (deg)</string>
<string name="description_location_latitude">Latitude (deg)</string>
<string name="description_location_longitude">Longitude (deg)</string>
<string name="description_location_speed">Speed (m/s)</string>
<string name="description_max_elevation">Max elevation: %1$d m (%2$d ft)</string>
<string name="description_max_grade">Max grade: %1$d %%</string>
<string name="description_max_speed">Max speed: %1$.2f km/h (%2$.1f mi/h)</string>
<string name="description_min_elevation">Min elevation: %1$d m (%2$d ft)</string>
<string name="description_min_grade">Min grade: %1$d %%</string>
<string name="description_moving_time">Moving time: %1$s</string>
<string name="description_pace_imperial">Pace (min/mi)</string>
<string name="description_pace_metric">Pace (min/km)</string>
<string name="description_recorded_time">Recorded: %1$s</string>
<string name="description_sensor_cadence">Cadence (rpm)</string>
<string name="description_sensor_heart_rate">Heart rate (bpm)</string>
<string name="description_sensor_power">Power (W)</string>
<string name="description_speed_imperial">Speed (mi/hr)</string>
<string name="description_speed_metric">Speed (km/hr)</string>
<string name="description_time">Time</string>
<string name="description_total_distance">Total distance: %1$.2f km (%2$.1f mi)</string>
<string name="description_total_time">Total time: %1$s</string>
<string name="description_track_point">Point</string>
<string name="description_track_segment">Segment</string>
<string name="export_all_option">All as %1$s to %2$s</string>
<string name="export_all_title">Export to external storage</string>
<string name="export_external_storage">External storage</string>
<string name="export_external_storage_error">Exported %1$d of %2$s to %3$s</string>
<string name="export_external_storage_option">as %1$s to %2$s</string>
<string name="export_external_storage_progress_message">Exporting to %1$s…</string>
<string name="export_external_storage_success">Exported %1$s to %2$s</string>
<string name="export_title">Export to…</string>
<string name="external_storage_not_available">External storage not available</string>
<string name="external_storage_not_writable">External storage not writable</string>
<string name="files_few">%1$d files</string>
<string name="files_many">%1$d files</string>
<string name="files_one">1 file</string>
<string name="files_other">%1$d files</string>
<string name="files_two">2 files</string>
<string name="files_zero">0 files</string>
<string name="generic_add">Add</string>
<string name="generic_cancel">Avbryt</string>
<string name="generic_delete_all_confirm_title">Delete all?</string>
<string name="generic_delete_selected_confirm_title">Delete selected?</string>
<string name="generic_description">Description</string>
<string name="generic_description_line">Description: %1$s</string>
<string name="generic_error_title">Feil</string>
<string name="generic_name">Name</string>
<string name="generic_name_line">Name: %1$s</string>
<string name="generic_no">Nei</string>
<string name="generic_ok">OK</string>
<string name="generic_paused">Paused</string>
<string name="generic_progress_title">Please wait…</string>
<string name="generic_recording">Recording…</string>
<string name="generic_save">Lagre</string>
<string name="generic_success_title">Vellykket</string>
<string name="generic_tracks">Tracks</string>
<string name="generic_yes">Ja</string>
<string name="gps_location_access">‘Location access’</string>
<string name="gps_disabled">GPS not enabled. Tap here to go to %1$s. Press the back button to return.</string>
<string name="gps_disabled_location_mode">Select high-accuracy location mode. Tap here to go to %1$s. Press the back button to return.</string>
<string name="gps_disabled_my_location">Current location cannot be determined. Enable more location sources at %1$s &amp; make sure the
sky is visible. Try again later.</string>
<string name="gps_disabled_my_location_location_mode">Current location cannot be determined. Select the high-accuracy location mode at %1$s &amp; make
sure the sky is visible. Try again later.</string>
<string name="gps_not_found">No GPS is found on the device</string>
<string name="gps_starting">Starting GPS</string>
<string name="gps_starting_error">Unable to start GPS</string>
<string name="gps_stopping">Stopping GPS</string>
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</string>
<string name="gps_wait_for_signal">Waiting for GPS signal</string>
<string name="image_application">Application</string>
<string name="image_arrow">Arrow</string>
<string name="image_marker">Marker</string>
<string name="image_marker_count">Marker count</string>
<string name="image_my_location">My Location</string>
<string name="image_pause">Pause</string>
<string name="image_photo_marker">Photo marker</string>
<string name="image_record">Record</string>
<string name="image_stop">Stop</string>
<string name="image_track">Track</string>
<string name="import_error">Imported %1$d of %2$s from %3$s</string>
<string name="import_no_directory">%1$s does not exist</string>
<string name="import_no_file">There were no files to import. Connect your device to your computer and place the files to
import in %1$s.</string>
<string name="import_no_file_title">Import files</string>
<string name="import_progress_message">Importing from %1$s…</string>
<string name="import_selection_option">All %1$s from %2$s</string>
<string name="import_selection_title">Import from external storage</string>
<string name="import_success">Imported %1$s from %2$s</string>
<string name="marker_add_error">Unable to insert a marker. No GPS signal. Try again.</string>
<string name="marker_add_success">A marker was inserted</string>
<string name="marker_add_canceled">Canceled, no marker added</string>
<string name="marker_delete_all_confirm_message">All markers for this track will be permanently deleted from the device</string>
<string name="marker_delete_one_confirm_message">The marker will be permanently deleted from the device</string>
<string name="marker_delete_one_confirm_title">Delete marker?</string>
<string name="marker_delete_multiple_confirm_message">The selected markers will be permanently deleted from the device</string>
<string name="marker_label_end">%1$s (End)</string>
<string name="marker_label_start">%1$s (Start)</string>
<string name="marker_name_format">Marker %1$d</string>
<string name="marker_split_name_format">Split %1$d</string>
<string name="marker_title">%1$d of %2$d</string>
<string name="marker_detail_statistics_title">Since last split marker:</string>
<string name="marker_edit_marker_type">Marker type (e.g. waterfall)</string>
<string name="marker_edit_marker_type_hint">Marker type</string>
<string name="marker_list_empty_message">You don\'t have any markers</string>
<string name="menu_aggregated_statistics">Aggregated stats</string>
<string name="menu_delete">Delete</string>
<string name="menu_delete_all">Delete all</string>
<string name="menu_edit">Edit</string>
<string name="menu_export">Export</string>
<string name="menu_export_all">Export all</string>
<string name="menu_feedback">Feedback</string>
<string name="menu_help">Hjelp</string>
<string name="menu_help_feedback">Help &amp; feedback</string>
<string name="menu_help_forum">Help forum</string>
<string name="menu_import_all">Import all</string>
<string name="menu_insert_marker">Insert marker</string>
<string name="menu_insert_photo">Insert photo</string>
<string name="menu_map">Map</string>
<string name="menu_markers">Markører</string>
<string name="menu_play">Play</string>
<string name="menu_search">Search</string>
<string name="menu_select_all">Select all</string>
<string name="menu_sensor_state">Sensortilstand</string>
<string name="menu_settings">Innstillinger</string>
<string name="menu_share">Share</string>
<string name="menu_show_on_map">Show on map</string>
<string name="menu_split_frequency">Split frequency</string>
<string name="menu_start_gps">Start GPS</string>
<string name="menu_stop_gps">Stop GPS</string>
<string name="menu_view_photo">View photo</string>
<string name="menu_voice_frequency">Voice frequency</string>
<string name="app_name">OpenTracks</string>
<string name="recording_service">Opptakstjeneste</string>
<string name="search_global_description">Tracks and markers</string>
<string name="search_list_empty_message">No tracks or markers matched your search</string>
<string name="search_list_marker_track_location">In track ‘%1$s’.</string>
<string name="search_voice_prompt">Speak the name of a track or marker</string>
<string name="sensor_state_battery">Batterinivå</string>
<string name="sensor_state_cadence">Kadens</string>
<string name="sensor_state_cadence_value">%1$d rpm</string>
<string name="sensor_state_connected">Tilkoblet</string>
<string name="sensor_state_connecting">Kobler til</string>
<string name="sensor_state_disconnected">Frakoblet</string>
<string name="sensor_state_heart_rate">Puls</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
<string name="sensor_state_last_sensor_time">Forrige sensormåling</string>
<string name="sensor_state_power">Strøm</string>
<string name="sensor_state_power_value">%1$d W</string>
<string name="sensor_state_sending">Sender</string>
<string name="settings_not_while_recording">Not available while recording a track</string>
<string name="settings_show">Show</string>
<string name="settings_advanced">Advanced</string>
<string name="settings_chart_by_distance">By distance</string>
<string name="settings_chart_by_time">By time</string>
<string name="settings_chart_x_axis">X axis</string>
<string name="settings_recording">Opptak</string>
<string name="settings_recording_auto_resume_track_timeout_always_summary">Always resume a recording after reboot</string>
<string name="settings_recording_auto_resume_track_timeout_never_summary">Never resume a recording after reboot</string>
<string name="settings_recording_auto_resume_track_timeout_summary">Resume a recording after reboot if the last recorded location was within the last %1$s</string>
<string name="settings_recording_auto_resume_track_timeout_title">Auto-resume</string>
<string name="settings_recording_default_activity_title">Default activity type</string>
<string name="settings_recording_location_frequency_summary">%1$s between recorded locations</string>
<string name="settings_recording_max_recording_distance_summary">A new segment is created if the distance between two locations is greater than %1$s</string>
<string name="settings_recording_max_recording_distance_title">Max distance</string>
<string name="settings_recording_min_recording_distance_title">Recording distance interval</string>
<string name="settings_recording_min_recording_interval_title">Recording time interval</string>
<string name="settings_recording_min_required_accuracy_summary">Location is ignored if its accuracy is less than %1$s</string>
<string name="settings_recording_min_required_accuracy_title">GPS accuracy</string>
<string name="settings_recording_track_name_date_iso_8601_option">Dato (ISO 8601)</string>
<string name="settings_recording_track_name_date_local_option">Dato (lokal)</string>
<string name="settings_recording_track_name_number_option">Tall</string>
<string name="settings_recording_track_name_title">Default track name</string>
<string name="settings_reset">Tilbakestill</string>
<string name="settings_reset_confirm_message">All settings will be reverted to the default values.</string>
<string name="settings_reset_confirm_title">Reset now?</string>
<string name="settings_reset_done">All settings have been reverted to the default values</string>
<string name="settings_sensor">Sensor</string>
<string name="settings_sensor_ant_reset_bike_cadence_sensor">Reset bike cadence</string>
<string name="settings_sensor_ant_reset_combined_bike_sensor">Reset combined bike</string>
<string name="settings_sensor_ant_reset_heart_rate_monitor">Reset heart rate</string>
<string name="settings_sensor_ant_reset_speed_distance_monitor">Reset foot pod</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_sensor_bluetooth_pairing">Bluetooth-sammenkobling</string>
<string name="settings_sensor_bluetooth_pairing_summary">Open Android\'s Bluetooth settings to pair a sensor</string>
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connected">Connected to %1$s</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_grade_elevation">Grade/Elevation</string>
<string name="settings_stats_rate_title">Preferred rate</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metric (km, m)</string>
<string name="settings_stats_units_title">Preferred units</string>
<string name="share_track_emails_hint">Email addresses</string>
<string name="share_track_picker_title">Select an application</string>
<string name="share_track_share_file">Share track file</string>
<string name="share_track_share_file_body">I think you might be interested in this track.\n\nTo view this track, use Google Earth or other
mapping software.\n\n%1$s</string>
<string name="share_track_subject">Jeg vil dele en sti med deg</string>
<string name="stats_average_moving_pace">Avg moving pace</string>
<string name="stats_average_moving_speed">Avg moving speed</string>
<string name="stats_average_pace">Avg pace</string>
<string name="stats_average_speed">Avg speed</string>
<string name="stats_elevation">Elevation</string>
<string name="stats_fastest_pace">Fastest pace</string>
<string name="stats_gain">Gain</string>
<string name="stats_grade">Grade</string>
<string name="stats_latitude">Latitude</string>
<string name="stats_longitude">Longitude</string>
<string name="stats_max">Max</string>
<string name="stats_max_speed">Max speed</string>
<string name="stats_min">Min</string>
<string name="stats_moving_time">Moving time</string>
<string name="stats_pace">Pace</string>
<string name="stats_speed">Speed</string>
<string name="stats_distance">Distance</string>
<string name="stats_total_time">Total time</string>
<string name="track_name_format">Sti %1$d</string>
<string name="track_record_notification">Recording your track…</string>
<string name="track_markers">%1$s Markers</string>
<string name="track_delete_one_confirm_message">The track and its markers will be permanently deleted from the device.</string>
<string name="track_delete_one_confirm_title">Delete track?</string>
<string name="track_delete_multiple_confirm_message">The selected tracks and their markers will be permanently deleted from the device.</string>
<string name="track_delete_progress_message">Deleting…</string>
<string name="track_detail_chart_tab">Chart</string>
<string name="track_detail_stats_tab">Stats</string>
<string name="track_edit_activity_type">Activity type (e.g. hiking)</string>
<string name="track_edit_activity_type_hint">Activity type</string>
<string name="track_edit_new_track_title">New Track</string>
<string name="track_list_empty_message">Start recording your next adventure here</string>
<string name="track_list_record_error">Unable to start recording. No GPS signal. Try again.</string>
<string name="track_list_record_success">Now recording a new track</string>
<string name="track_widget_config_title">Configure Widget Items</string>
<string name="tracks_few">%1$d tracks</string>
<string name="tracks_many">%1$d tracks</string>
<string name="tracks_one">1 track</string>
<string name="tracks_other">%1$d tracks</string>
<string name="tracks_two">2 tracks</string>
<string name="tracks_zero">0 tracks</string>
<string name="unit_feet">fot</string>
<string name="unit_kilometer">km</string>
<string name="unit_kilometer_per_hour">km/t</string>
<string name="unit_meter">m</string>
<string name="unit_mile">eng. mil</string>
<string name="unit_mile_per_hour">eng. mil per time</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min per eng. mil</string>
<string name="value_adapt_accuracy">Tilpass for nøyaktighet</string>
<string name="value_adapt_battery_life">Tilpass for batterilevetid</string>
<string name="value_always">Alltid</string>
<string name="value_float_feet">%1$.2f ft</string>
<string name="value_float_kilometer">%1$.2f km</string>
<string name="value_float_meter">%1$.2f m</string>
<string name="value_float_mile">%1$.2f eng. mil</string>
<string name="value_float_mile_poor_gps">%1$.1f eng. mil (dårlig GPS)</string>
<string name="value_integer_feet">%1$d fot</string>
<string name="value_integer_feet_excellent_gps">%1$d fot (utmerket GPS)</string>
<string name="value_integer_feet_recommended">%1$d fot (anbefales)</string>
<string name="value_integer_kilobyte">%1$d kB</string>
<string name="value_integer_kilometer">%1$d km</string>
<string name="value_integer_kilometer_hour">%1$d km/h</string>
<string name="value_integer_megabyte">%1$d MB</string>
<string name="value_integer_meter">%1$d m</string>
<string name="value_integer_meter_excellent_gps">%1$d m (utmerket GPS)</string>
<string name="value_integer_meter_poor_gps">%1$d m (dårlig GPS)</string>
<string name="value_integer_meter_recommended">%1$d m (anbefales)</string>
<string name="value_integer_mile">%1$d eng. mil</string>
<string name="value_integer_mile_hour">%1$d mi/h</string>
<string name="value_integer_minute">%1$d min</string>
<string name="value_integer_percent">%1$d %%</string>
<string name="value_integer_second">%1$d s</string>
<string name="value_never">Aldri</string>
<string name="value_none">None</string>
<string name="value_off">Av</string>
<string name="value_smallest">Smallest</string>
<string name="value_smallest_recommended">Minste (anbefales)</string>
<string name="voice_hours_few">%1$d timer</string>
<string name="voice_hours_many">%1$d timer</string>
<string name="voice_hours_one">Én time</string>
<string name="voice_hours_other">%1$d timer</string>
<string name="voice_hours_two">To timer</string>
<string name="voice_hours_zero">Null timer</string>
<string name="voice_minutes_few">%1$d minutter</string>
<string name="voice_minutes_many">%1$d minutter</string>
<string name="voice_minutes_one">Ett minutt</string>
<string name="voice_minutes_other">%1$d minutter</string>
<string name="voice_minutes_two">To minutter</string>
<string name="voice_minutes_zero">Null minutter</string>
<string name="voice_pace_per_kilometer">%1$s per kilometer</string>
<string name="voice_pace_per_mile">%1$s per engelske mil</string>
<string name="voice_seconds_few">%1$d sekunder</string>
<string name="voice_seconds_many">%1$d sekunder</string>
<string name="voice_seconds_one">Ett sekund</string>
<string name="voice_seconds_other">%1$d sekunder</string>
<string name="voice_seconds_two">To sekunder</string>
<string name="voice_seconds_zero">Null sekunder</string>
<string name="voice_speed_kilometers_per_hour_few">%1$.1f kilometer i timen</string>
<string name="voice_speed_kilometers_per_hour_many">%1$.1f kilometer i timen</string>
<string name="voice_speed_kilometers_per_hour_one">Én kilometer i timen</string>
<string name="voice_speed_kilometers_per_hour_other">%1$.1f kilometer i timen</string>
<string name="voice_speed_kilometers_per_hour_two">To kilometer i timen</string>
<string name="voice_speed_kilometers_per_hour_zero">Null kilometer i timen</string>
<string name="voice_speed_miles_per_hour_few">%1$.1f engelske mil i timen</string>
<string name="voice_speed_miles_per_hour_many">%1$.1f engelske mil i timen</string>
<string name="voice_speed_miles_per_hour_one">Én engelsk mil i timen</string>
<string name="voice_speed_miles_per_hour_other">%1$.1f engelske mil i timen</string>
<string name="voice_speed_miles_per_hour_two">To engelske mil i timen</string>
<string name="voice_speed_miles_per_hour_zero">Null engelske mil i timen</string>
<string name="voice_template">OpenTracks %1$s in %2$s at %3$s</string>
<string name="voice_total_distance_kilometers_few">total avstand er %1$.2f kilometer</string>
<string name="voice_total_distance_kilometers_many">total avstand er %1$.2f kilometer</string>
<string name="voice_total_distance_kilometers_one">total avstand er én kilometer</string>
<string name="voice_total_distance_kilometers_other">total avstand er %1$.2f kilometer</string>
<string name="voice_total_distance_kilometers_two">total avstand er to kilometer</string>
<string name="voice_total_distance_kilometers_zero">total avstand er null kilometer</string>
<string name="voice_total_distance_miles_few">total avstand er %1$.2f engelske mil</string>
<string name="voice_total_distance_miles_many">total avstand er %1$.2f engelske mil</string>
<string name="voice_total_distance_miles_one">total avstand er én engelsk mil</string>
<string name="voice_total_distance_miles_other">total avstand er %1$.2f engelske mil</string>
<string name="voice_total_distance_miles_two">total avstand er to engelske mil</string>
<string name="voice_total_distance_miles_zero">total avstand er null engelske mil</string>
<string name="voice_total_distance_zero">Total avstand i Spor er null.</string>
<string name="waypoint_type_atm">Minibank</string>
<string name="waypoint_type_bank">bank</string>
<string name="waypoint_type_bar">bar</string>
<string name="waypoint_type_barbecue">grillmat</string>
<string name="waypoint_type_building">bygning</string>
<string name="waypoint_type_bus_stop">busstopp</string>
<string name="waypoint_type_cafe">kafé</string>
<string name="waypoint_type_campfire">leirbål</string>
<string name="waypoint_type_campground">campingplass</string>
<string name="waypoint_type_church">kirke</string>
<string name="waypoint_type_coffee_house">kaffebar</string>
<string name="waypoint_type_convenience_store">nærbutikk</string>
<string name="waypoint_type_drinking_water">drikkevann</string>
<string name="waypoint_type_falling_rocks">fallende steiner</string>
<string name="waypoint_type_ferry">ferge</string>
<string name="waypoint_type_fire_place">ildsted</string>
<string name="waypoint_type_fire_station">brannstasjon</string>
<string name="waypoint_type_fishing">fiske</string>
<string name="waypoint_type_gas_station">bensinstasjon</string>
<string name="waypoint_type_golf_course">golfbane</string>
<string name="waypoint_type_grocery_store">dagligvarebutikk</string>
<string name="waypoint_type_hair_dresser">frisør</string>
<string name="waypoint_type_harbor">havn</string>
<string name="waypoint_type_hiking_path">tursti</string>
<string name="waypoint_type_hotel">hotell</string>
<string name="waypoint_type_house">hus</string>
<string name="waypoint_type_information">informasjon</string>
<string name="waypoint_type_intersection">veikryss</string>
<string name="waypoint_type_mail_box">postkasse</string>
<string name="waypoint_type_market">marked</string>
<string name="waypoint_type_meeting_point">møtested</string>
<string name="waypoint_type_opera">opera</string>
<string name="waypoint_type_parking">parkering</string>
<string name="waypoint_type_police_station">politistasjon</string>
<string name="waypoint_type_post_office">postkontor</string>
<string name="waypoint_type_pothole">jettegryte</string>
<string name="waypoint_type_railway_crossing">jernbaneovergang</string>
<string name="waypoint_type_restaurant">restaurant</string>
<string name="waypoint_type_shopping">shopping</string>
<string name="waypoint_type_sight">attraksjon</string>
<string name="waypoint_type_store">butikk</string>
<string name="waypoint_type_swimming_pool">svømmebasseng</string>
<string name="waypoint_type_telephone">telefon</string>
<string name="waypoint_type_theater">teater</string>
<string name="waypoint_type_toilet">toalett</string>
<string name="waypoint_type_tourist_information">turistinformasjon</string>
<string name="waypoint_type_tree">tre</string>
<string name="waypoint_type_tunnel">tunnel</string>
<string name="waypoint_type_waterfall">foss</string>
<string name="waypoint_type_water_fountain">vannfontene</string>
<string name="waypoint_type_waypoint">veipunkt</string>
</resources>
+2 -2
View File
@@ -100,7 +100,7 @@
<string name="description_total_time">Temps total: %1$s</string>
<string name="description_track_point">Punt</string>
<string name="description_track_segment">Segment</string>
<string name="export_all_option">Totes com a %1$s a %2$s</string>
<string name="export_all_option">Totes com a %1$s</string>
<string name="export_all_title">Exporta a la unitat d\'emmagatzematge extern</string>
<string name="export_external_storage">Emmagatzematge extern</string>
<string name="export_external_storage_error">S\'han exportat %1$d de %2$s a %3$s.</string>
@@ -163,7 +163,7 @@ es vegi el cel. Torneu-ho a provar més tard.</string>
els fitxers que vulgueu importar.</string>
<string name="import_no_file_title">Importació de fitxers</string>
<string name="import_progress_message">S\'està important des de %1$s…</string>
<string name="import_selection_option">Tots els %1$s de %2$s</string>
<string name="import_selection_option">Tots els %1$s</string>
<string name="import_selection_title">Importació de la unitat d\'emmagatzematge extern</string>
<string name="import_success">S\'han importat %1$s del directori %2$s.</string>
<string name="marker_add_error">No es pot inserir un marcador. No hi ha senyal del GPS. Torneu-ho a provar.</string>

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