Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 306522863a | |||
| e54d85e04d | |||
| cb30ad27c3 | |||
| 8685c9cc88 | |||
| a6f03d0f23 | |||
| e3c812b155 | |||
| f149de9ce3 | |||
| 3447dd2569 | |||
| b2b8c7fd54 | |||
| 486bcd0aec | |||
| c8a33201f5 | |||
| 4a37c2cc7e | |||
| d23db5bafd | |||
| ac528f9a10 | |||
| 256eadd49d | |||
| 64e58934a5 | |||
| 92dfadfdba | |||
| cbce70143b | |||
| f4b9e3159e | |||
| 928781391d | |||
| 05cd672464 | |||
| d3109bbc37 | |||
| eabdb7c721 | |||
| ad990e8cd7 | |||
| db3eef0104 | |||
| c4f1ba7962 | |||
| 7451fbd413 | |||
| b6c55de55a | |||
| dd3ee6d99e | |||
| bbb6a7c22c | |||
| dc1b50a7bf | |||
| 21bfb88e0b | |||
| aaab8adce9 |
@@ -0,0 +1,21 @@
|
|||||||
|
#https://github.com/marketplace/actions/android-emulator-runner
|
||||||
|
name: Test
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: macos-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
api-level: [21, 29]
|
||||||
|
target: [default]
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: run tests
|
||||||
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
|
with:
|
||||||
|
api-level: ${{ matrix.api-level }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
arch: x86_64
|
||||||
|
script: ./gradlew connectedCheck
|
||||||
@@ -45,19 +45,16 @@ captures/
|
|||||||
|
|
||||||
|
|
||||||
# IntelliJ IDEA
|
# IntelliJ IDEA
|
||||||
*.iml
|
|
||||||
*.iws
|
*.iws
|
||||||
/out/
|
/out/
|
||||||
|
|
||||||
# User-specific configurations
|
# User-specific configurations
|
||||||
.idea/.name
|
.idea/.name
|
||||||
.idea/assetWizardSettings.xml
|
|
||||||
.idea/caches/
|
.idea/caches/
|
||||||
.idea/compiler.xml
|
.idea/compiler.xml
|
||||||
.idea/copyright/profiles_settings.xml
|
.idea/copyright/profiles_settings.xml
|
||||||
.idea/dataSources.ids
|
.idea/dataSources.ids
|
||||||
.idea/datasources.xml
|
.idea/datasources.xml
|
||||||
.idea/dictionaries
|
|
||||||
.idea/dynamic.xml
|
.idea/dynamic.xml
|
||||||
.idea/encodings.xml
|
.idea/encodings.xml
|
||||||
.idea/jsLibraryMappings.xml
|
.idea/jsLibraryMappings.xml
|
||||||
@@ -67,10 +64,8 @@ captures/
|
|||||||
.idea/scopes/scope_settings.xml
|
.idea/scopes/scope_settings.xml
|
||||||
.idea/shelf/
|
.idea/shelf/
|
||||||
.idea/sqlDataSources.xml
|
.idea/sqlDataSources.xml
|
||||||
.idea/tasks.xml
|
|
||||||
.idea/vcs.xml
|
.idea/vcs.xml
|
||||||
.idea/uiDesigner.xml
|
.idea/uiDesigner.xml
|
||||||
.idea/workspace.xml
|
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
|
|||||||
@@ -22,18 +22,18 @@ android:
|
|||||||
- android-29
|
- android-29
|
||||||
|
|
||||||
# Emulator Management: Create, Start and Wait
|
# Emulator Management: Create, Start and Wait
|
||||||
#install:
|
install:
|
||||||
# - echo y | sdkmanager "platform-tools"
|
- echo y | sdkmanager "platform-tools"
|
||||||
# - echo y | sdkmanager --channel=4 "emulator"
|
- echo y | sdkmanager --channel=4 "emulator"
|
||||||
# - echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||||
# - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||||
# - emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||||
# - android-wait-for-emulator
|
- android-wait-for-emulator
|
||||||
# - adb shell input keyevent 82 &
|
- adb shell input keyevent 82 &
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew clean build
|
- ./gradlew build
|
||||||
# - ./gradlew connectedCheck
|
- ./gradlew connectedCheck
|
||||||
|
|
||||||
sbefore_cache:
|
sbefore_cache:
|
||||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
|
|||||||
@@ -14,15 +14,15 @@ For the time being, the OpenTracks Data API can be used to implement mainly cust
|
|||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
OpenTracks by default does not expose any data to 3rd-party applications.
|
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.
|
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 of one or more selected tracks.
|
||||||
For each, one URI is provided.
|
For each, one URI is provided.
|
||||||
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
|
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
|
||||||
|
|
||||||
Limitations:
|
Limitations:
|
||||||
* only tracks selected by the user is exposed,
|
* only tracks selected by the user are exposed,
|
||||||
* `Waypoints` are not exposed,
|
* `Waypoints` are not exposed,
|
||||||
* access is only granted temporarily and automatically revoked (see `FLAG_GRANT_READ_URI_PERMISSION`),
|
* access is only granted temporarily and automatically revoked (see `FLAG_GRANT_READ_URI_PERMISSION`),
|
||||||
* no write access is impossible, and
|
* no write access is possible, and
|
||||||
* neither track recordings can be started, paused, or stopped.
|
* neither track recordings can be started, paused, or stopped.
|
||||||
|
|
||||||
All required information can be found in `IntentDashboardUtils.java`.
|
All required information can be found in `IntentDashboardUtils.java`.
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
@@ -109,5 +110,5 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
v3.4.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Dashboard API: multiple tracks
|
||||||
|
- Updated help
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fixed crash on fragment re-creation
|
||||||
|
- When paused: prevent stopping app
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Enable testing via CI (Github Actions and TravisCI)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.5.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Marker: select pictures from gallery
|
||||||
|
- Marker: share picture
|
||||||
|
- Use track name as filename when sharing
|
||||||
|
- Removed elevation min/max (was imprecise)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Charts use preferred units and rate
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Database: remove unused columns
|
||||||
|
- Database: add indices
|
||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 168 KiB |
@@ -19,7 +19,6 @@ public class TestDataUtil {
|
|||||||
Track track = new Track();
|
Track track = new Track();
|
||||||
track.setId(id);
|
track.setId(id);
|
||||||
track.setName("Test: " + id);
|
track.setName("Test: " + id);
|
||||||
track.setNumberOfPoints(numPoints);
|
|
||||||
for (int i = 0; i < numPoints; i++) {
|
for (int i = 0; i < numPoints; i++) {
|
||||||
track.addLocation(createLocation(i));
|
track.addLocation(createLocation(i));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.content.provider;
|
package de.dennisguse.opentracks.content.provider;
|
||||||
|
|
||||||
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
@@ -29,7 +29,6 @@ import org.junit.Test;
|
|||||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider.DatabaseHelper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link CustomContentProvider}.
|
* Tests {@link CustomContentProvider}.
|
||||||
@@ -38,30 +37,15 @@ import de.dennisguse.opentracks.content.provider.CustomContentProvider.DatabaseH
|
|||||||
*/
|
*/
|
||||||
public class CustomContentProviderTest {
|
public class CustomContentProviderTest {
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "test.db";
|
|
||||||
|
|
||||||
private SQLiteDatabase db;
|
|
||||||
private CustomContentProvider customContentProvider;
|
private CustomContentProvider customContentProvider;
|
||||||
private Context context = ApplicationProvider.getApplicationContext();
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
context.deleteDatabase(DATABASE_NAME);
|
|
||||||
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
|
|
||||||
customContentProvider = new CustomContentProvider() {
|
customContentProvider = new CustomContentProvider() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link CustomContentProvider.DatabaseHelper#onCreate(SQLiteDatabase)}.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testDatabaseHelper_OnCreate() {
|
|
||||||
Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME));
|
|
||||||
Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME));
|
|
||||||
Assert.assertTrue(hasTable(WaypointsColumns.TABLE_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
|
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
|
||||||
*/
|
*/
|
||||||
@@ -76,73 +60,12 @@ public class CustomContentProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetType() {
|
public void testGetType() {
|
||||||
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
|
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
|
||||||
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI));
|
Assert.assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build()));
|
||||||
|
|
||||||
|
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
|
||||||
|
Assert.assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
|
||||||
|
|
||||||
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
|
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
|
||||||
}
|
Assert.assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a table, containing one column.
|
|
||||||
*
|
|
||||||
* @param table the table name
|
|
||||||
*/
|
|
||||||
private void createTable(String table) {
|
|
||||||
db.execSQL("CREATE TABLE " + table + " (test INTEGER)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drops a table in database.
|
|
||||||
*
|
|
||||||
* @param table the table name
|
|
||||||
*/
|
|
||||||
private void dropTable(String table) {
|
|
||||||
db.execSQL("Drop TABLE " + table);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the table exists.
|
|
||||||
*
|
|
||||||
* @param table the table name
|
|
||||||
*/
|
|
||||||
private boolean hasTable(String table) {
|
|
||||||
try {
|
|
||||||
db.rawQuery("select count(*) from " + table, null);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the column in the table exists.
|
|
||||||
*
|
|
||||||
* @param table the table name
|
|
||||||
* @param column the column name
|
|
||||||
*/
|
|
||||||
private boolean hasColumn(String table, String column) {
|
|
||||||
try {
|
|
||||||
db.execSQL("SElECT count(*) from " + table + " order by " + column);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (e.getMessage().contains("no such column")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up upgrade.
|
|
||||||
*
|
|
||||||
* @param oldVersion thd old database version
|
|
||||||
*/
|
|
||||||
private void setupUpgrade(int oldVersion) {
|
|
||||||
dropTable(TracksColumns.TABLE_NAME);
|
|
||||||
dropTable(TrackPointsColumns.TABLE_NAME);
|
|
||||||
dropTable(WaypointsColumns.TABLE_NAME);
|
|
||||||
createTable(TracksColumns.TABLE_NAME);
|
|
||||||
createTable(TrackPointsColumns.TABLE_NAME);
|
|
||||||
createTable(WaypointsColumns.TABLE_NAME);
|
|
||||||
|
|
||||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
|
||||||
databaseHelper.onUpgrade(db, oldVersion, CustomContentProvider.DATABASE_VERSION);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
|
|
||||||
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, TrackPointFactory trackPointFactory) {
|
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, TrackPointFactory trackPointFactory) {
|
||||||
long lastPointId = initializeTrack(trackId, numPoints);
|
long lastPointId = initializeTrack(trackId, numPoints);
|
||||||
((ContentProviderUtils) contentProviderUtils).setDefaultCursorBatchSize(batchSize);
|
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
||||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending, trackPointFactory)) {
|
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending, trackPointFactory)) {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@@ -155,7 +155,6 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Track track = new Track();
|
Track track = new Track();
|
||||||
track.setId(id);
|
track.setId(id);
|
||||||
track.setName("Test: " + id);
|
track.setName("Test: " + id);
|
||||||
track.setNumberOfPoints(numPoints);
|
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
track = contentProviderUtils.getTrack(id);
|
track = contentProviderUtils.getTrack(id);
|
||||||
Assert.assertNotNull(track);
|
Assert.assertNotNull(track);
|
||||||
@@ -183,7 +182,6 @@ public class CustomContentProviderUtilsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
||||||
Assert.assertEquals(numPoints, track.getNumberOfPoints());
|
|
||||||
Assert.assertEquals(numPoints, counter);
|
Assert.assertEquals(numPoints, counter);
|
||||||
|
|
||||||
return lastPointId;
|
return lastPointId;
|
||||||
@@ -230,7 +228,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
Assert.assertEquals(1, tracksCursor.getCount());
|
Assert.assertEquals(1, tracksCursor.getCount());
|
||||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
Assert.assertEquals(10, tracksPointsCursor.getCount());
|
Assert.assertEquals(10, tracksPointsCursor.getCount());
|
||||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
Assert.assertEquals(1, waypointCursor.getCount());
|
Assert.assertEquals(1, waypointCursor.getCount());
|
||||||
@@ -239,7 +237,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
// Check whether all have been deleted.
|
// Check whether all have been deleted.
|
||||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
Assert.assertEquals(0, tracksCursor.getCount());
|
Assert.assertEquals(0, tracksCursor.getCount());
|
||||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
Assert.assertEquals(0, tracksPointsCursor.getCount());
|
Assert.assertEquals(0, tracksPointsCursor.getCount());
|
||||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
Assert.assertEquals(0, waypointCursor.getCount());
|
Assert.assertEquals(0, waypointCursor.getCount());
|
||||||
@@ -265,7 +263,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
Assert.assertEquals(3, tracksCursor.getCount());
|
Assert.assertEquals(3, tracksCursor.getCount());
|
||||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
Assert.assertEquals(1, waypointCursor.getCount());
|
Assert.assertEquals(1, waypointCursor.getCount());
|
||||||
@@ -274,7 +272,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
// Check whether all data of a track has been deleted.
|
// Check whether all data of a track has been deleted.
|
||||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
Assert.assertEquals(2, tracksCursor.getCount());
|
Assert.assertEquals(2, tracksCursor.getCount());
|
||||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
Assert.assertEquals(0, waypointCursor.getCount());
|
Assert.assertEquals(0, waypointCursor.getCount());
|
||||||
@@ -616,8 +614,8 @@ public class CustomContentProviderUtilsTest {
|
|||||||
when(cursorMock.getBlob(index++)).thenReturn(sensor);
|
when(cursorMock.getBlob(index++)).thenReturn(sensor);
|
||||||
|
|
||||||
Location location = contentProviderUtils.createTrackPoint(cursorMock);
|
Location location = contentProviderUtils.createTrackPoint(cursorMock);
|
||||||
Assert.assertEquals((double) longitude, location.getLongitude(), 0.01);
|
Assert.assertEquals(longitude, location.getLongitude(), 0.01);
|
||||||
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
|
Assert.assertEquals(latitude, location.getLatitude(), 0.01);
|
||||||
Assert.assertEquals(time, location.getTime(), 0.01);
|
Assert.assertEquals(time, location.getTime(), 0.01);
|
||||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
||||||
}
|
}
|
||||||
@@ -661,7 +659,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
long[] trackpointIds = new long[track.getLocations().size()];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
}
|
}
|
||||||
@@ -680,7 +678,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
long[] trackpointIds = new long[track.getLocations().size()];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
}
|
}
|
||||||
@@ -699,7 +697,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
long[] trackpointIds = new long[track.getLocations().size()];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
}
|
}
|
||||||
@@ -726,7 +724,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
long[] trackpointIds = new long[track.getLocations().size()];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||||
}
|
}
|
||||||
@@ -766,4 +764,12 @@ public class CustomContentProviderUtilsTest {
|
|||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
|
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFormatIdListForUri() {
|
||||||
|
Assert.assertEquals("", ContentProviderUtils.formatIdListForUri(new long[]{}));
|
||||||
|
Assert.assertEquals("12", ContentProviderUtils.formatIdListForUri(new long[]{12}));
|
||||||
|
Assert.assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new long[]{42, 43, 44}));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package de.dennisguse.opentracks.content.provider;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
|
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class CustomSQLiteOpenHelperTest {
|
||||||
|
|
||||||
|
private static final String TRACKS_CREATE_TABLE_V23 = "CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)";
|
||||||
|
private static final String TRACKPOINTS_CREATE_TABLE_V23 = "CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT)";
|
||||||
|
private static final String WAYPOINTS_CREATE_TABLE_V23 = "CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, type INTEGER, length FLOAT, duration INTEGER, starttime INTEGER, startid INTEGER, stopid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, photoUrl TEXT)";
|
||||||
|
|
||||||
|
private static final String DATABASE_NAME = "test.db";
|
||||||
|
|
||||||
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SQL create statements for all tables (ordered by name).
|
||||||
|
*
|
||||||
|
* @return Map(TableName, SQL)
|
||||||
|
*/
|
||||||
|
private static Map<String, String> getTableSQL(SQLiteDatabase db) {
|
||||||
|
HashMap<String, String> tableSQL = new HashMap<>();
|
||||||
|
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + WaypointsColumns.TABLE_NAME + "')", null, null, null, "name")) {
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
tableSQL.put(cursor.getString(0), cursor.getString(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SQL create statements for all indices (ordered by name).
|
||||||
|
*
|
||||||
|
* @return Map(tableName, SQL)
|
||||||
|
*/
|
||||||
|
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
|
||||||
|
HashMap<String, String> indexSQL = new HashMap<>();
|
||||||
|
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
indexSQL.put(cursor.getString(0), cursor.getString(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indexSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a SQL create statement is in the schema.
|
||||||
|
*
|
||||||
|
* @param sqlCreate the table name
|
||||||
|
*/
|
||||||
|
private static boolean hasSqlCreate(SQLiteDatabase db, String sqlCreate) {
|
||||||
|
try (Cursor cursor = db.rawQuery("SELECT SQL FROM sqlite_master", null)) {
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
String sql = cursor.getString(0);
|
||||||
|
if (sqlCreate.equals(sql)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
context.deleteDatabase(DATABASE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onCreate() {
|
||||||
|
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
|
||||||
|
Assert.assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE));
|
||||||
|
|
||||||
|
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
|
||||||
|
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
|
||||||
|
|
||||||
|
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
|
||||||
|
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onUpgrade_FromVersion23() {
|
||||||
|
// Manually create database schema with version 23 (base version)
|
||||||
|
{
|
||||||
|
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
}
|
||||||
|
}.getWritableDatabase();
|
||||||
|
|
||||||
|
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
|
||||||
|
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
|
||||||
|
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
|
||||||
|
|
||||||
|
dbBase.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open database with SQL upgrade
|
||||||
|
Map<String, String> tableByUpgrade;
|
||||||
|
Map<String, String> indicesByUpgrade;
|
||||||
|
try (SQLiteDatabase dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
|
||||||
|
tableByUpgrade = getTableSQL(dbUpgraded);
|
||||||
|
indicesByUpgrade = getIndexSQL(dbUpgraded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open database via
|
||||||
|
context.deleteDatabase(DATABASE_NAME);
|
||||||
|
|
||||||
|
Map<String, String> tablesByCreate;
|
||||||
|
Map<String, String> indicesByCreate;
|
||||||
|
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
|
||||||
|
tablesByCreate = getTableSQL(dbCreated);
|
||||||
|
indicesByCreate = getIndexSQL(dbCreated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then - verify table structure
|
||||||
|
Assert.assertEquals(3, tableByUpgrade.size());
|
||||||
|
Assert.assertEquals(tableByUpgrade.size(), tablesByCreate.size());
|
||||||
|
|
||||||
|
Assert.assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
|
||||||
|
Assert.assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
|
||||||
|
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
|
||||||
|
|
||||||
|
// then - verify custom indices
|
||||||
|
Assert.assertEquals(2, indicesByCreate.size());
|
||||||
|
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
|
||||||
|
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onDowngrade_ToVersion23() {
|
||||||
|
// Create most recent database schema
|
||||||
|
new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase().close();
|
||||||
|
|
||||||
|
// Downgrade schema to version 23 (base version)
|
||||||
|
Map<String, String> tablesByDowngrade;
|
||||||
|
Map<String, String> indicesByDowngrade;
|
||||||
|
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23).getReadableDatabase()) {
|
||||||
|
tablesByDowngrade = getTableSQL(db);
|
||||||
|
indicesByDowngrade = getIndexSQL(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then - verify table structure
|
||||||
|
Assert.assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks"));
|
||||||
|
Assert.assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints"));
|
||||||
|
Assert.assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints"));
|
||||||
|
|
||||||
|
// then - verify custom indices
|
||||||
|
Assert.assertEquals(0, indicesByDowngrade.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package de.dennisguse.opentracks.content.provider;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
|
|
||||||
|
public class ShareContentProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateandParseURI() {
|
||||||
|
long[] trackIds = {1, 3, 5};
|
||||||
|
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
|
||||||
|
|
||||||
|
Assert.assertArrayEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateURIescapeFilename() {
|
||||||
|
long[] trackIds = {1};
|
||||||
|
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK);
|
||||||
|
|
||||||
|
Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,8 +61,7 @@ public class ChartFragmentTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
boolean chartByDistance = false;
|
boolean chartByDistance = false;
|
||||||
chartFragment = new ChartFragment(chartByDistance) {
|
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
|
||||||
};
|
|
||||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||||
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AnnouncementUtilsTest {
|
||||||
|
|
||||||
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnouncement() {
|
||||||
|
TripStatistics stats = new TripStatistics();
|
||||||
|
stats.setTotalDistance(20000);
|
||||||
|
stats.setTotalTime(600000);
|
||||||
|
stats.setMovingTime(300000);
|
||||||
|
stats.setMaxSpeed(100);
|
||||||
|
stats.setTotalElevationGain(6000);
|
||||||
|
|
||||||
|
// when
|
||||||
|
String announcement = AnnouncementUtils.getAnnouncement(context, stats);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -164,7 +164,10 @@ public class StringUtilsTest {
|
|||||||
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||||
calendar.set(year, month - 1, day, hour, minute, second);
|
calendar.set(year, month - 1, day, hour, minute, second);
|
||||||
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
|
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
|
||||||
Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
|
|
||||||
|
// This comparision tends to be flaky (difference of 1ms)
|
||||||
|
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
|
||||||
|
Assert.assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime)) <= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ limitations under the License.
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="3272"
|
android:versionCode="3305"
|
||||||
android:versionName="v3.3.2"> <!-- For FDroid. -->
|
android:versionName="v3.5.0"> <!-- For FDroid. -->
|
||||||
<!-- Permission to initialize services -->
|
<!-- Permission to initialize services -->
|
||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
@@ -31,7 +31,9 @@ limitations under the License.
|
|||||||
<!-- Activities -->
|
<!-- Activities -->
|
||||||
<activity android:name=".AboutActivity" />
|
<activity android:name=".AboutActivity" />
|
||||||
|
|
||||||
<activity android:name=".TrackDeleteActivity" />
|
<activity
|
||||||
|
android:name=".TrackDeleteActivity"
|
||||||
|
android:theme="@style/ThemeCustomTransparent" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".HelpActivity"
|
android:name=".HelpActivity"
|
||||||
|
|||||||
@@ -16,18 +16,38 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.AutoCompleteTextView;
|
import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to add/edit a marker.
|
* An activity to add/edit a marker.
|
||||||
@@ -39,16 +59,27 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||||
|
|
||||||
|
private static final int CAMERA_REQUEST_CODE = 5;
|
||||||
|
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
||||||
|
|
||||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||||
private long trackId;
|
private long trackId;
|
||||||
private long markerId;
|
private long markerId;
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private Waypoint waypoint;
|
private Waypoint waypoint;
|
||||||
|
|
||||||
|
private MenuItem insertPhotoMenuItem;
|
||||||
|
private MenuItem insertGalleryImgMenuItem;
|
||||||
|
|
||||||
|
private Uri photoUri;
|
||||||
|
private boolean hasCamera;
|
||||||
|
|
||||||
// UI elements
|
// UI elements
|
||||||
private EditText waypointName;
|
private EditText waypointName;
|
||||||
private AutoCompleteTextView waypointMarkerType;
|
private AutoCompleteTextView waypointMarkerType;
|
||||||
private EditText waypointDescription;
|
private EditText waypointDescription;
|
||||||
|
private ImageView waypointPhoto;
|
||||||
|
private ImageView waypointDeletePhotoBtn;
|
||||||
private Button done;
|
private Button done;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,12 +90,27 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||||
|
|
||||||
|
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||||
|
|
||||||
// Setup UI elements
|
// Setup UI elements
|
||||||
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
||||||
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
|
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
|
||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
||||||
waypointMarkerType.setAdapter(adapter);
|
waypointMarkerType.setAdapter(adapter);
|
||||||
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
|
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
|
||||||
|
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
|
||||||
|
|
||||||
|
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
|
||||||
|
waypointDeletePhotoBtn.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (waypoint != null && waypoint.hasPhoto())
|
||||||
|
waypoint.setPhotoUrl(null);
|
||||||
|
waypointPhoto.setImageBitmap(null);
|
||||||
|
photoUri = null;
|
||||||
|
hideAndShowOptions();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Button cancel = findViewById(R.id.marker_edit_cancel);
|
Button cancel = findViewById(R.id.marker_edit_cancel);
|
||||||
cancel.setOnClickListener(new OnClickListener() {
|
cancel.setOnClickListener(new OnClickListener() {
|
||||||
@@ -94,6 +140,64 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
return R.layout.marker_edit;
|
return R.layout.marker_edit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.marker_edit, menu);
|
||||||
|
|
||||||
|
insertPhotoMenuItem = menu.findItem(R.id.marker_edit_insert_photo);
|
||||||
|
insertPhotoMenuItem.setVisible(hasCamera);
|
||||||
|
insertGalleryImgMenuItem = menu.findItem(R.id.marker_edit_insert_gallery_img);
|
||||||
|
hideAndShowOptions();
|
||||||
|
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.marker_edit_insert_photo:
|
||||||
|
createWaypointWithPicture();
|
||||||
|
return true;
|
||||||
|
case R.id.marker_edit_insert_gallery_img:
|
||||||
|
createWaypointWithGalleryImage();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == CAMERA_REQUEST_CODE) {
|
||||||
|
if (resultCode == RESULT_CANCELED) {
|
||||||
|
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
} else if (resultCode == RESULT_OK) {
|
||||||
|
setWaypointImageView(photoUri);
|
||||||
|
}
|
||||||
|
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
|
||||||
|
if (resultCode == RESULT_CANCELED) {
|
||||||
|
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
} else if (resultCode == RESULT_OK) {
|
||||||
|
Uri srcUri = data.getData();
|
||||||
|
try (ParcelFileDescriptor parcelFd = getContentResolver().openFileDescriptor(srcUri, "r")) {
|
||||||
|
FileDescriptor srcFd = parcelFd.getFileDescriptor();
|
||||||
|
File dstFile = new File(FileUtils.getImageUrl(this, getTrackId()));
|
||||||
|
FileUtils.copy(srcFd, dstFile);
|
||||||
|
|
||||||
|
photoUri = FileUtils.getUriForFile(this, dstFile);
|
||||||
|
setWaypointImageView(photoUri);
|
||||||
|
} catch(Exception e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the UI based on the marker id.
|
* Updates the UI based on the marker id.
|
||||||
*/
|
*/
|
||||||
@@ -133,9 +237,64 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointName.setText(waypoint.getName());
|
waypointName.setText(waypoint.getName());
|
||||||
waypointMarkerType.setText(waypoint.getCategory());
|
waypointMarkerType.setText(waypoint.getCategory());
|
||||||
waypointDescription.setText(waypoint.getDescription());
|
waypointDescription.setText(waypoint.getDescription());
|
||||||
|
if (waypoint.hasPhoto()) {
|
||||||
|
photoUri = waypoint.getPhotoURI();
|
||||||
|
setWaypointImageView(photoUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hideAndShowOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and hide/shows all buttons/options about marker photo options.
|
||||||
|
*
|
||||||
|
* If a photo is set then one's options are shown, otherwise another ones are shown.
|
||||||
|
*/
|
||||||
|
private void hideAndShowOptions() {
|
||||||
|
boolean isPhotoSet = (waypoint != null && waypoint.hasPhoto()) || photoUri != null;
|
||||||
|
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
|
||||||
|
insertPhotoMenuItem.setVisible(isPhotoSet ? false : true);
|
||||||
|
insertGalleryImgMenuItem.setVisible(isPhotoSet ? false : true);
|
||||||
|
}
|
||||||
|
waypointDeletePhotoBtn.setVisibility(isPhotoSet ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the trackId. If trackId == -1 then get it from waypoint object.
|
||||||
|
*/
|
||||||
|
private long getTrackId() {
|
||||||
|
return trackId == -1 ? waypoint.getTrackId() : trackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the ImageView waypointPhoto with the uri photo.
|
||||||
|
*
|
||||||
|
* @param uri the uri photo.
|
||||||
|
*/
|
||||||
|
private void setWaypointImageView(Uri uri) {
|
||||||
|
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
|
||||||
|
FileDescriptor fd = pfd.getFileDescriptor();
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
|
||||||
|
waypointPhoto.setImageBitmap(bitmap);
|
||||||
|
hideAndShowOptions();
|
||||||
|
} catch(IOException e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createWaypointWithPicture() {
|
||||||
|
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
|
||||||
|
photoUri = intentAndPhotoUri.second;
|
||||||
|
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createWaypointWithGalleryImage() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||||
|
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a marker.
|
* Adds a marker.
|
||||||
*/
|
*/
|
||||||
@@ -144,7 +303,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointName.getText().toString(),
|
waypointName.getText().toString(),
|
||||||
waypointMarkerType.getText().toString(),
|
waypointMarkerType.getText().toString(),
|
||||||
waypointDescription.getText().toString(),
|
waypointDescription.getText().toString(),
|
||||||
null);
|
photoUri != null ? photoUri.toString() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,6 +313,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypoint.setName(waypointName.getText().toString());
|
waypoint.setName(waypointName.getText().toString());
|
||||||
waypoint.setCategory(waypointMarkerType.getText().toString());
|
waypoint.setCategory(waypointMarkerType.getText().toString());
|
||||||
waypoint.setDescription(waypointDescription.getText().toString());
|
waypoint.setDescription(waypointDescription.getText().toString());
|
||||||
|
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||||
|
|
||||||
new ContentProviderUtils(this).updateWaypoint(waypoint);
|
new ContentProviderUtils(this).updateWaypoint(waypoint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
|
|
||||||
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
|
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
|
||||||
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
|
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
|
||||||
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
|
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
|
||||||
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
|
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
|
||||||
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
@@ -179,7 +179,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
new String[]{String.valueOf(track.getId())}, null);
|
new String[]{String.valueOf(track.getId())}, null);
|
||||||
} else {
|
} else {
|
||||||
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
|
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
|
||||||
WaypointsColumns.STARTTIME + " IS NULL", null, null);
|
null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,26 +16,18 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
@@ -46,8 +38,7 @@ import de.dennisguse.opentracks.content.TrackDataHub;
|
|||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
|
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChartTimeFragment;
|
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||||
@@ -72,16 +63,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||||
private static final String PHOTO_URI_KEY = "photo_uri_key";
|
|
||||||
private static final String HAS_PHOTO_KEY = "has_photo_key";
|
|
||||||
|
|
||||||
private static final int CAMERA_REQUEST_CODE = 5;
|
|
||||||
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
|
|
||||||
|
|
||||||
// The following are set in onCreate
|
// The following are set in onCreate
|
||||||
private boolean hasCamera;
|
|
||||||
private Uri photoUri;
|
|
||||||
private boolean hasPhoto;
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
@@ -104,10 +87,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
trackController.update(isRecording(), recordingTrackPaused);
|
trackController.update(isRecording(), recordingTrackPaused);
|
||||||
if (hasPhoto && photoUri != null) {
|
|
||||||
hasPhoto = false;
|
|
||||||
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, null, null, null, photoUri.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -150,7 +129,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
};
|
};
|
||||||
|
|
||||||
private MenuItem insertMarkerMenuItem;
|
private MenuItem insertMarkerMenuItem;
|
||||||
private MenuItem insertPhotoMenuItem;
|
|
||||||
private MenuItem markerListMenuItem;
|
private MenuItem markerListMenuItem;
|
||||||
private MenuItem shareMenuItem;
|
private MenuItem shareMenuItem;
|
||||||
|
|
||||||
@@ -184,10 +162,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
|
||||||
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
|
||||||
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
|
||||||
|
|
||||||
contentProviderUtils = new ContentProviderUtils(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
|
|
||||||
@@ -206,17 +180,15 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
|
||||||
return new StatsFragment();
|
|
||||||
case 1:
|
case 1:
|
||||||
return new ChartTimeFragment();
|
return ChartFragment.newInstance(false);
|
||||||
case 2:
|
case 2:
|
||||||
return new ChartDistanceFragment();
|
return ChartFragment.newInstance(true);
|
||||||
|
default: //0
|
||||||
|
return new StatsFragment();
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
@@ -311,23 +283,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
||||||
|
|
||||||
if (photoUri != null) {
|
|
||||||
outState.putParcelable(PHOTO_URI_KEY, photoUri);
|
|
||||||
}
|
|
||||||
outState.putBoolean(HAS_PHOTO_KEY, hasPhoto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == CAMERA_REQUEST_CODE) {
|
|
||||||
if (resultCode == RESULT_CANCELED) {
|
|
||||||
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hasPhoto = resultCode == RESULT_OK;
|
|
||||||
}
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -347,8 +302,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
getMenuInflater().inflate(R.menu.track_detail, menu);
|
getMenuInflater().inflate(R.menu.track_detail, menu);
|
||||||
|
|
||||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||||
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
|
||||||
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
|
||||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||||
|
|
||||||
@@ -376,9 +329,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
intent = Intent.createChooser(intent, null);
|
intent = Intent.createChooser(intent, null);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_insert_photo:
|
|
||||||
createWaypointWithPicture();
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_menu_show_on_map:
|
case R.id.track_detail_menu_show_on_map:
|
||||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
||||||
return true;
|
return true;
|
||||||
@@ -404,20 +354,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
|
||||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
|
||||||
} else {
|
|
||||||
createWaypointWithPicture();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||||
return trackRecordingServiceConnection;
|
return trackRecordingServiceConnection;
|
||||||
@@ -475,7 +411,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
*/
|
*/
|
||||||
private void updateMenuItems(boolean isPaused) {
|
private void updateMenuItems(boolean isPaused) {
|
||||||
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
|
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
|
||||||
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
|
||||||
shareMenuItem.setVisible(!isRecording());
|
shareMenuItem.setVisible(!isRecording());
|
||||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
String title;
|
String title;
|
||||||
@@ -488,12 +423,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
setTitle(title);
|
setTitle(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createWaypointWithPicture() {
|
|
||||||
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, trackId);
|
|
||||||
photoUri = intentAndPhotoUri.second;
|
|
||||||
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void chooseActivityType(String category) {
|
public void chooseActivityType(String category) {
|
||||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
|
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,6 +295,10 @@ public class ChartView extends View {
|
|||||||
series[index].setEnabled(enabled);
|
series[index].setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getMetricUnits() {
|
||||||
|
return metricUnits;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets metric units.
|
* Sets metric units.
|
||||||
*
|
*
|
||||||
@@ -304,6 +308,10 @@ public class ChartView extends View {
|
|||||||
metricUnits = value;
|
metricUnits = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getReportSpeed() {
|
||||||
|
return reportSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets report speed.
|
* Sets report speed.
|
||||||
*
|
*
|
||||||
@@ -486,7 +494,6 @@ public class ChartView extends View {
|
|||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
synchronized (chartData) {
|
synchronized (chartData) {
|
||||||
|
|
||||||
canvas.save();
|
canvas.save();
|
||||||
|
|
||||||
canvas.drawColor(Color.WHITE);
|
canvas.drawColor(Color.WHITE);
|
||||||
|
|||||||
@@ -111,8 +111,7 @@ class DataSourceManager {
|
|||||||
dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
|
dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
|
||||||
break;
|
break;
|
||||||
case SAMPLED_IN_TRACK_POINTS_TABLE:
|
case SAMPLED_IN_TRACK_POINTS_TABLE:
|
||||||
dataSource.registerContentObserver(
|
dataSource.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, trackPointsTableObserver);
|
||||||
TrackPointsColumns.CONTENT_URI, trackPointsTableObserver);
|
|
||||||
break;
|
break;
|
||||||
case SAMPLED_OUT_TRACK_POINTS_TABLE:
|
case SAMPLED_OUT_TRACK_POINTS_TABLE:
|
||||||
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
|
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
|
||||||
|
|||||||
@@ -38,10 +38,6 @@ public class Track {
|
|||||||
private String description = "";
|
private String description = "";
|
||||||
private String category = "";
|
private String category = "";
|
||||||
|
|
||||||
/*
|
|
||||||
* The number of location points (present even if the points themselves are not loaded)
|
|
||||||
*/
|
|
||||||
private int numberOfPoints = 0;
|
|
||||||
private String icon = "";
|
private String icon = "";
|
||||||
|
|
||||||
private TripStatistics tripStatistics = new TripStatistics();
|
private TripStatistics tripStatistics = new TripStatistics();
|
||||||
@@ -84,14 +80,6 @@ public class Track {
|
|||||||
this.category = category;
|
this.category = category;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNumberOfPoints() {
|
|
||||||
return numberOfPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNumberOfPoints(int numberOfPoints) {
|
|
||||||
this.numberOfPoints = numberOfPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIcon() {
|
public String getIcon() {
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,15 +29,20 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
|||||||
public interface TrackPointsColumns extends BaseColumns {
|
public interface TrackPointsColumns extends BaseColumns {
|
||||||
|
|
||||||
String TABLE_NAME = "trackpoints";
|
String TABLE_NAME = "trackpoints";
|
||||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
Uri CONTENT_URI_BY_ID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||||
|
Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
|
||||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
|
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
|
||||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
|
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
|
||||||
String DEFAULT_SORT_ORDER = "_id";
|
String DEFAULT_SORT_ORDER = "_id";
|
||||||
|
|
||||||
// Columns
|
// Columns
|
||||||
String TRACKID = "trackid";
|
String TRACKID = "trackid";
|
||||||
|
|
||||||
String LONGITUDE = "longitude";
|
String LONGITUDE = "longitude";
|
||||||
String LATITUDE = "latitude";
|
String LATITUDE = "latitude";
|
||||||
|
double PAUSE_LATITUDE = 100.0;
|
||||||
|
double RESUME_LATITUDE = 200.0;
|
||||||
|
|
||||||
String TIME = "time";
|
String TIME = "time";
|
||||||
String ALTITUDE = "elevation";
|
String ALTITUDE = "elevation";
|
||||||
String ACCURACY = "accuracy";
|
String ACCURACY = "accuracy";
|
||||||
@@ -59,5 +64,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
|||||||
+ BEARING + " FLOAT, "
|
+ BEARING + " FLOAT, "
|
||||||
+ SENSOR_HEARTRATE + " FLOAT, "
|
+ SENSOR_HEARTRATE + " FLOAT, "
|
||||||
+ SENSOR_CADENCE + " FLOAT, "
|
+ SENSOR_CADENCE + " FLOAT, "
|
||||||
+ SENSOR_POWER + " FLOAT);";
|
+ SENSOR_POWER + " FLOAT)";
|
||||||
|
|
||||||
|
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,34 +38,20 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
String NAME = "name"; // track name
|
String NAME = "name"; // track name
|
||||||
String DESCRIPTION = "description"; // track description
|
String DESCRIPTION = "description"; // track description
|
||||||
String CATEGORY = "category"; // track activity type
|
String CATEGORY = "category"; // track activity type
|
||||||
@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 STARTTIME = "starttime"; // track start time
|
||||||
String STOPTIME = "stoptime"; // track stop time
|
String STOPTIME = "stoptime"; // track stop time
|
||||||
String NUMPOINTS = "numpoints"; // number of track points
|
@Deprecated
|
||||||
|
String NUMPOINTS = "numpoints"; // number of track points //TODO UNUSED
|
||||||
String TOTALDISTANCE = "totaldistance"; // total distance
|
String TOTALDISTANCE = "totaldistance"; // total distance
|
||||||
String TOTALTIME = "totaltime"; // total time
|
String TOTALTIME = "totaltime"; // total time
|
||||||
String MOVINGTIME = "movingtime"; // moving time
|
String MOVINGTIME = "movingtime"; // moving time
|
||||||
|
|
||||||
@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 AVGSPEED = "avgspeed"; // average speed
|
||||||
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
|
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
|
||||||
String MAXSPEED = "maxspeed"; // maximum speed
|
String MAXSPEED = "maxspeed"; // maximum speed
|
||||||
String MINELEVATION = "minelevation"; // minimum elevation
|
String MINELEVATION = "minelevation"; // minimum elevation
|
||||||
String MAXELEVATION = "maxelevation"; // maximum elevation
|
String MAXELEVATION = "maxelevation"; // maximum elevation
|
||||||
String ELEVATIONGAIN = "elevationgain"; // elevation gain
|
String ELEVATIONGAIN = "elevationgain"; // elevation gain
|
||||||
String MINGRADE = "mingrade"; // minimum grade
|
|
||||||
String MAXGRADE = "maxgrade"; // maximum grade
|
|
||||||
String ICON = "icon"; // track activity type icon
|
String ICON = "icon"; // track activity type icon
|
||||||
|
|
||||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" // table
|
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" // table
|
||||||
@@ -73,25 +59,17 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
+ NAME + " TEXT, " // name
|
+ NAME + " TEXT, " // name
|
||||||
+ DESCRIPTION + " TEXT, " // description
|
+ DESCRIPTION + " TEXT, " // description
|
||||||
+ CATEGORY + " TEXT, " // category
|
+ CATEGORY + " TEXT, " // category
|
||||||
+ STARTID + " INTEGER, " // start id
|
|
||||||
+ STOPID + " INTEGER, " // stop id
|
|
||||||
+ STARTTIME + " INTEGER, " // start time
|
+ STARTTIME + " INTEGER, " // start time
|
||||||
+ STOPTIME + " INTEGER, " // stop time
|
+ STOPTIME + " INTEGER, " // stop time
|
||||||
+ NUMPOINTS + " INTEGER, " // num points
|
+ NUMPOINTS + " INTEGER, " // num points
|
||||||
+ TOTALDISTANCE + " FLOAT, " // total distance
|
+ TOTALDISTANCE + " FLOAT, " // total distance
|
||||||
+ TOTALTIME + " INTEGER, " // total time
|
+ TOTALTIME + " INTEGER, " // total time
|
||||||
+ MOVINGTIME + " INTEGER, " // moving time
|
+ MOVINGTIME + " INTEGER, " // moving time
|
||||||
+ MINLAT + " INTEGER, " // min latitude
|
|
||||||
+ MAXLAT + " INTEGER, " // max latitude
|
|
||||||
+ MINLON + " INTEGER, " // min longitude
|
|
||||||
+ MAXLON + " INTEGER, " // max longitude
|
|
||||||
+ AVGSPEED + " FLOAT, " // average speed
|
+ AVGSPEED + " FLOAT, " // average speed
|
||||||
+ AVGMOVINGSPEED + " FLOAT, " // average moving speed
|
+ AVGMOVINGSPEED + " FLOAT, " // average moving speed
|
||||||
+ MAXSPEED + " FLOAT, " // max speed
|
+ MAXSPEED + " FLOAT, " // max speed
|
||||||
+ MINELEVATION + " FLOAT, " // min elevation
|
+ MINELEVATION + " FLOAT, " // min elevation
|
||||||
+ MAXELEVATION + " FLOAT, " // max elevation
|
+ MAXELEVATION + " FLOAT, " // max elevation
|
||||||
+ ELEVATIONGAIN + " FLOAT, " // elevation gain
|
+ ELEVATIONGAIN + " FLOAT, " // elevation gain
|
||||||
+ MINGRADE + " FLOAT, " // min grade
|
+ ICON + " TEXT)"; // icon
|
||||||
+ MAXGRADE + " FLOAT, " // max grade
|
|
||||||
+ ICON + " TEXT);"; // icon
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,49 +40,17 @@ public interface WaypointsColumns extends BaseColumns {
|
|||||||
String CATEGORY = "category"; // waypoint category
|
String CATEGORY = "category"; // waypoint category
|
||||||
String ICON = "icon"; // waypoint icon
|
String ICON = "icon"; // waypoint icon
|
||||||
String TRACKID = "trackid"; // track id
|
String TRACKID = "trackid"; // track id
|
||||||
@Deprecated
|
|
||||||
String TYPE = "type"; // type // TODO UNUSED
|
|
||||||
|
|
||||||
String LENGTH = "length"; // length of the track (without smoothing)
|
String LENGTH = "length"; // length of the track (without smoothing)
|
||||||
String DURATION = "duration"; // total duration of the track (not from last waypoint)
|
String DURATION = "duration"; // total duration of the track from the beginning until now
|
||||||
String STARTTIME = "starttime"; // start time of the trip statistics
|
|
||||||
@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 LONGITUDE = "longitude"; // longitude
|
||||||
String LATITUDE = "latitude"; // latitude
|
String LATITUDE = "latitude"; // latitude
|
||||||
String TIME = "time"; // time
|
String TIME = "time"; // time
|
||||||
String ALTITUDE = "elevation"; // altitude
|
String ALTITUDE = "elevation"; // altitude
|
||||||
String ACCURACY = "accuracy"; // accuracy
|
String ACCURACY = "accuracy"; // accuracy
|
||||||
String SPEED = "speed"; // speed
|
|
||||||
String BEARING = "bearing"; // bearing
|
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
|
|
||||||
|
|
||||||
@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
|
String PHOTOURL = "photoUrl"; // url for the photo
|
||||||
|
|
||||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||||
@@ -92,30 +60,16 @@ public interface WaypointsColumns extends BaseColumns {
|
|||||||
+ CATEGORY + " TEXT, "
|
+ CATEGORY + " TEXT, "
|
||||||
+ ICON + " TEXT, "
|
+ ICON + " TEXT, "
|
||||||
+ TRACKID + " INTEGER, "
|
+ TRACKID + " INTEGER, "
|
||||||
+ TYPE + " INTEGER, "
|
|
||||||
+ LENGTH + " FLOAT, "
|
+ LENGTH + " FLOAT, "
|
||||||
+ DURATION + " INTEGER, "
|
+ DURATION + " INTEGER, "
|
||||||
+ STARTTIME + " INTEGER, "
|
|
||||||
+ STARTID + " INTEGER, "
|
|
||||||
+ STOPID + " INTEGER, "
|
|
||||||
+ LONGITUDE + " INTEGER, "
|
+ LONGITUDE + " INTEGER, "
|
||||||
+ LATITUDE + " INTEGER, "
|
+ LATITUDE + " INTEGER, "
|
||||||
+ TIME + " INTEGER, "
|
+ TIME + " INTEGER, "
|
||||||
+ ALTITUDE + " FLOAT, "
|
+ ALTITUDE + " FLOAT, "
|
||||||
+ ACCURACY + " FLOAT, "
|
+ ACCURACY + " FLOAT, "
|
||||||
+ SPEED + " FLOAT, "
|
|
||||||
+ BEARING + " FLOAT, "
|
+ BEARING + " FLOAT, "
|
||||||
+ TOTALDISTANCE + " FLOAT, "
|
|
||||||
+ TOTALTIME + " INTEGER, "
|
|
||||||
+ MOVINGTIME + " INTEGER, "
|
|
||||||
+ AVGSPEED + " FLOAT, "
|
|
||||||
+ AVGMOVINGSPEED + " FLOAT, "
|
|
||||||
+ MAXSPEED + " FLOAT, "
|
|
||||||
+ MINELEVATION + " FLOAT, "
|
|
||||||
+ MAXELEVATION + " FLOAT, "
|
|
||||||
+ ELEVATIONGAIN + " FLOAT, "
|
|
||||||
+ MINGRADE + " FLOAT, "
|
|
||||||
+ MAXGRADE + " FLOAT, "
|
|
||||||
+ PHOTOURL + " TEXT"
|
+ PHOTOURL + " TEXT"
|
||||||
+ ");";
|
+ ")";
|
||||||
|
|
||||||
|
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.content.Context;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
@@ -68,6 +69,7 @@ public class ContentProviderUtils {
|
|||||||
* Maximum number of waypoints that will be loaded at one time.
|
* Maximum number of waypoints that will be loaded at one time.
|
||||||
*/
|
*/
|
||||||
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
||||||
|
public static final String ID_SEPARATOR = ",";
|
||||||
|
|
||||||
private final IContentResolver contentResolver;
|
private final IContentResolver contentResolver;
|
||||||
private int defaultCursorBatchSize = 2000;
|
private int defaultCursorBatchSize = 2000;
|
||||||
@@ -109,7 +111,6 @@ public class ContentProviderUtils {
|
|||||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||||
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
|
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
|
||||||
int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME);
|
int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME);
|
||||||
int numPointsIndex = cursor.getColumnIndexOrThrow(TracksColumns.NUMPOINTS);
|
|
||||||
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
|
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
|
||||||
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
|
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
|
||||||
int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME);
|
int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME);
|
||||||
@@ -117,8 +118,6 @@ public class ContentProviderUtils {
|
|||||||
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
|
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
|
||||||
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
|
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
|
||||||
int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
|
int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
|
||||||
int minGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINGRADE);
|
|
||||||
int maxGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXGRADE);
|
|
||||||
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
|
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
|
||||||
|
|
||||||
Track track = new Track();
|
Track track = new Track();
|
||||||
@@ -141,9 +140,6 @@ public class ContentProviderUtils {
|
|||||||
if (!cursor.isNull(stopTimeIndex)) {
|
if (!cursor.isNull(stopTimeIndex)) {
|
||||||
tripStatistics.setStopTime(cursor.getLong(stopTimeIndex));
|
tripStatistics.setStopTime(cursor.getLong(stopTimeIndex));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(numPointsIndex)) {
|
|
||||||
track.setNumberOfPoints(cursor.getInt(numPointsIndex));
|
|
||||||
}
|
|
||||||
if (!cursor.isNull(totalDistanceIndex)) {
|
if (!cursor.isNull(totalDistanceIndex)) {
|
||||||
tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
|
tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
|
||||||
}
|
}
|
||||||
@@ -165,12 +161,6 @@ public class ContentProviderUtils {
|
|||||||
if (!cursor.isNull(elevationGainIndex)) {
|
if (!cursor.isNull(elevationGainIndex)) {
|
||||||
tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
|
tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(minGradeIndex)) {
|
|
||||||
tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex));
|
|
||||||
}
|
|
||||||
if (!cursor.isNull(maxGradeIndex)) {
|
|
||||||
tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex));
|
|
||||||
}
|
|
||||||
if (!cursor.isNull(iconIndex)) {
|
if (!cursor.isNull(iconIndex)) {
|
||||||
track.setIcon(cursor.getString(iconIndex));
|
track.setIcon(cursor.getString(iconIndex));
|
||||||
}
|
}
|
||||||
@@ -181,13 +171,13 @@ public class ContentProviderUtils {
|
|||||||
* Deletes all tracks (including waypoints and track points).
|
* Deletes all tracks (including waypoints and track points).
|
||||||
*/
|
*/
|
||||||
public void deleteAllTracks(Context context) {
|
public void deleteAllTracks(Context context) {
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
|
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
||||||
// Delete tracks last since it triggers a database vaccum call
|
// Delete tracks last since it triggers a database vaccum call
|
||||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||||
|
|
||||||
File dir = FileUtils.getPhotoDir(context);
|
File dir = FileUtils.getPhotoDir(context);
|
||||||
deleteDirectoryRecurse(context, dir);
|
deleteDirectoryRecurse(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,18 +195,17 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes track points and waypoints of a track.
|
* Deletes track points and waypoints of a track.
|
||||||
* Assumes {@link TracksColumns#NUMPOINTS} will be updated by the caller.
|
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
private void deleteTrackPointsAndWaypoints(Context context, long trackId) {
|
private void deleteTrackPointsAndWaypoints(Context context, long trackId) {
|
||||||
String where = TrackPointsColumns.TRACKID + "=?";
|
String where = TrackPointsColumns.TRACKID + "=?";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs);
|
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, where, selectionArgs);
|
||||||
|
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?",
|
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?",
|
||||||
new String[]{Long.toString(trackId)});
|
new String[]{Long.toString(trackId)});
|
||||||
deleteDirectoryRecurse(context, FileUtils.getPhotoDir(context, trackId));
|
deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,10 +213,10 @@ public class ContentProviderUtils {
|
|||||||
*
|
*
|
||||||
* @param dir the directory
|
* @param dir the directory
|
||||||
*/
|
*/
|
||||||
private void deleteDirectoryRecurse(Context context, File dir) {
|
private void deleteDirectoryRecurse(File dir) {
|
||||||
if (dir.exists() && dir.isDirectory()) {
|
if (dir != null && dir.exists() && dir.isDirectory()) {
|
||||||
for (File child : dir.listFiles()) {
|
for (File child : dir.listFiles()) {
|
||||||
deleteDirectoryRecurse(context, child);
|
deleteDirectoryRecurse(child);
|
||||||
}
|
}
|
||||||
dir.delete();
|
dir.delete();
|
||||||
}
|
}
|
||||||
@@ -330,7 +319,6 @@ public class ContentProviderUtils {
|
|||||||
values.put(TracksColumns.CATEGORY, track.getCategory());
|
values.put(TracksColumns.CATEGORY, track.getCategory());
|
||||||
values.put(TracksColumns.STARTTIME, tripStatistics.getStartTime());
|
values.put(TracksColumns.STARTTIME, tripStatistics.getStartTime());
|
||||||
values.put(TracksColumns.STOPTIME, tripStatistics.getStopTime());
|
values.put(TracksColumns.STOPTIME, tripStatistics.getStopTime());
|
||||||
values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints());
|
|
||||||
values.put(TracksColumns.TOTALDISTANCE, tripStatistics.getTotalDistance());
|
values.put(TracksColumns.TOTALDISTANCE, tripStatistics.getTotalDistance());
|
||||||
values.put(TracksColumns.TOTALTIME, tripStatistics.getTotalTime());
|
values.put(TracksColumns.TOTALTIME, tripStatistics.getTotalTime());
|
||||||
values.put(TracksColumns.MOVINGTIME, tripStatistics.getMovingTime());
|
values.put(TracksColumns.MOVINGTIME, tripStatistics.getMovingTime());
|
||||||
@@ -340,8 +328,6 @@ public class ContentProviderUtils {
|
|||||||
values.put(TracksColumns.MINELEVATION, tripStatistics.getMinElevation());
|
values.put(TracksColumns.MINELEVATION, tripStatistics.getMinElevation());
|
||||||
values.put(TracksColumns.MAXELEVATION, tripStatistics.getMaxElevation());
|
values.put(TracksColumns.MAXELEVATION, tripStatistics.getMaxElevation());
|
||||||
values.put(TracksColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain());
|
values.put(TracksColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain());
|
||||||
values.put(TracksColumns.MINGRADE, tripStatistics.getMinGrade());
|
|
||||||
values.put(TracksColumns.MAXGRADE, tripStatistics.getMaxGrade());
|
|
||||||
values.put(TracksColumns.ICON, track.getIcon());
|
values.put(TracksColumns.ICON, track.getIcon());
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
@@ -367,7 +353,6 @@ public class ContentProviderUtils {
|
|||||||
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
||||||
int altitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE);
|
int altitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE);
|
||||||
int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY);
|
int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY);
|
||||||
int speedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED);
|
|
||||||
int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING);
|
int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING);
|
||||||
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
|
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
|
||||||
|
|
||||||
@@ -412,9 +397,6 @@ public class ContentProviderUtils {
|
|||||||
if (!cursor.isNull(accuracyIndex)) {
|
if (!cursor.isNull(accuracyIndex)) {
|
||||||
location.setAccuracy(cursor.getFloat(accuracyIndex));
|
location.setAccuracy(cursor.getFloat(accuracyIndex));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(speedIndex)) {
|
|
||||||
location.setSpeed(cursor.getFloat(speedIndex));
|
|
||||||
}
|
|
||||||
if (!cursor.isNull(bearingIndex)) {
|
if (!cursor.isNull(bearingIndex)) {
|
||||||
location.setBearing(cursor.getFloat(bearingIndex));
|
location.setBearing(cursor.getFloat(bearingIndex));
|
||||||
}
|
}
|
||||||
@@ -611,9 +593,6 @@ public class ContentProviderUtils {
|
|||||||
if (location.hasAccuracy()) {
|
if (location.hasAccuracy()) {
|
||||||
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
|
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
|
||||||
}
|
}
|
||||||
if (location.hasSpeed()) {
|
|
||||||
values.put(WaypointsColumns.SPEED, location.getSpeed());
|
|
||||||
}
|
|
||||||
if (location.hasBearing()) {
|
if (location.hasBearing()) {
|
||||||
values.put(WaypointsColumns.BEARING, location.getBearing());
|
values.put(WaypointsColumns.BEARING, location.getBearing());
|
||||||
}
|
}
|
||||||
@@ -659,7 +638,7 @@ public class ContentProviderUtils {
|
|||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
values[i] = createContentValues(locations[i], trackId);
|
values[i] = createContentValues(locations[i], trackId);
|
||||||
}
|
}
|
||||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -846,7 +825,7 @@ public class ContentProviderUtils {
|
|||||||
* @return the content provider URI of the inserted track point
|
* @return the content provider URI of the inserted track point
|
||||||
*/
|
*/
|
||||||
public Uri insertTrackPoint(Location location, long trackId) {
|
public Uri insertTrackPoint(Location location, long trackId) {
|
||||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId));
|
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(location, trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -926,7 +905,7 @@ public class ContentProviderUtils {
|
|||||||
* @param sortOrder the sort order
|
* @param sortOrder the sort order
|
||||||
*/
|
*/
|
||||||
private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
return contentResolver.query(TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDefaultCursorBatchSize() {
|
int getDefaultCursorBatchSize() {
|
||||||
@@ -942,4 +921,26 @@ public class ContentProviderUtils {
|
|||||||
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
|
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
|
||||||
this.defaultCursorBatchSize = defaultCursorBatchSize;
|
this.defaultCursorBatchSize = defaultCursorBatchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an array of IDs as comma separated string value
|
||||||
|
*
|
||||||
|
* @param ids array with IDs
|
||||||
|
* @return comma separated list of ids
|
||||||
|
*/
|
||||||
|
public static String formatIdListForUri(long[] ids) {
|
||||||
|
StringBuilder idsPathSegment = new StringBuilder();
|
||||||
|
for (long id : ids) {
|
||||||
|
if (idsPathSegment.length() > 0) {
|
||||||
|
idsPathSegment.append(ID_SEPARATOR);
|
||||||
|
}
|
||||||
|
idsPathSegment.append(id);
|
||||||
|
}
|
||||||
|
return idsPathSegment.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] parseTrackIdsFromUri(Uri url) {
|
||||||
|
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.database.Cursor;
|
|||||||
import android.database.SQLException;
|
import android.database.SQLException;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.database.sqlite.SQLiteQueryBuilder;
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -45,27 +44,25 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
|||||||
*/
|
*/
|
||||||
public abstract class CustomContentProvider extends ContentProvider {
|
public abstract class CustomContentProvider extends ContentProvider {
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final int DATABASE_VERSION = 23;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final String DATABASE_NAME = "database.db";
|
|
||||||
|
|
||||||
private static final String TAG = CustomContentProvider.class.getSimpleName();
|
private static final String TAG = CustomContentProvider.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String SQL_LIST_DELIMITER = ",";
|
||||||
|
|
||||||
private final UriMatcher uriMatcher;
|
private final UriMatcher uriMatcher;
|
||||||
|
|
||||||
private SQLiteDatabase db;
|
private SQLiteDatabase db;
|
||||||
|
|
||||||
public CustomContentProvider() {
|
public CustomContentProvider() {
|
||||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_BY_TRACKID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", 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_BY_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
|
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath(), UrlType.WAYPOINTS.ordinal());
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath() + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,7 +78,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
boolean onCreate(Context context) {
|
boolean onCreate(Context context) {
|
||||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(context);
|
||||||
try {
|
try {
|
||||||
db = databaseHelper.getWritableDatabase();
|
db = databaseHelper.getWritableDatabase();
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
@@ -200,11 +197,11 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
break;
|
break;
|
||||||
case TRACKPOINTS_BY_ID:
|
case TRACKPOINTS_BY_ID:
|
||||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + url.getPathSegments().get(1));
|
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||||
break;
|
break;
|
||||||
case TRACKPOINTS_BY_TRACKID:
|
case TRACKPOINTS_BY_TRACKID:
|
||||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + "=" + url.getPathSegments().get(1));
|
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
break;
|
break;
|
||||||
case TRACKS:
|
case TRACKS:
|
||||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||||
@@ -212,7 +209,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
break;
|
break;
|
||||||
case TRACKS_BY_ID:
|
case TRACKS_BY_ID:
|
||||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TracksColumns._ID + "=" + url.getPathSegments().get(1));
|
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
break;
|
break;
|
||||||
case WAYPOINTS:
|
case WAYPOINTS:
|
||||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||||
@@ -220,7 +217,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
break;
|
break;
|
||||||
case WAYPOINTS_BY_ID:
|
case WAYPOINTS_BY_ID:
|
||||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + url.getPathSegments().get(1));
|
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown url " + url);
|
throw new IllegalArgumentException("Unknown url " + url);
|
||||||
@@ -242,7 +239,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
break;
|
break;
|
||||||
case TRACKPOINTS_BY_ID:
|
case TRACKPOINTS_BY_ID:
|
||||||
table = TrackPointsColumns.TABLE_NAME;
|
table = TrackPointsColumns.TABLE_NAME;
|
||||||
whereClause = TrackPointsColumns._ID + "=" + url.getPathSegments().get(1);
|
whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url);
|
||||||
if (!TextUtils.isEmpty(where)) {
|
if (!TextUtils.isEmpty(where)) {
|
||||||
whereClause += " AND (" + where + ")";
|
whereClause += " AND (" + where + ")";
|
||||||
}
|
}
|
||||||
@@ -253,7 +250,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
break;
|
break;
|
||||||
case TRACKS_BY_ID:
|
case TRACKS_BY_ID:
|
||||||
table = TracksColumns.TABLE_NAME;
|
table = TracksColumns.TABLE_NAME;
|
||||||
whereClause = TracksColumns._ID + "=" + url.getPathSegments().get(1);
|
whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url);
|
||||||
if (!TextUtils.isEmpty(where)) {
|
if (!TextUtils.isEmpty(where)) {
|
||||||
whereClause += " AND (" + where + ")";
|
whereClause += " AND (" + where + ")";
|
||||||
}
|
}
|
||||||
@@ -264,7 +261,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
break;
|
break;
|
||||||
case WAYPOINTS_BY_ID:
|
case WAYPOINTS_BY_ID:
|
||||||
table = WaypointsColumns.TABLE_NAME;
|
table = WaypointsColumns.TABLE_NAME;
|
||||||
whereClause = WaypointsColumns._ID + "=" + url.getPathSegments().get(1);
|
whereClause = WaypointsColumns._ID + "=" + ContentUris.parseId(url);
|
||||||
if (!TextUtils.isEmpty(where)) {
|
if (!TextUtils.isEmpty(where)) {
|
||||||
whereClause += " AND (" + where + ")";
|
whereClause += " AND (" + where + ")";
|
||||||
}
|
}
|
||||||
@@ -289,8 +286,15 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
*
|
*
|
||||||
* @param url the url
|
* @param url the url
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
private UrlType getUrlType(Uri url) {
|
private UrlType getUrlType(Uri url) {
|
||||||
return UrlType.values()[uriMatcher.match(url)];
|
UrlType[] urlTypes = UrlType.values();
|
||||||
|
int matchIndex = uriMatcher.match(url);
|
||||||
|
if (0 <= matchIndex && matchIndex < urlTypes.length) {
|
||||||
|
return urlTypes[matchIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unknown URL " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -328,7 +332,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values);
|
long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values);
|
||||||
if (rowId >= 0) {
|
if (rowId >= 0) {
|
||||||
return ContentUris.appendId(TrackPointsColumns.CONTENT_URI.buildUpon(), rowId).build();
|
return ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_ID.buildUpon(), rowId).build();
|
||||||
}
|
}
|
||||||
throw new SQLiteException("Failed to insert a track point " + url);
|
throw new SQLiteException("Failed to insert a track point " + url);
|
||||||
}
|
}
|
||||||
@@ -380,31 +384,4 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
WAYPOINTS,
|
WAYPOINTS,
|
||||||
WAYPOINTS_BY_ID
|
WAYPOINTS_BY_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Database helper for creating and upgrading the database.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
static class DatabaseHelper extends SQLiteOpenHelper {
|
|
||||||
|
|
||||||
public DatabaseHelper(Context context) {
|
|
||||||
this(context, DATABASE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public DatabaseHelper(Context context, String databaseName) {
|
|
||||||
super(context, databaseName, null, DATABASE_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
db.execSQL(TrackPointsColumns.CREATE_TABLE);
|
|
||||||
db.execSQL(TracksColumns.CREATE_TABLE);
|
|
||||||
db.execSQL(WaypointsColumns.CREATE_TABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package de.dennisguse.opentracks.content.provider;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database helper for creating and upgrading the database.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final int DATABASE_VERSION = 25;
|
||||||
|
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String DATABASE_NAME = "database.db";
|
||||||
|
|
||||||
|
|
||||||
|
public CustomSQLiteOpenHelper(Context context) {
|
||||||
|
this(context, DATABASE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public CustomSQLiteOpenHelper(Context context, String databaseName) {
|
||||||
|
super(context, databaseName, null, DATABASE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public CustomSQLiteOpenHelper(Context context, String databaseName, int databaseVersion) {
|
||||||
|
super(context, databaseName, null, databaseVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(TrackPointsColumns.CREATE_TABLE);
|
||||||
|
db.execSQL(TrackPointsColumns.CREATE_TABLE_INDEX);
|
||||||
|
|
||||||
|
db.execSQL(TracksColumns.CREATE_TABLE);
|
||||||
|
|
||||||
|
db.execSQL(WaypointsColumns.CREATE_TABLE);
|
||||||
|
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) {
|
||||||
|
Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion);
|
||||||
|
switch (toVersion) {
|
||||||
|
case 24:
|
||||||
|
upgradeFrom23to24(db);
|
||||||
|
break;
|
||||||
|
case 25:
|
||||||
|
upgradeFrom24to25(db);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
for (int toVersion = oldVersion - 1; toVersion >= newVersion; toVersion--) {
|
||||||
|
Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion);
|
||||||
|
switch (toVersion) {
|
||||||
|
case 23:
|
||||||
|
downgradeFrom24to23(db);
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
downgradeFrom25to24(db);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade from database version 23 (waypoints, tracks): remove unused columns.
|
||||||
|
* SQLite3 does not support drop columns; therefore new tables are created and data is copied.
|
||||||
|
*/
|
||||||
|
private void upgradeFrom23to24(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||||
|
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, icon TEXT)");
|
||||||
|
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, icon FROM tracks_old");
|
||||||
|
db.execSQL("DROP TABLE tracks_old");
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE waypoints RENAME TO waypoints_old");
|
||||||
|
db.execSQL("CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, length FLOAT, duration INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT)");
|
||||||
|
db.execSQL("INSERT INTO waypoints SELECT _id, name, description, category, icon, trackid, length, duration, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM waypoints_old");
|
||||||
|
db.execSQL("DROP TABLE waypoints_old");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downgradeFrom24to23(SQLiteDatabase db) {
|
||||||
|
//Not needed as the delete columns did not contain any data
|
||||||
|
db.beginTransaction();
|
||||||
|
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||||
|
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)");
|
||||||
|
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, 0, 0, icon FROM tracks_old");
|
||||||
|
db.execSQL("DROP TABLE tracks_old");
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE waypoints RENAME TO waypoints_old");
|
||||||
|
db.execSQL("CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, type INTEGER, length FLOAT, duration INTEGER, starttime INTEGER, startid INTEGER, stopid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, photoUrl TEXT)");
|
||||||
|
db.execSQL("INSERT INTO waypoints SELECT _id, name, description, category, icon, trackid, 0, length, duration, 0, 0, 0, longitude, latitude, time, elevation, accuracy, 0, bearing, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, photoUrl FROM waypoints_old");
|
||||||
|
db.execSQL("DROP TABLE waypoints_old");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void upgradeFrom24to25(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
|
||||||
|
db.execSQL("CREATE INDEX waypoints_trackid_index ON waypoints(trackid)");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downgradeFrom25to24(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
db.execSQL("DROP INDEX trackpoints_trackid_index");
|
||||||
|
db.execSQL("DROP INDEX waypoints_trackid_index");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.android.IContentResolver;
|
import de.dennisguse.opentracks.android.IContentResolver;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
@@ -58,30 +59,30 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
private static final String TRACKID_DELIMITER = "_";
|
private static final String TRACKID_DELIMITER = "_";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*", URI_GPX);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*/*", URI_GPX);
|
||||||
|
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_ONLY_TRACK.getName() + "/*", URI_KML_ONLY);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_ONLY_TRACK.getName() + "/*/*", URI_KML_ONLY);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL.getName() + "/*", URI_KML_WITH_TRACKDETAIL);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_ONLY_TRACK.getName() + "/*", URI_KMZ_ONLY_TRACK);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_ONLY_TRACK.getName() + "/*/*", URI_KMZ_ONLY_TRACK);
|
||||||
|
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pair<Uri, String> createURI(long[] trackIds, @NonNull TrackFileFormat trackFileFormat) {
|
public static Pair<Uri, String> createURI(long[] trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
|
||||||
if (trackIds.length == 0) {
|
if (trackIds.length == 0) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder trackIdBuilder = new StringBuilder();
|
||||||
for (long trackId : trackIds) {
|
for (long trackId : trackIds) {
|
||||||
builder.append(trackId).append(TRACKID_DELIMITER);
|
trackIdBuilder.append(trackId).append(TRACKID_DELIMITER);
|
||||||
}
|
}
|
||||||
builder.deleteCharAt(builder.lastIndexOf(TRACKID_DELIMITER));
|
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
|
||||||
|
|
||||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
|
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + trackIdBuilder + "/" + Uri.encode(trackName) + "." + trackFileFormat.getExtension());
|
||||||
String mime = getTypeMime(uri);
|
String mime = getTypeMime(uri);
|
||||||
|
|
||||||
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
|
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
|
||||||
@@ -89,18 +90,18 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
return new Pair<>(uri, mime);
|
return new Pair<>(uri, mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long[] parseURI(Uri uri) {
|
protected static long[] parseURI(Uri uri) {
|
||||||
String lastPathSegment = uri.getLastPathSegment();
|
List<String> uriPaths = uri.getPathSegments();
|
||||||
if (lastPathSegment == null) {
|
if (uriPaths == null || uriPaths.size() < 2) {
|
||||||
|
Log.d(TAG, "URI does not contain any trackIds.");
|
||||||
return new long[]{};
|
return new long[]{};
|
||||||
}
|
}
|
||||||
|
|
||||||
String fileExtension = "." + getTrackFileFormat(uri).getExtension();
|
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
|
||||||
String[] lastPathSegmentSplit = lastPathSegment.replace(fileExtension, "").split(TRACKID_DELIMITER);
|
|
||||||
|
|
||||||
long[] trackIds = new long[lastPathSegmentSplit.length];
|
long[] trackIds = new long[uriTrackIds.length];
|
||||||
for (int i = 0; i < trackIds.length; i++) {
|
for (int i = 0; i < trackIds.length; i++) {
|
||||||
trackIds[i] = Long.valueOf(lastPathSegmentSplit[i]);
|
trackIds[i] = Long.valueOf(uriTrackIds[i]);
|
||||||
}
|
}
|
||||||
return trackIds;
|
return trackIds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,4 +108,13 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
|||||||
public void remove() {
|
public void remove() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
if (cursor == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.fragments;
|
|
||||||
|
|
||||||
public class ChartDistanceFragment extends ChartFragment {
|
|
||||||
|
|
||||||
public ChartDistanceFragment() {
|
|
||||||
super(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,6 @@ import android.view.ViewGroup;
|
|||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
@@ -56,9 +55,18 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public abstract class ChartFragment extends Fragment implements TrackDataListener {
|
public class ChartFragment extends Fragment implements TrackDataListener {
|
||||||
|
|
||||||
private static final String STATE_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
|
private static final String KEY_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
|
||||||
|
|
||||||
|
public static Fragment newInstance(boolean chartByDistance) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putBoolean(KEY_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
|
||||||
|
|
||||||
|
ChartFragment chartFragment = new ChartFragment();
|
||||||
|
chartFragment.setArguments(bundle);
|
||||||
|
return chartFragment;
|
||||||
|
}
|
||||||
|
|
||||||
private final List<double[]> pendingPoints = new ArrayList<>();
|
private final List<double[]> pendingPoints = new ArrayList<>();
|
||||||
|
|
||||||
@@ -68,8 +76,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
private TripStatisticsUpdater tripStatisticsUpdater;
|
private TripStatisticsUpdater tripStatisticsUpdater;
|
||||||
private long startTime;
|
private long startTime;
|
||||||
|
|
||||||
private boolean metricUnits = true;
|
|
||||||
private boolean reportSpeed = true;
|
|
||||||
private int recordingDistanceInterval;
|
private int recordingDistanceInterval;
|
||||||
|
|
||||||
// Modes of operation
|
// Modes of operation
|
||||||
@@ -82,13 +88,9 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (!isResumed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
||||||
boolean metric = PreferencesUtils.isMetricUnits(getContext());
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
if (metricUnits != metric) {
|
if (metricUnits != chartView.getMetricUnits()) {
|
||||||
metricUnits = metric;
|
|
||||||
chartView.setMetricUnits(metricUnits);
|
chartView.setMetricUnits(metricUnits);
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -101,9 +103,8 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||||
boolean speed = PreferencesUtils.isReportSpeed(getContext());
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
||||||
if (reportSpeed != speed) {
|
if (reportSpeed != chartView.getReportSpeed()) {
|
||||||
reportSpeed = speed;
|
|
||||||
chartView.setReportSpeed(reportSpeed);
|
chartView.setReportSpeed(reportSpeed);
|
||||||
setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
|
setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
|
||||||
setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
|
setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
|
||||||
@@ -139,14 +140,13 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public ChartFragment(boolean chartByDistance) {
|
|
||||||
this.chartByDistance = chartByDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
chartByDistance = getArguments().getBoolean(KEY_CHART_VIEW_BY_DISTANCE_KEY, true);
|
||||||
|
|
||||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
|
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
|
||||||
|
|
||||||
// Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
|
// Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
|
||||||
@@ -171,6 +171,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
resumeTrackDataHub();
|
resumeTrackDataHub();
|
||||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||||
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||||
|
|
||||||
checkChartSettings();
|
checkChartSettings();
|
||||||
getActivity().runOnUiThread(updateChart);
|
getActivity().runOnUiThread(updateChart);
|
||||||
@@ -180,7 +181,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
pauseTrackDataHub();
|
pauseTrackDataHub();
|
||||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -190,20 +191,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
layout.removeView(chartView);
|
layout.removeView(chartView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewStateRestored(savedInstanceState);
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
chartByDistance = savedInstanceState.getBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(Track track) {
|
public void onTrackUpdated(Track track) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -284,10 +271,10 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
private void checkChartSettings() {
|
private void checkChartSettings() {
|
||||||
boolean needUpdate = false;
|
boolean needUpdate = false;
|
||||||
|
|
||||||
if (setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed)) {
|
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartView.getReportSpeed())) {
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
if (setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed)) {
|
if (setSeriesEnabled(ChartView.PACE_SERIES, !chartView.getReportSpeed())) {
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +375,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
|
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||||
if (!metricUnits) {
|
if (!chartView.getMetricUnits()) {
|
||||||
distance *= UnitConversions.KM_TO_MI;
|
distance *= UnitConversions.KM_TO_MI;
|
||||||
}
|
}
|
||||||
timeOrDistance = distance;
|
timeOrDistance = distance;
|
||||||
@@ -397,12 +384,12 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
elevation = tripStatisticsUpdater.getSmoothedElevation();
|
elevation = tripStatisticsUpdater.getSmoothedElevation();
|
||||||
if (!metricUnits) {
|
if (!chartView.getMetricUnits()) {
|
||||||
elevation *= UnitConversions.M_TO_FT;
|
elevation *= UnitConversions.M_TO_FT;
|
||||||
}
|
}
|
||||||
|
|
||||||
speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
|
speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
|
||||||
if (!metricUnits) {
|
if (!chartView.getMetricUnits()) {
|
||||||
speed *= UnitConversions.KM_TO_MI;
|
speed *= UnitConversions.KM_TO_MI;
|
||||||
}
|
}
|
||||||
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||||
@@ -448,12 +435,12 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void setMetricUnits(boolean value) {
|
void setMetricUnits(boolean value) {
|
||||||
metricUnits = value;
|
chartView.setMetricUnits(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void setReportSpeed(boolean value) {
|
void setReportSpeed(boolean value) {
|
||||||
reportSpeed = value;
|
chartView.setReportSpeed(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.fragments;
|
|
||||||
|
|
||||||
public class ChartTimeFragment extends ChartFragment {
|
|
||||||
|
|
||||||
public ChartTimeFragment() {
|
|
||||||
super(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,12 +36,9 @@ import de.dennisguse.opentracks.util.DialogUtils;
|
|||||||
public class ConfirmDeleteDialogFragment extends DialogFragment {
|
public class ConfirmDeleteDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
private static final String CONFIRM_DELETE_DIALOG_TAG = "confirmDeleteDialog";
|
private static final String CONFIRM_DELETE_DIALOG_TAG = "confirmDeleteDialog";
|
||||||
private ConfirmDeleteCaller caller;
|
private static final String KEY_TRACK_IDS = "trackIds";
|
||||||
private final long[] trackIds;
|
|
||||||
|
|
||||||
public ConfirmDeleteDialogFragment(long[] trackIds) {
|
private ConfirmDeleteCaller caller;
|
||||||
this.trackIds = trackIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
@@ -49,7 +46,12 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
|||||||
* @param trackIds list of track ids to delete. To delete all, set to size 1 with trackIds[0] == -1L
|
* @param trackIds list of track ids to delete. To delete all, set to size 1 with trackIds[0] == -1L
|
||||||
*/
|
*/
|
||||||
public static void showDialog(FragmentManager fragmentManager, long[] trackIds) {
|
public static void showDialog(FragmentManager fragmentManager, long[] trackIds) {
|
||||||
new ConfirmDeleteDialogFragment(trackIds).show(fragmentManager, CONFIRM_DELETE_DIALOG_TAG);
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putLongArray(KEY_TRACK_IDS, trackIds);
|
||||||
|
|
||||||
|
ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment();
|
||||||
|
deleteTrackDialogFragment.setArguments(bundle);
|
||||||
|
deleteTrackDialogFragment.show(fragmentManager, CONFIRM_DELETE_DIALOG_TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,6 +67,8 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
|
||||||
|
|
||||||
int titleId;
|
int titleId;
|
||||||
int messageId;
|
int messageId;
|
||||||
titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
|
titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
|
||||||
|
|||||||
@@ -38,16 +38,17 @@ import de.dennisguse.opentracks.util.DialogUtils;
|
|||||||
public class DeleteMarkerDialogFragment extends DialogFragment {
|
public class DeleteMarkerDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
private static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog";
|
private static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog";
|
||||||
private final long[] markerIds;
|
private static final String KEY_MARKER_IDS = "markerIds";
|
||||||
|
|
||||||
private DeleteMarkerCaller caller;
|
private DeleteMarkerCaller caller;
|
||||||
|
|
||||||
public DeleteMarkerDialogFragment(long[] markerIds) {
|
|
||||||
this.markerIds = markerIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void showDialog(FragmentManager fragmentManager, long[] markerIds) {
|
public static void showDialog(FragmentManager fragmentManager, long[] markerIds) {
|
||||||
new DeleteMarkerDialogFragment(markerIds).show(fragmentManager, DELETE_MARKER_DIALOG_TAG);
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putLongArray(KEY_MARKER_IDS, markerIds);
|
||||||
|
|
||||||
|
DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment();
|
||||||
|
deleteMarkerDialogFragment.setArguments(bundle);
|
||||||
|
deleteMarkerDialogFragment.show(fragmentManager, DELETE_MARKER_DIALOG_TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,6 +64,8 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS);
|
||||||
|
|
||||||
final FragmentActivity fragmentActivity = getActivity();
|
final FragmentActivity fragmentActivity = getActivity();
|
||||||
int titleId;
|
int titleId;
|
||||||
int messageId;
|
int messageId;
|
||||||
|
|||||||
@@ -40,18 +40,19 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
|||||||
public class FileTypeDialogFragment extends DialogFragment {
|
public class FileTypeDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
private static final String FILE_TYPE_DIALOG_TAG = "fileType";
|
private static final String FILE_TYPE_DIALOG_TAG = "fileType";
|
||||||
private final int titleId;
|
private static final String KEY_TITLE_ID = "titleId";
|
||||||
|
private static final String KEY_OPTION_ID = "optionId";
|
||||||
|
|
||||||
private FileTypeCaller caller;
|
private FileTypeCaller caller;
|
||||||
private final int optionId;
|
|
||||||
|
|
||||||
public FileTypeDialogFragment(int titleId, int optionId) {
|
|
||||||
this.titleId = titleId;
|
|
||||||
this.optionId = optionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void showDialog(FragmentManager fragmentManager, int titleId, int optionId) {
|
public static void showDialog(FragmentManager fragmentManager, int titleId, int optionId) {
|
||||||
new FileTypeDialogFragment(titleId, optionId).show(fragmentManager, FILE_TYPE_DIALOG_TAG);
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt(KEY_TITLE_ID, titleId);
|
||||||
|
bundle.putInt(KEY_OPTION_ID, optionId);
|
||||||
|
|
||||||
|
FileTypeDialogFragment fileTypeDialogFragment = new FileTypeDialogFragment();
|
||||||
|
fileTypeDialogFragment.setArguments(bundle);
|
||||||
|
fileTypeDialogFragment.show(fragmentManager, FILE_TYPE_DIALOG_TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -67,6 +68,9 @@ public class FileTypeDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
int optionId = getArguments().getInt(KEY_OPTION_ID);
|
||||||
|
final int titleId = getArguments().getInt(KEY_TITLE_ID);
|
||||||
|
|
||||||
final TrackFileFormat[] trackFileFormats = {TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.GPX};
|
final TrackFileFormat[] trackFileFormats = {TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.GPX};
|
||||||
String[] choices = new String[trackFileFormats.length];
|
String[] choices = new String[trackFileFormats.length];
|
||||||
for (int i = 0; i < choices.length; i++) {
|
for (int i = 0; i < choices.length; i++) {
|
||||||
|
|||||||
@@ -57,7 +57,11 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private static final String TAG = MarkerDetailFragment.class.getSimpleName();
|
private static final String TAG = MarkerDetailFragment.class.getSimpleName();
|
||||||
private static final String KEY_MARKER_ID = "markerId";
|
private static final String KEY_MARKER_ID = "markerId";
|
||||||
|
|
||||||
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND_MS;
|
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND_MS;
|
||||||
|
|
||||||
|
private MenuItem shareMarkerImageMenuItem;
|
||||||
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
private ImageView photoView;
|
private ImageView photoView;
|
||||||
@@ -143,6 +147,7 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
// Need to update the waypoint in case returning after an edit
|
// Need to update the waypoint in case returning after an edit
|
||||||
updateWaypoint(true);
|
updateWaypoint(true);
|
||||||
updateUi();
|
updateUi();
|
||||||
|
updateMenuItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -168,8 +173,14 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.marker_detail, menu);
|
inflater.inflate(R.menu.marker_detail, menu);
|
||||||
|
shareMarkerImageMenuItem = menu.findItem(R.id.marker_detail_share);
|
||||||
updateWaypoint(false);
|
updateWaypoint(false);
|
||||||
|
updateMenuItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMenuItems() {
|
||||||
|
if (shareMarkerImageMenuItem != null)
|
||||||
|
shareMarkerImageMenuItem.setVisible(waypoint.hasPhoto());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -186,6 +197,13 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
|
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.marker_detail_share:
|
||||||
|
if (waypoint.hasPhoto()) {
|
||||||
|
intent = IntentUtils.newShareImageIntent(getContext(), waypoint.getPhotoURI());
|
||||||
|
intent = Intent.createChooser(intent, null);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
case R.id.marker_detail_delete:
|
case R.id.marker_detail_delete:
|
||||||
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), new long[]{markerId});
|
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), new long[]{markerId});
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -139,15 +139,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
private TextView speedMovingUnit;
|
private TextView speedMovingUnit;
|
||||||
private View gradeElevationSeparator;
|
private View gradeElevationSeparator;
|
||||||
private View gradeElevationContainer;
|
private View gradeElevationContainer;
|
||||||
private TextView elevationMinValue;
|
|
||||||
private TextView elevationMinUnit;
|
|
||||||
private TextView elevationMaxValue;
|
|
||||||
private TextView elevationMaxUnit;
|
|
||||||
private View speedContainer;
|
private View speedContainer;
|
||||||
private TextView speedLabel;
|
private TextView speedLabel;
|
||||||
private TextView speedValue;
|
private TextView speedValue;
|
||||||
private TextView speedUnit;
|
private TextView speedUnit;
|
||||||
private View elevationContainer;
|
|
||||||
private TextView elevationValue;
|
private TextView elevationValue;
|
||||||
private TextView elevationUnit;
|
private TextView elevationUnit;
|
||||||
private View coordinateSeparator;
|
private View coordinateSeparator;
|
||||||
@@ -188,18 +183,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
gradeElevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
gradeElevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
||||||
gradeElevationContainer = view.findViewById(R.id.stats_elevation_container);
|
gradeElevationContainer = view.findViewById(R.id.stats_elevation_container);
|
||||||
|
|
||||||
elevationMinValue = view.findViewById(R.id.stats_elevation_min_value);
|
|
||||||
elevationMinUnit = view.findViewById(R.id.stats_elevation_min_unit);
|
|
||||||
|
|
||||||
elevationMaxValue = view.findViewById(R.id.stats_elevation_max_value);
|
|
||||||
elevationMaxUnit = view.findViewById(R.id.stats_elevation_max_unit);
|
|
||||||
|
|
||||||
speedContainer = view.findViewById(R.id.stats_speed);
|
speedContainer = view.findViewById(R.id.stats_speed);
|
||||||
speedLabel = view.findViewById(R.id.stats_speed_label);
|
speedLabel = view.findViewById(R.id.stats_speed_label);
|
||||||
speedValue = view.findViewById(R.id.stats_speed_value);
|
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||||
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
||||||
|
|
||||||
elevationContainer = view.findViewById(R.id.stats_elevation);
|
|
||||||
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
||||||
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||||
|
|
||||||
@@ -302,18 +290,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
gradeElevationSeparator = null;
|
gradeElevationSeparator = null;
|
||||||
gradeElevationContainer = null;
|
gradeElevationContainer = null;
|
||||||
|
|
||||||
elevationMinValue = null;
|
|
||||||
elevationMinUnit = null;
|
|
||||||
|
|
||||||
elevationMaxValue = null;
|
|
||||||
elevationMaxUnit = null;
|
|
||||||
|
|
||||||
speedContainer = null;
|
speedContainer = null;
|
||||||
speedLabel = null;
|
speedLabel = null;
|
||||||
speedValue = null;
|
speedValue = null;
|
||||||
speedUnit = null;
|
speedUnit = null;
|
||||||
|
|
||||||
elevationContainer = null;
|
|
||||||
elevationValue = null;
|
elevationValue = null;
|
||||||
elevationUnit = null;
|
elevationUnit = null;
|
||||||
|
|
||||||
@@ -542,31 +523,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
speedMovingUnit.setText(parts.second);
|
speedMovingUnit.setText(parts.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make elevation visible?
|
||||||
// Set elevation
|
|
||||||
{
|
{
|
||||||
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(getContext());
|
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||||
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
if (showElevation) {
|
|
||||||
{
|
|
||||||
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation();
|
|
||||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
|
||||||
|
|
||||||
elevationMinValue.setText(parts.first);
|
|
||||||
elevationMinUnit.setText(parts.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation();
|
|
||||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
|
||||||
|
|
||||||
elevationMaxValue.setText(parts.first);
|
|
||||||
elevationMaxUnit.setText(parts.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,7 +558,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
// Set elevation
|
// Set elevation
|
||||||
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(getContext());
|
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||||
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (showGradeElevation) {
|
if (showGradeElevation) {
|
||||||
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
|
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
|
||||||
|
|||||||
@@ -232,7 +232,9 @@ public enum TrackFileFormat {
|
|||||||
/**
|
/**
|
||||||
* Returns whether the format supports photos.
|
* Returns whether the format supports photos.
|
||||||
*/
|
*/
|
||||||
public boolean includesPhotos() { return false; };
|
public boolean includesPhotos() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of for each format.
|
* Returns the name of for each format.
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import de.dennisguse.opentracks.content.data.Track;
|
|||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
import de.dennisguse.opentracks.util.SystemUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,10 +50,10 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private ExportActivity exportActivity;
|
private ExportActivity exportActivity;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
private boolean completed;
|
private boolean completed = false;
|
||||||
|
|
||||||
private int processedTrackCount;
|
private int processedTrackCount = 0;
|
||||||
private int totalTrackCount;
|
private int totalTrackCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an AsyncTask.
|
* Creates an AsyncTask.
|
||||||
@@ -69,10 +68,6 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
context = exportActivity.getApplicationContext();
|
context = exportActivity.getApplicationContext();
|
||||||
contentProviderUtils = new ContentProviderUtils(context);
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
completed = false;
|
|
||||||
processedTrackCount = 0;
|
|
||||||
totalTrackCount = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,17 +93,12 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
try {
|
try {
|
||||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||||
boolean isRecording = PreferencesUtils.isRecording(exportActivity);
|
|
||||||
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(exportActivity);
|
|
||||||
// Get the wake lock if not recording or paused
|
|
||||||
if (!isRecording || isPaused) {
|
|
||||||
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
|
||||||
}
|
|
||||||
return exportAllTracks();
|
return exportAllTracks();
|
||||||
} finally {
|
} finally {
|
||||||
if (wakeLock != null && wakeLock.isHeld()) {
|
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||||
wakeLock.release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
// Write the current location
|
// Write the current location
|
||||||
trackWriter.writeLocation(location);
|
trackWriter.writeLocation(location);
|
||||||
if (trackExporterListener != null) {
|
if (trackExporterListener != null) {
|
||||||
trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
|
trackExporterListener.onProgressUpdate(locationNumber, locationIterator.getCount());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (wroteSegment) {
|
if (wroteSegment) {
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ import javax.xml.parsers.SAXParserFactory;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
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.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
|
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
|
||||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
@@ -262,7 +262,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
trackData.tripStatisticsUpdater.updateTime(trackData.importTime);
|
trackData.tripStatisticsUpdater.updateTime(trackData.importTime);
|
||||||
}
|
}
|
||||||
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
|
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
|
||||||
trackData.track.setNumberOfPoints(trackData.numberOfLocations);
|
|
||||||
contentProviderUtils.updateTrack(trackData.track);
|
contentProviderUtils.updateTrack(trackData.track);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +273,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
|
|
||||||
//If not the first segment, add a pause separator if there is at least one location in the last segment.
|
//If not the first segment, add a pause separator if there is at least one location in the last segment.
|
||||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
|
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
|
||||||
insertLocation(createLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0, 0.0, trackData.lastLocationInCurrentSegment.getTime()));
|
insertLocation(createLocation(TrackPointsColumns.PAUSE_LATITUDE, 0.0, 0.0, trackData.lastLocationInCurrentSegment.getTime()));
|
||||||
}
|
}
|
||||||
trackData.lastLocationInCurrentSegment = null;
|
trackData.lastLocationInCurrentSegment = null;
|
||||||
}
|
}
|
||||||
@@ -344,8 +343,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
|
|
||||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
|
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
|
||||||
// If not the first segment, add a resume separator before adding the first location.
|
// If not the first segment, add a resume separator before adding the first location.
|
||||||
insertLocation(
|
insertLocation(createLocation(TrackPointsColumns.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
|
||||||
createLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
|
|
||||||
}
|
}
|
||||||
trackData.lastLocationInCurrentSegment = location;
|
trackData.lastLocationInCurrentSegment = location;
|
||||||
return location;
|
return location;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import de.dennisguse.opentracks.TrackDetailActivity;
|
|||||||
import de.dennisguse.opentracks.TrackListActivity;
|
import de.dennisguse.opentracks.TrackListActivity;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||||
@@ -70,12 +71,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
*/
|
*/
|
||||||
public class TrackRecordingService extends Service {
|
public class TrackRecordingService extends Service {
|
||||||
|
|
||||||
// TODO Move to a different place.
|
|
||||||
@Deprecated
|
|
||||||
public static final double PAUSE_LATITUDE = 100.0;
|
|
||||||
@Deprecated
|
|
||||||
public static final double RESUME_LATITUDE = 200.0;
|
|
||||||
|
|
||||||
// Anything faster than that (in meters per second) will be considered moving.
|
// Anything faster than that (in meters per second) will be considered moving.
|
||||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||||
|
|
||||||
@@ -386,7 +381,7 @@ public class TrackRecordingService extends Service {
|
|||||||
if (track != null) {
|
if (track != null) {
|
||||||
Location resume = new Location(LocationManager.GPS_PROVIDER);
|
Location resume = new Location(LocationManager.GPS_PROVIDER);
|
||||||
resume.setLongitude(0);
|
resume.setLongitude(0);
|
||||||
resume.setLatitude(RESUME_LATITUDE);
|
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
|
||||||
resume.setTime(System.currentTimeMillis());
|
resume.setTime(System.currentTimeMillis());
|
||||||
insertLocation(track, resume, null);
|
insertLocation(track, resume, null);
|
||||||
}
|
}
|
||||||
@@ -444,7 +439,7 @@ public class TrackRecordingService extends Service {
|
|||||||
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(trackId));
|
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(trackId));
|
||||||
|
|
||||||
// Update the recording track time
|
// Update the recording track time
|
||||||
updateRecordingTrack(track, false);
|
updateRecordingTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
String trackName = TrackNameUtils.getTrackName(this, trackId, track.getTripStatistics().getStartTime());
|
String trackName = TrackNameUtils.getTrackName(this, trackId, track.getTripStatistics().getStartTime());
|
||||||
@@ -473,7 +468,7 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
||||||
pause.setLongitude(0);
|
pause.setLongitude(0);
|
||||||
pause.setLatitude(PAUSE_LATITUDE);
|
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||||
pause.setTime(System.currentTimeMillis());
|
pause.setTime(System.currentTimeMillis());
|
||||||
insertLocation(track, pause, null);
|
insertLocation(track, pause, null);
|
||||||
}
|
}
|
||||||
@@ -610,7 +605,7 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
||||||
pause.setLongitude(0);
|
pause.setLongitude(0);
|
||||||
pause.setLatitude(PAUSE_LATITUDE);
|
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||||
pause.setTime(lastLocation.getTime());
|
pause.setTime(lastLocation.getTime());
|
||||||
insertLocation(track, pause, null);
|
insertLocation(track, pause, null);
|
||||||
|
|
||||||
@@ -655,7 +650,7 @@ public class TrackRecordingService extends Service {
|
|||||||
try {
|
try {
|
||||||
contentProviderUtils.insertTrackPoint(location, track.getId());
|
contentProviderUtils.insertTrackPoint(location, track.getId());
|
||||||
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||||
updateRecordingTrack(track, LocationUtils.isValidLocation(location));
|
updateRecordingTrack(track);
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
/*
|
/*
|
||||||
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
||||||
@@ -668,16 +663,10 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the recording track time.
|
* Updates the recording track time.
|
||||||
* Increase the number of points if it is a new and valid track point.
|
|
||||||
*
|
*
|
||||||
* @param track the track
|
* @param track the track
|
||||||
* @param increaseNumberOfPoints true to increase the number of points
|
|
||||||
*/
|
*/
|
||||||
private void updateRecordingTrack(Track track, boolean increaseNumberOfPoints) {
|
private void updateRecordingTrack(Track track) {
|
||||||
if (increaseNumberOfPoints) {
|
|
||||||
track.setNumberOfPoints(track.getNumberOfPoints() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
trackTripStatisticsUpdater.updateTime(System.currentTimeMillis());
|
trackTripStatisticsUpdater.updateTime(System.currentTimeMillis());
|
||||||
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
||||||
contentProviderUtils.updateTrack(track);
|
contentProviderUtils.updateTrack(track);
|
||||||
@@ -713,17 +702,7 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
// Dijkstra If
|
if (isRecording()) {
|
||||||
if (isRecording() && isPaused()) {
|
|
||||||
stopForeground(true);
|
|
||||||
notificationManager.cancelNotification();
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!isRecording() && !isGpsStarted) {
|
|
||||||
stopForeground(true);
|
|
||||||
notificationManager.cancelNotification();
|
|
||||||
}
|
|
||||||
if (isRecording() && !isPaused()) {
|
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
@@ -745,5 +724,9 @@ public class TrackRecordingService extends Service {
|
|||||||
notificationManager.updateContent(getString(R.string.gps_starting));
|
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||||
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.getNotification());
|
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.getNotification());
|
||||||
}
|
}
|
||||||
|
if (!isRecording() && !isGpsStarted) {
|
||||||
|
stopForeground(true);
|
||||||
|
notificationManager.cancelNotification();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,9 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.AnnouncementUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will periodically announce the user's trip statistics.
|
* This class will periodically announce the user's trip statistics.
|
||||||
@@ -160,7 +157,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
Log.i(TAG, "Speech is not allowed at this time.");
|
Log.i(TAG, "Speech is not allowed at this time.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
speakAnnouncement(getAnnouncement(tripStatistics));
|
String announcement = AnnouncementUtils.getAnnouncement(context, tripStatistics);
|
||||||
|
speakAnnouncement(announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -169,8 +167,6 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
tts.shutdown();
|
tts.shutdown();
|
||||||
tts = null;
|
tts = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTtsReady() {
|
private void onTtsReady() {
|
||||||
@@ -196,81 +192,4 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
|
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
|
||||||
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
|
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAnnouncement(TripStatistics tripStatistics) {
|
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
|
||||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
|
||||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
|
||||||
double distancePerTime = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
|
|
||||||
|
|
||||||
if (distance == 0) {
|
|
||||||
return context.getString(R.string.voice_total_distance_zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!metricUnits) {
|
|
||||||
distance *= UnitConversions.KM_TO_MI;
|
|
||||||
distancePerTime *= UnitConversions.KM_TO_MI;
|
|
||||||
}
|
|
||||||
|
|
||||||
String rate;
|
|
||||||
if (reportSpeed) {
|
|
||||||
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
|
||||||
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
|
|
||||||
} else {
|
|
||||||
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
|
|
||||||
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
|
||||||
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
|
||||||
rate = context.getString(paceId, getAnnounceTime(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
|
||||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
|
|
||||||
|
|
||||||
return context.getString(R.string.voice_template, totalDistance,
|
|
||||||
getAnnounceTime(tripStatistics.getMovingTime()), rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAnnounceTime(long time) {
|
|
||||||
int[] parts = StringUtils.getTimeParts(time);
|
|
||||||
String seconds = context.getResources()
|
|
||||||
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
|
|
||||||
String minutes = context.getResources()
|
|
||||||
.getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]);
|
|
||||||
String hours = context.getResources()
|
|
||||||
.getQuantityString(R.plurals.voiceHours, parts[2], parts[2]);
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (parts[2] != 0) {
|
|
||||||
sb.append(hours);
|
|
||||||
sb.append(" ");
|
|
||||||
}
|
|
||||||
sb.append(minutes);
|
|
||||||
sb.append(" ");
|
|
||||||
sb.append(seconds);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the plural count to be used by getQuantityString.
|
|
||||||
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
|
|
||||||
* <p>
|
|
||||||
* As a temporary workaround, we convert a double quantity to an integer quantity.
|
|
||||||
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
|
|
||||||
* Otherwise, we cast the double quantity to an integer quantity.
|
|
||||||
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
|
|
||||||
*
|
|
||||||
* @param d the double value
|
|
||||||
*/
|
|
||||||
private int getQuantityCount(double d) {
|
|
||||||
if (d == 0) {
|
|
||||||
return 0;
|
|
||||||
} else if (d == 1) {
|
|
||||||
return 1;
|
|
||||||
} else if (d == 2) {
|
|
||||||
return 2;
|
|
||||||
} else {
|
|
||||||
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
|
|
||||||
int count = (int) d;
|
|
||||||
return count < 3 ? 3 : count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class TripStatistics {
|
|||||||
// The min and max elevation (meters) seen on this trip.
|
// The min and max elevation (meters) seen on this trip.
|
||||||
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
|
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
|
||||||
// The min and max grade seen on this trip.
|
// The min and max grade seen on this trip.
|
||||||
|
@Deprecated //Remove grade min/max completely
|
||||||
private final ExtremityMonitor gradeExtremities = new ExtremityMonitor();
|
private final ExtremityMonitor gradeExtremities = new ExtremityMonitor();
|
||||||
|
|
||||||
// The trip start time. This is the system time, might not match the GPs time.
|
// The trip start time. This is the system time, might not match the GPs time.
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
|
|
||||||
import static de.dennisguse.opentracks.services.TrackRecordingService.MAX_NO_MOVEMENT_SPEED;
|
import static de.dennisguse.opentracks.services.TrackRecordingService.MAX_NO_MOVEMENT_SPEED;
|
||||||
import static de.dennisguse.opentracks.services.TrackRecordingService.PAUSE_LATITUDE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updater for {@link TripStatistics}.
|
* Updater for {@link TripStatistics}.
|
||||||
@@ -123,7 +123,7 @@ public class TripStatisticsUpdater {
|
|||||||
updateTime(location.getTime());
|
updateTime(location.getTime());
|
||||||
if (!LocationUtils.isValidLocation(location)) {
|
if (!LocationUtils.isValidLocation(location)) {
|
||||||
// Either pause or resume marker
|
// Either pause or resume marker
|
||||||
if (location.getLatitude() == PAUSE_LATITUDE) {
|
if (location.getLatitude() == TrackPointsColumns.PAUSE_LATITUDE) {
|
||||||
if (lastLocation != null && lastMovingLocation != null && lastLocation != lastMovingLocation) {
|
if (lastLocation != null && lastMovingLocation != null && lastLocation != lastMovingLocation) {
|
||||||
currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation));
|
currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
|
||||||
|
public class AnnouncementUtils {
|
||||||
|
|
||||||
|
private AnnouncementUtils() {}
|
||||||
|
|
||||||
|
public static String getAnnouncement(Context context, TripStatistics tripStatistics) {
|
||||||
|
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||||
|
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||||
|
double distancePerTime = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
|
||||||
|
|
||||||
|
if (distance == 0) {
|
||||||
|
return context.getString(R.string.voice_total_distance_zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!metricUnits) {
|
||||||
|
distance *= UnitConversions.KM_TO_MI;
|
||||||
|
distancePerTime *= UnitConversions.KM_TO_MI;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rate;
|
||||||
|
if (reportSpeed) {
|
||||||
|
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
||||||
|
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
|
||||||
|
} else {
|
||||||
|
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
|
||||||
|
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||||
|
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||||
|
rate = context.getString(paceId, getAnnounceTime(context, time));
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||||
|
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
|
||||||
|
|
||||||
|
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, tripStatistics.getMovingTime()), rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAnnounceTime(Context context, long time) {
|
||||||
|
int[] parts = StringUtils.getTimeParts(time);
|
||||||
|
String seconds = context.getResources()
|
||||||
|
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
|
||||||
|
String minutes = context.getResources()
|
||||||
|
.getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]);
|
||||||
|
String hours = context.getResources()
|
||||||
|
.getQuantityString(R.plurals.voiceHours, parts[2], parts[2]);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (parts[2] != 0) {
|
||||||
|
sb.append(hours);
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
sb.append(minutes);
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(seconds);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the plural count to be used by getQuantityString.
|
||||||
|
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
|
||||||
|
* <p>
|
||||||
|
* As a temporary workaround, we convert a double quantity to an integer quantity.
|
||||||
|
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
|
||||||
|
* Otherwise, we cast the double quantity to an integer quantity.
|
||||||
|
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
|
||||||
|
*
|
||||||
|
* @param d the double value
|
||||||
|
*/
|
||||||
|
public static int getQuantityCount(double d) {
|
||||||
|
if (d == 0) {
|
||||||
|
return 0;
|
||||||
|
} else if (d == 1) {
|
||||||
|
return 1;
|
||||||
|
} else if (d == 2) {
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
|
||||||
|
int count = (int) d;
|
||||||
|
return count < 3 ? 3 : count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -16,11 +16,21 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.BuildConfig;
|
import de.dennisguse.opentracks.BuildConfig;
|
||||||
|
|
||||||
@@ -31,6 +41,8 @@ import de.dennisguse.opentracks.BuildConfig;
|
|||||||
*/
|
*/
|
||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
|
|
||||||
|
private static final String TAG = FileUtils.class.getSimpleName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to transfer picture from the camera.
|
* Used to transfer picture from the camera.
|
||||||
*/
|
*/
|
||||||
@@ -38,6 +50,8 @@ public class FileUtils {
|
|||||||
|
|
||||||
public static final String EXPORT_DIR = "OpenTracks";
|
public static final String EXPORT_DIR = "OpenTracks";
|
||||||
|
|
||||||
|
private static final String JPEG_EXTENSION = "jpeg";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum FAT32 path length. See the FAT32 spec at
|
* The maximum FAT32 path length. See the FAT32 spec at
|
||||||
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
|
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
|
||||||
@@ -196,4 +210,46 @@ public class FileUtils {
|
|||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the image's absolute path from a track identified by trackId.
|
||||||
|
*
|
||||||
|
* @param context the context.
|
||||||
|
* @param trackId the track id.
|
||||||
|
*/
|
||||||
|
public static String getImageUrl(Context context, long trackId) {
|
||||||
|
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||||
|
|
||||||
|
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
|
||||||
|
File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, JPEG_EXTENSION));
|
||||||
|
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a File (src) to a File (dst).
|
||||||
|
*
|
||||||
|
* @param src source file.
|
||||||
|
* @param dst destination file.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void copy(FileDescriptor src, File dst) throws IOException {
|
||||||
|
try (FileChannel in = new FileInputStream(src).getChannel();
|
||||||
|
FileChannel out = new FileOutputStream(dst).getChannel()) {
|
||||||
|
in.transferTo(0, in.size(), out);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// post to log
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Uri for the file.
|
||||||
|
*
|
||||||
|
* @param context the context.
|
||||||
|
* @param file the file.
|
||||||
|
*/
|
||||||
|
public static Uri getUriForFile(Context context, File file) {
|
||||||
|
return FileProvider.getUriForFile(context, FileUtils.FILEPROVIDER, file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -10,6 +9,7 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an {@link Intent} to request showing a Dashboard.
|
* Create an {@link Intent} to request showing a Dashboard.
|
||||||
@@ -24,11 +24,12 @@ public class IntentDashboardUtils {
|
|||||||
private IntentDashboardUtils() {
|
private IntentDashboardUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean startDashboard(Context context, long trackId) {
|
public static boolean startDashboard(Context context, long[] trackIds) {
|
||||||
ArrayList<Uri> uris = new ArrayList<>();
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
|
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
||||||
|
|
||||||
uris.add(0, ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId));
|
uris.add(0, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
||||||
uris.add(1, ContentUris.withAppendedId(TrackPointsColumns.CONTENT_URI, trackId));
|
uris.add(1, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||||
|
|
||||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||||
@@ -47,4 +48,5 @@ public class IntentDashboardUtils {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,9 +75,12 @@ public class IntentUtils {
|
|||||||
if (trackIds.length == 0) {
|
if (trackIds.length == 0) {
|
||||||
throw new RuntimeException("Need to share at least one track.");
|
throw new RuntimeException("Need to share at least one track.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
String trackDescription = "";
|
String trackDescription = "";
|
||||||
if (trackIds.length == 1) {
|
if (trackIds.length == 1) {
|
||||||
Track track = new ContentProviderUtils(context).getTrack(trackIds[0]);
|
Track track = contentProviderUtils.getTrack(trackIds[0]);
|
||||||
trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false);
|
trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +89,12 @@ public class IntentUtils {
|
|||||||
|
|
||||||
ArrayList<Uri> uris = new ArrayList<>();
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
for (long trackId : trackIds) {
|
for (long trackId : trackIds) {
|
||||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
if (track == null) {
|
||||||
|
Log.e(TAG, "TrackId " + trackId + " could not be resolved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, track.getName(), TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
||||||
uris.add(uriAndMime.first);
|
uris.add(uriAndMime.first);
|
||||||
mime = uriAndMime.second;
|
mime = uriAndMime.second;
|
||||||
}
|
}
|
||||||
@@ -99,6 +107,30 @@ public class IntentUtils {
|
|||||||
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription));
|
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an intent to share a waypoint image with an app.
|
||||||
|
*
|
||||||
|
* @param context the context.
|
||||||
|
* @param uri uri with the image to share.
|
||||||
|
*/
|
||||||
|
public static Intent newShareImageIntent(Context context, Uri uri) {
|
||||||
|
String mime = context.getContentResolver().getType(uri);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Because the #166 bug, when you import KMZ tracks then it creates file:/// from markers with photo.
|
||||||
|
* The photos should be content:/// not file:/// because getType(uri) always returns null for file:///
|
||||||
|
* In .setType, to avoid side effects because the #166 bug described above it checks if mime is null.
|
||||||
|
* If it is then it hardcode "images/*".
|
||||||
|
*/
|
||||||
|
return new Intent(Intent.ACTION_SEND)
|
||||||
|
.setType(mime != null ? mime : "image/*")
|
||||||
|
.setAction(Intent.ACTION_SEND)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_image_subject))
|
||||||
|
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body));
|
||||||
|
}
|
||||||
|
|
||||||
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
|
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
|
||||||
showCoordinateOnMap(context, waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
|
showCoordinateOnMap(context, waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
|
||||||
}
|
}
|
||||||
@@ -128,7 +160,6 @@ public class IntentUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send intent to show tracks on a map (needs an another app).
|
* Send intent to show tracks on a map (needs an another app).
|
||||||
*
|
*
|
||||||
@@ -140,14 +171,14 @@ public class IntentUtils {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntentDashboardUtils.startDashboard(context, trackIds[0])) {
|
if (IntentDashboardUtils.startDashboard(context, trackIds)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
|
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
|
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
|
||||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_WITH_TRACKDETAIL);
|
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", TrackFileFormat.KMZ_WITH_TRACKDETAIL);
|
||||||
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
|
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
|
||||||
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M3,4V1h2v3h3v2H5v3H3V6H0V4H3zM6,10V7h3V4h7l1.83,2H21c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H5c-1.1,0 -2,-0.9 -2,-2V10H6zM13,19c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5s-5,2.24 -5,5S10.24,19 13,19zM9.8,14c0,1.77 1.43,3.2 3.2,3.2s3.2,-1.43 3.2,-3.2s-1.43,-3.2 -3.2,-3.2S9.8,12.23 9.8,14z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#FF000000" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#FF000000" android:pathData="M15,1L9,1v2h6L15,1zM11,14h2L13,8h-2v6zM19.03,7.39l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#FF000000" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid
|
||||||
|
android:color="@color/text_gradient_center"/>
|
||||||
|
</shape>
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
android:id="@+id/imageView"
|
android:id="@+id/imageView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="239dp"
|
android:layout_height="239dp"
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:contentDescription="@string/app_name"
|
android:contentDescription="@string/app_name"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
android:src="@drawable/ic_logo_color_24dp" />
|
android:src="@drawable/ic_logo_color_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -80,6 +80,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autoLink="web"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/third_party_libraries" />
|
android:text="@string/third_party_libraries" />
|
||||||
|
|
||||||
|
|||||||
@@ -44,20 +44,6 @@
|
|||||||
android:linksClickable="true"
|
android:linksClickable="true"
|
||||||
android:text="@string/help_track_map_content" />
|
android:text="@string/help_track_map_content" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:text="@string/help_data_statistics_customized_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/help_data_statistics_customized_content" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextMedium"
|
style="@style/TextMedium"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -72,34 +58,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/help_recording_content" />
|
android:text="@string/help_recording_content" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:text="@string/help_gps_imprecise_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/help_gps_imprecise_content" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:text="@string/help_heart_rate_sensors_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/help_heart_rate_sensors_content" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextMedium"
|
style="@style/TextMedium"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -128,20 +86,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/help_track_export_content" />
|
android:text="@string/help_track_export_content" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:text="@string/help_data_upload_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/generic_no" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextMedium"
|
style="@style/TextMedium"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -155,20 +99,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/generic_no" />
|
android:text="@string/generic_no" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:text="@string/help_data_online_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/generic_no" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextMedium"
|
style="@style/TextMedium"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -182,6 +112,36 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/help_data_storage_content" />
|
android:text="@string/help_data_storage_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_gps_troubleshooting_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:text="@string/help_gps_troubleshooting_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/help_support_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:text="@string/help_support_content" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,39 @@ limitations under the License.
|
|||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:importantForAutofill="no" />
|
android:importantForAutofill="no" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/marker_edit_waypoint_photo"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:scaleType="fitStart"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:contentDescription="@string/image_photo_marker" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/marker_edit_waypoint_photo_delete"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:background="@drawable/shape_circle_darkgray"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:src="@drawable/ic_delete_forever_24dp" />
|
||||||
|
</RelativeLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -209,11 +209,6 @@ limitations under the License.
|
|||||||
android:layout_gravity="center">
|
android:layout_gravity="center">
|
||||||
|
|
||||||
<!-- Current elevation -->
|
<!-- Current elevation -->
|
||||||
<TableRow
|
|
||||||
android:id="@+id/stats_elevation"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
style="@style/StatsSmallValueContainer"
|
style="@style/StatsSmallValueContainer"
|
||||||
android:layout_gravity="start|bottom">
|
android:layout_gravity="start|bottom">
|
||||||
@@ -227,80 +222,6 @@ limitations under the License.
|
|||||||
android:id="@+id/stats_elevation_current_unit"
|
android:id="@+id/stats_elevation_current_unit"
|
||||||
style="@style/StatsUnit" />
|
style="@style/StatsUnit" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
<!-- Minimal elevation -->
|
|
||||||
<TableRow
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
style="@style/StatsSmallValueContainer"
|
|
||||||
android:layout_gravity="start|bottom">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_elevation_min_value"
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_elevation_min_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
style="@style/StatsSmallValueContainer"
|
|
||||||
android:layout_gravity="start|bottom">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:visibility="invisible" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsSmallLabel"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:text="@string/stats_min" />
|
|
||||||
</LinearLayout>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
<!-- Maximal elevation -->
|
|
||||||
<TableRow
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
style="@style/StatsSmallValueContainer"
|
|
||||||
android:layout_gravity="start|bottom">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_elevation_max_value"
|
|
||||||
style="@style/StatsSmallValue" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_elevation_max_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
style="@style/StatsSmallValueContainer"
|
|
||||||
android:layout_gravity="start|bottom">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:visibility="invisible" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsSmallLabel"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:text="@string/stats_max" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</TableRow>
|
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ limitations under the License.
|
|||||||
-->
|
-->
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
|
||||||
android:id="@+id/list_context_menu_share"
|
|
||||||
android:icon="@drawable/ic_share_24dp"
|
|
||||||
android:title="@string/menu_share"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/list_context_menu_show_on_map"
|
android:id="@+id/list_context_menu_show_on_map"
|
||||||
android:icon="@drawable/ic_map_24dp"
|
android:icon="@drawable/ic_map_24dp"
|
||||||
android:title="@string/menu_show_on_map"
|
android:title="@string/menu_show_on_map"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/list_context_menu_share"
|
||||||
|
android:icon="@drawable/ic_share_24dp"
|
||||||
|
android:title="@string/menu_share"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/list_context_menu_edit"
|
android:id="@+id/list_context_menu_edit"
|
||||||
android:icon="@drawable/ic_edit_24dp"
|
android:icon="@drawable/ic_edit_24dp"
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ limitations under the License.
|
|||||||
android:icon="@drawable/ic_map_24dp"
|
android:icon="@drawable/ic_map_24dp"
|
||||||
android:title="@string/menu_show_on_map"
|
android:title="@string/menu_show_on_map"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/marker_detail_share"
|
||||||
|
android:icon="@drawable/ic_share_24dp"
|
||||||
|
android:title="@string/menu_share"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/marker_detail_edit"
|
android:id="@+id/marker_detail_edit"
|
||||||
android:icon="@drawable/ic_edit_24dp"
|
android:icon="@drawable/ic_edit_24dp"
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/marker_edit_insert_photo"
|
||||||
|
android:icon="@drawable/ic_add_a_photo_24dp"
|
||||||
|
android:orderInCategory="2"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_insert_photo" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/marker_edit_insert_gallery_img"
|
||||||
|
android:icon="@drawable/ic_add_photo_alternate_24dp"
|
||||||
|
android:orderInCategory="2"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_insert_gallery_img" />
|
||||||
|
</menu>
|
||||||
@@ -21,12 +21,6 @@ limitations under the License.
|
|||||||
android:orderInCategory="1"
|
android:orderInCategory="1"
|
||||||
app:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:title="@string/menu_insert_marker" />
|
android:title="@string/menu_insert_marker" />
|
||||||
<item
|
|
||||||
android:id="@+id/track_detail_insert_photo"
|
|
||||||
android:icon="@drawable/ic_camera_24dp"
|
|
||||||
android:orderInCategory="2"
|
|
||||||
app:showAsAction="ifRoom"
|
|
||||||
android:title="@string/menu_insert_photo" />
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_detail_menu_show_on_map"
|
android:id="@+id/track_detail_menu_show_on_map"
|
||||||
android:icon="@drawable/ic_map_24dp"
|
android:icon="@drawable/ic_map_24dp"
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">مستشعر بلوتوث</string>
|
<string name="settings_sensor_bluetooth_sensor">مستشعر بلوتوث</string>
|
||||||
<string name="settings_sensor_connected">تم التوصيل بـ %1$s</string>
|
<string name="settings_sensor_connected">تم التوصيل بـ %1$s</string>
|
||||||
<string name="settings_stats_coordinate">خط العرض/خط الطول</string>
|
<string name="settings_stats_coordinate">خط العرض/خط الطول</string>
|
||||||
<string name="settings_stats_grade_elevation">الدرجة/الارتفاع</string>
|
|
||||||
<string name="settings_stats_rate_title">المعدل المفضل</string>
|
<string name="settings_stats_rate_title">المعدل المفضل</string>
|
||||||
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
|
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
|
||||||
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
|
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensor</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensor</string>
|
||||||
<string name="settings_sensor_connected">Connected to %1$s</string>
|
<string name="settings_sensor_connected">Connected to %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitude/Longitude</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_rate_title">Preferred rate</string>
|
||||||
<string name="settings_stats_units_imperial">Imperial (miles, ft)</string>
|
<string name="settings_stats_units_imperial">Imperial (miles, ft)</string>
|
||||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||||
|
|||||||
@@ -192,6 +192,7 @@
|
|||||||
<string name="menu_import_all">Importar todo</string>
|
<string name="menu_import_all">Importar todo</string>
|
||||||
<string name="menu_insert_marker">Insertar marcador</string>
|
<string name="menu_insert_marker">Insertar marcador</string>
|
||||||
<string name="menu_insert_photo">Insertar foto</string>
|
<string name="menu_insert_photo">Insertar foto</string>
|
||||||
|
<string name="menu_insert_gallery_img">Insertar imagen de galería</string>
|
||||||
<string name="menu_map">Mapa</string>
|
<string name="menu_map">Mapa</string>
|
||||||
<string name="menu_markers">Marcadores</string>
|
<string name="menu_markers">Marcadores</string>
|
||||||
<string name="menu_play">Reproducir</string>
|
<string name="menu_play">Reproducir</string>
|
||||||
@@ -262,7 +263,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Conectado a %1$s</string>
|
<string name="settings_sensor_connected">Conectado a %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitud/longitud</string>
|
<string name="settings_stats_coordinate">Latitud/longitud</string>
|
||||||
<string name="settings_stats_grade_elevation">Pendiente/elevación</string>
|
|
||||||
<string name="settings_stats_rate_title">Tasa preferida</string>
|
<string name="settings_stats_rate_title">Tasa preferida</string>
|
||||||
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
|
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
|
||||||
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
||||||
@@ -446,4 +446,7 @@
|
|||||||
<string name="waypoint_type_waterfall">cascada</string>
|
<string name="waypoint_type_waterfall">cascada</string>
|
||||||
<string name="waypoint_type_water_fountain">fuente de agua</string>
|
<string name="waypoint_type_water_fountain">fuente de agua</string>
|
||||||
<string name="waypoint_type_waypoint">punto de referencia</string>
|
<string name="waypoint_type_waypoint">punto de referencia</string>
|
||||||
|
<!-- Share image -->
|
||||||
|
<string name="share_image_subject">Me gustaría compartir una imagen contigo</string>
|
||||||
|
<string name="share_image_body">Creo que esta imagen podría interesarte.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -263,7 +263,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth сензор</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth сензор</string>
|
||||||
<string name="settings_sensor_connected">Свързан с %1$s</string>
|
<string name="settings_sensor_connected">Свързан с %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Ширина и дължина</string>
|
<string name="settings_stats_coordinate">Ширина и дължина</string>
|
||||||
<string name="settings_stats_grade_elevation">Наклон/кота</string>
|
|
||||||
<string name="settings_stats_rate_title">Предпочитана мярка</string>
|
<string name="settings_stats_rate_title">Предпочитана мярка</string>
|
||||||
<string name="settings_stats_units_imperial">Имперски (мил, фт)</string>
|
<string name="settings_stats_units_imperial">Имперски (мил, фт)</string>
|
||||||
<string name="settings_stats_units_metric">Метрични (км, м)</string>
|
<string name="settings_stats_units_metric">Метрични (км, м)</string>
|
||||||
|
|||||||
@@ -265,7 +265,6 @@ es vegi el cel. Torneu-ho a provar més tard.</string>
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Connectat a %1$s</string>
|
<string name="settings_sensor_connected">Connectat a %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitud/Longitud:</string>
|
<string name="settings_stats_coordinate">Latitud/Longitud:</string>
|
||||||
<string name="settings_stats_grade_elevation">Desnivell/elevació</string>
|
|
||||||
<string name="settings_stats_rate_title">Mesura preferida</string>
|
<string name="settings_stats_rate_title">Mesura preferida</string>
|
||||||
<string name="settings_stats_units_imperial">Sistema anglosaxó (milles, peus)</string>
|
<string name="settings_stats_units_imperial">Sistema anglosaxó (milles, peus)</string>
|
||||||
<string name="settings_stats_units_metric">Sistema mètric (km, m)</string>
|
<string name="settings_stats_units_metric">Sistema mètric (km, m)</string>
|
||||||
@@ -449,4 +448,7 @@ es vegi el cel. Torneu-ho a provar més tard.</string>
|
|||||||
<string name="waypoint_type_waterfall">salt d\'aigua</string>
|
<string name="waypoint_type_waterfall">salt d\'aigua</string>
|
||||||
<string name="waypoint_type_water_fountain">font d\'aigua</string>
|
<string name="waypoint_type_water_fountain">font d\'aigua</string>
|
||||||
<string name="waypoint_type_waypoint">punt de ruta</string>
|
<string name="waypoint_type_waypoint">punt de ruta</string>
|
||||||
|
<!-- Share image -->
|
||||||
|
<string name="share_image_subject">Vull compartir una imatge amb tu</string>
|
||||||
|
<string name="share_image_body">Crec que aquesta imatge et podria interessar.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Připojeno k senzoru %1$s</string>
|
<string name="settings_sensor_connected">Připojeno k senzoru %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Zeměpisná šířka/délka</string>
|
<string name="settings_stats_coordinate">Zeměpisná šířka/délka</string>
|
||||||
<string name="settings_stats_grade_elevation">Sklon/Převýšení</string>
|
|
||||||
<string name="settings_stats_rate_title">Preferovaná rychlost</string>
|
<string name="settings_stats_rate_title">Preferovaná rychlost</string>
|
||||||
<string name="settings_stats_units_imperial">Imperiální jednotky (míle, stopy)</string>
|
<string name="settings_stats_units_imperial">Imperiální jednotky (míle, stopy)</string>
|
||||||
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
|
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
|
||||||
|
|||||||
@@ -265,7 +265,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
|
||||||
<string name="settings_sensor_connected">Tilsluttet %1$s</string>
|
<string name="settings_sensor_connected">Tilsluttet %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Breddegrad/længdegrad</string>
|
<string name="settings_stats_coordinate">Breddegrad/længdegrad</string>
|
||||||
<string name="settings_stats_grade_elevation">Hældning/stigning</string>
|
|
||||||
<string name="settings_stats_rate_title">Foretrukkenfartenhed</string>
|
<string name="settings_stats_rate_title">Foretrukkenfartenhed</string>
|
||||||
<string name="settings_stats_units_imperial">Britisk (mil, ft)</string>
|
<string name="settings_stats_units_imperial">Britisk (mil, ft)</string>
|
||||||
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
|
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
|
||||||
|
|||||||
@@ -265,7 +265,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth-Sensor</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth-Sensor</string>
|
||||||
<string name="settings_sensor_connected">Verbunden mit %1$s</string>
|
<string name="settings_sensor_connected">Verbunden mit %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Breiten-/Längengrad</string>
|
<string name="settings_stats_coordinate">Breiten-/Längengrad</string>
|
||||||
<string name="settings_stats_grade_elevation">Gefälle/Höhe</string>
|
|
||||||
<string name="settings_stats_rate_title">Bevorzugtes Maß</string>
|
<string name="settings_stats_rate_title">Bevorzugtes Maß</string>
|
||||||
<string name="settings_stats_units_imperial">Englisch (mi, ft)</string>
|
<string name="settings_stats_units_imperial">Englisch (mi, ft)</string>
|
||||||
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
|
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
|
||||||
|
|||||||
@@ -263,7 +263,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Αισθητήρας Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Αισθητήρας Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Έγινε σύνδεση με %1$s</string>
|
<string name="settings_sensor_connected">Έγινε σύνδεση με %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Πλάτος/Μήκος</string>
|
<string name="settings_stats_coordinate">Πλάτος/Μήκος</string>
|
||||||
<string name="settings_stats_grade_elevation">Κλίση/Ανύψωση</string>
|
|
||||||
<string name="settings_stats_rate_title">Προτιμώμενος ρυθμός</string>
|
<string name="settings_stats_rate_title">Προτιμώμενος ρυθμός</string>
|
||||||
<string name="settings_stats_units_imperial">Αγγλοσαξονικό (μίλια, πόδια)</string>
|
<string name="settings_stats_units_imperial">Αγγλοσαξονικό (μίλια, πόδια)</string>
|
||||||
<string name="settings_stats_units_metric">Μετρικό (χλμ, μέτρα)</string>
|
<string name="settings_stats_units_metric">Μετρικό (χλμ, μέτρα)</string>
|
||||||
|
|||||||
@@ -169,6 +169,7 @@
|
|||||||
<string name="marker_add_error">No se puede insertar el marcador. No se recibe señal de GPS. Inténtalo de nuevo.</string>
|
<string name="marker_add_error">No se puede insertar el marcador. No se recibe señal de GPS. Inténtalo de nuevo.</string>
|
||||||
<string name="marker_add_success">Se ha insertado un marcador</string>
|
<string name="marker_add_success">Se ha insertado un marcador</string>
|
||||||
<string name="marker_add_canceled">Cancelado, no se ha añadido ningún marcador</string>
|
<string name="marker_add_canceled">Cancelado, no se ha añadido ningún marcador</string>
|
||||||
|
<string name="marker_add_photo_canceled">Cancelado, no se ha añadido la foto al marcador</string>
|
||||||
<string name="marker_delete_all_confirm_message">Todos los marcadores de esta ruta se eliminarán del dispositivo de forma permanente</string>
|
<string name="marker_delete_all_confirm_message">Todos los marcadores de esta ruta se eliminarán del dispositivo de forma permanente</string>
|
||||||
<string name="marker_delete_one_confirm_message">El marcador se eliminará del dispositivo de forma permanente</string>
|
<string name="marker_delete_one_confirm_message">El marcador se eliminará del dispositivo de forma permanente</string>
|
||||||
<string name="marker_delete_one_confirm_title">¿Eliminar marcador?</string>
|
<string name="marker_delete_one_confirm_title">¿Eliminar marcador?</string>
|
||||||
@@ -195,6 +196,7 @@
|
|||||||
<string name="menu_import_all">Importar todo</string>
|
<string name="menu_import_all">Importar todo</string>
|
||||||
<string name="menu_insert_marker">Insertar marcador</string>
|
<string name="menu_insert_marker">Insertar marcador</string>
|
||||||
<string name="menu_insert_photo">Insertar foto</string>
|
<string name="menu_insert_photo">Insertar foto</string>
|
||||||
|
<string name="menu_insert_gallery_img">Insertar imagen de galería</string>
|
||||||
<string name="menu_map">Mapa</string>
|
<string name="menu_map">Mapa</string>
|
||||||
<string name="menu_markers">Marcadores</string>
|
<string name="menu_markers">Marcadores</string>
|
||||||
<string name="menu_play">Reproducir</string>
|
<string name="menu_play">Reproducir</string>
|
||||||
@@ -265,7 +267,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Conectado a %1$s</string>
|
<string name="settings_sensor_connected">Conectado a %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitud/Longitud</string>
|
<string name="settings_stats_coordinate">Latitud/Longitud</string>
|
||||||
<string name="settings_stats_grade_elevation">Pendiente/Elevación</string>
|
|
||||||
<string name="settings_stats_rate_title">Ritmo preferido</string>
|
<string name="settings_stats_rate_title">Ritmo preferido</string>
|
||||||
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
|
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
|
||||||
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
||||||
@@ -449,4 +450,7 @@
|
|||||||
<string name="waypoint_type_waterfall">cascada</string>
|
<string name="waypoint_type_waterfall">cascada</string>
|
||||||
<string name="waypoint_type_water_fountain">fuente de agua</string>
|
<string name="waypoint_type_water_fountain">fuente de agua</string>
|
||||||
<string name="waypoint_type_waypoint">Punto de ruta</string>
|
<string name="waypoint_type_waypoint">Punto de ruta</string>
|
||||||
|
<!-- Share image -->
|
||||||
|
<string name="share_image_subject">Me gustaría compartir una imagen contigo</string>
|
||||||
|
<string name="share_image_body">Creo que esta imagen te podría interesar.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetoothi andur</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetoothi andur</string>
|
||||||
<string name="settings_sensor_connected">Ühendatud anduriga %1$s</string>
|
<string name="settings_sensor_connected">Ühendatud anduriga %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</string>
|
<string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</string>
|
||||||
<string name="settings_stats_grade_elevation">Kalle/tõus</string>
|
|
||||||
<string name="settings_stats_rate_title">Eelistatud määr</string>
|
<string name="settings_stats_rate_title">Eelistatud määr</string>
|
||||||
<string name="settings_stats_units_imperial">Inglise mõõtühikud (miili, jalga)</string>
|
<string name="settings_stats_units_imperial">Inglise mõõtühikud (miili, jalga)</string>
|
||||||
<string name="settings_stats_units_metric">Meetermõõdustik (km, m)</string>
|
<string name="settings_stats_units_metric">Meetermõõdustik (km, m)</string>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth sentsorea</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth sentsorea</string>
|
||||||
<string name="settings_sensor_connected">Honekin konektatuta: %1$s</string>
|
<string name="settings_sensor_connected">Honekin konektatuta: %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitudea/Longitudea</string>
|
<string name="settings_stats_coordinate">Latitudea/Longitudea</string>
|
||||||
<string name="settings_stats_grade_elevation">Inklinazioa/Altitudea</string>
|
|
||||||
<string name="settings_stats_rate_title">Erritmo hobetsia</string>
|
<string name="settings_stats_rate_title">Erritmo hobetsia</string>
|
||||||
<string name="settings_stats_units_imperial">Inperiala (mi, ft)</string>
|
<string name="settings_stats_units_imperial">Inperiala (mi, ft)</string>
|
||||||
<string name="settings_stats_units_metric">Metrikoa (km, m)</string>
|
<string name="settings_stats_units_metric">Metrikoa (km, m)</string>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">حسگر بلوتوث</string>
|
<string name="settings_sensor_bluetooth_sensor">حسگر بلوتوث</string>
|
||||||
<string name="settings_sensor_connected">به %1$s متصل شد</string>
|
<string name="settings_sensor_connected">به %1$s متصل شد</string>
|
||||||
<string name="settings_stats_coordinate">عرض جغرافیایی/ طول جغرافیایی</string>
|
<string name="settings_stats_coordinate">عرض جغرافیایی/ طول جغرافیایی</string>
|
||||||
<string name="settings_stats_grade_elevation">درجه / ارتفاع</string>
|
|
||||||
<string name="settings_stats_rate_title">اولویت سنجه سرعت</string>
|
<string name="settings_stats_rate_title">اولویت سنجه سرعت</string>
|
||||||
<string name="settings_stats_units_imperial">انگلیسی (مایل، فوت)</string>
|
<string name="settings_stats_units_imperial">انگلیسی (مایل، فوت)</string>
|
||||||
<string name="settings_stats_units_metric">متریک (کیلومتر، متر)</string>
|
<string name="settings_stats_units_metric">متریک (کیلومتر، متر)</string>
|
||||||
|
|||||||
@@ -265,7 +265,6 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth-anturi</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth-anturi</string>
|
||||||
<string name="settings_sensor_connected">Yhdistetty: %1$s</string>
|
<string name="settings_sensor_connected">Yhdistetty: %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Leveys-/pituusaste</string>
|
<string name="settings_stats_coordinate">Leveys-/pituusaste</string>
|
||||||
<string name="settings_stats_grade_elevation">Korkeustaso/korkeus</string>
|
|
||||||
<string name="settings_stats_rate_title">Haluttu nopeus</string>
|
<string name="settings_stats_rate_title">Haluttu nopeus</string>
|
||||||
<string name="settings_stats_units_imperial">Brittiläinen järj.
|
<string name="settings_stats_units_imperial">Brittiläinen järj.
|
||||||
(maili, jalka)</string>
|
(maili, jalka)</string>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Connecté à %1$s</string>
|
<string name="settings_sensor_connected">Connecté à %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitude/longitude</string>
|
<string name="settings_stats_coordinate">Latitude/longitude</string>
|
||||||
<string name="settings_stats_grade_elevation">Inclinaison/Altitude</string>
|
|
||||||
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
||||||
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
||||||
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
||||||
|
|||||||
@@ -264,7 +264,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Connecté à %1$s</string>
|
<string name="settings_sensor_connected">Connecté à %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||||
<string name="settings_stats_grade_elevation">Inclinaison/Altitude</string>
|
|
||||||
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
||||||
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
||||||
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
||||||
|
|||||||
@@ -264,7 +264,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Connecté à %1$s</string>
|
<string name="settings_sensor_connected">Connecté à %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||||
<string name="settings_stats_grade_elevation">Inclinaison/Altitude</string>
|
|
||||||
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
||||||
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
||||||
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
||||||
@@ -448,4 +447,11 @@
|
|||||||
<string name="waypoint_type_waterfall">cascade</string>
|
<string name="waypoint_type_waterfall">cascade</string>
|
||||||
<string name="waypoint_type_water_fountain">fontaine</string>
|
<string name="waypoint_type_water_fountain">fontaine</string>
|
||||||
<string name="waypoint_type_waypoint">point de cheminement</string>
|
<string name="waypoint_type_waypoint">point de cheminement</string>
|
||||||
|
<string name="settings_recording_show_on_lockscreen_while_recording_title">Afficher les statistiques sur l\'écran de verrouillage</string>
|
||||||
|
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Pendant l\'enregistrement, afficher les statistiques sans déverrouiller l\'appareil.</string>
|
||||||
|
<string name="settings_recording_keep_screen_on_while_recording_title">Garder l\'écran allumé</string>
|
||||||
|
<string name="settings_recording_keep_screen_on_while_recording_summary">Pendant l\'enregistrement, garder l\'écran allumé.</string>
|
||||||
|
<string name="about_preference_title">À propos d\'OpenTracks</string>
|
||||||
|
<string name="export_with_photos">avec les photos</string>
|
||||||
|
<string name="export_without_photos">sans les photos</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Conectado a %1$s</string>
|
<string name="settings_sensor_connected">Conectado a %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitude/lonxitude</string>
|
<string name="settings_stats_coordinate">Latitude/lonxitude</string>
|
||||||
<string name="settings_stats_grade_elevation">Pendente/elevación</string>
|
|
||||||
<string name="settings_stats_rate_title">Indicador preferido</string>
|
<string name="settings_stats_rate_title">Indicador preferido</string>
|
||||||
<string name="settings_stats_units_imperial">Imperiais (millas, pés)</string>
|
<string name="settings_stats_units_imperial">Imperiais (millas, pés)</string>
|
||||||
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth सेंसर</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth सेंसर</string>
|
||||||
<string name="settings_sensor_connected">%1$s से जुड़ा है</string>
|
<string name="settings_sensor_connected">%1$s से जुड़ा है</string>
|
||||||
<string name="settings_stats_coordinate">अक्षांश/देशांतर</string>
|
<string name="settings_stats_coordinate">अक्षांश/देशांतर</string>
|
||||||
<string name="settings_stats_grade_elevation">ढलान/ऊंचाई</string>
|
|
||||||
<string name="settings_stats_rate_title">पसंदीदा दर</string>
|
<string name="settings_stats_rate_title">पसंदीदा दर</string>
|
||||||
<string name="settings_stats_units_imperial">इंपीरियल (मील, फुट)</string>
|
<string name="settings_stats_units_imperial">इंपीरियल (मील, फुट)</string>
|
||||||
<string name="settings_stats_units_metric">मेट्रिक (किमी, मी)</string>
|
<string name="settings_stats_units_metric">मेट्रिक (किमी, मी)</string>
|
||||||
|
|||||||
@@ -263,7 +263,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetootha</string>
|
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetootha</string>
|
||||||
<string name="settings_sensor_connected">Povezan sa senzorom %1$s</string>
|
<string name="settings_sensor_connected">Povezan sa senzorom %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Geografska širina/duljina</string>
|
<string name="settings_stats_coordinate">Geografska širina/duljina</string>
|
||||||
<string name="settings_stats_grade_elevation">Nagib/elevacija</string>
|
|
||||||
<string name="settings_stats_rate_title">Preferirana stopa</string>
|
<string name="settings_stats_rate_title">Preferirana stopa</string>
|
||||||
<string name="settings_stats_units_imperial">Imperijalne (milje, stope)</string>
|
<string name="settings_stats_units_imperial">Imperijalne (milje, stope)</string>
|
||||||
<string name="settings_stats_units_metric">Metričke (km, m)</string>
|
<string name="settings_stats_units_metric">Metričke (km, m)</string>
|
||||||
|
|||||||
@@ -263,7 +263,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth érzékelő</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth érzékelő</string>
|
||||||
<string name="settings_sensor_connected">Kapcsolódva ehhez: %1$s</string>
|
<string name="settings_sensor_connected">Kapcsolódva ehhez: %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Szélesség/hosszúság</string>
|
<string name="settings_stats_coordinate">Szélesség/hosszúság</string>
|
||||||
<string name="settings_stats_grade_elevation">Lejtés/magasság</string>
|
|
||||||
<string name="settings_stats_rate_title">Mérték választása</string>
|
<string name="settings_stats_rate_title">Mérték választása</string>
|
||||||
<string name="settings_stats_units_imperial">Angolszász (mérföld, láb)</string>
|
<string name="settings_stats_units_imperial">Angolszász (mérföld, láb)</string>
|
||||||
<string name="settings_stats_units_metric">Metrikus (km, m)</string>
|
<string name="settings_stats_units_metric">Metrikus (km, m)</string>
|
||||||
|
|||||||
@@ -264,7 +264,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Tersambung ke %1$s</string>
|
<string name="settings_sensor_connected">Tersambung ke %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Lintang/Bujur</string>
|
<string name="settings_stats_coordinate">Lintang/Bujur</string>
|
||||||
<string name="settings_stats_grade_elevation">Kemiringan/Tinggi</string>
|
|
||||||
<string name="settings_stats_rate_title">Laju yang dipilih</string>
|
<string name="settings_stats_rate_title">Laju yang dipilih</string>
|
||||||
<string name="settings_stats_units_imperial">Imperial (mil, kaki)</string>
|
<string name="settings_stats_units_imperial">Imperial (mil, kaki)</string>
|
||||||
<string name="settings_stats_units_metric">Metrik (km, m)</string>
|
<string name="settings_stats_units_metric">Metrik (km, m)</string>
|
||||||
|
|||||||
@@ -265,7 +265,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Sensore bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Sensore bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Connesso a %1$s</string>
|
<string name="settings_sensor_connected">Connesso a %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
|
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
|
||||||
<string name="settings_stats_grade_elevation">Pendenza/altezza</string>
|
|
||||||
<string name="settings_stats_rate_title">Velocità preferita</string>
|
<string name="settings_stats_rate_title">Velocità preferita</string>
|
||||||
<string name="settings_stats_units_imperial">Imperiale (mi, ft)</string>
|
<string name="settings_stats_units_imperial">Imperiale (mi, ft)</string>
|
||||||
<string name="settings_stats_units_metric">Metrico (km, m)</string>
|
<string name="settings_stats_units_metric">Metrico (km, m)</string>
|
||||||
|
|||||||
@@ -264,7 +264,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">חיישן Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">חיישן Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">מחובר ל-%1$s</string>
|
<string name="settings_sensor_connected">מחובר ל-%1$s</string>
|
||||||
<string name="settings_stats_coordinate">קו רוחב/קו אורך</string>
|
<string name="settings_stats_coordinate">קו רוחב/קו אורך</string>
|
||||||
<string name="settings_stats_grade_elevation">שיפוע/גובה</string>
|
|
||||||
<string name="settings_stats_rate_title">קצב מועדף</string>
|
<string name="settings_stats_rate_title">קצב מועדף</string>
|
||||||
<string name="settings_stats_units_imperial">אימפריאלי (מייל, רגל)</string>
|
<string name="settings_stats_units_imperial">אימפריאלי (מייל, רגל)</string>
|
||||||
<string name="settings_stats_units_metric">מטרי (ק\"מ, מ\')</string>
|
<string name="settings_stats_units_metric">מטרי (ק\"מ, מ\')</string>
|
||||||
|
|||||||
@@ -265,7 +265,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetoothのセンサー</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetoothのセンサー</string>
|
||||||
<string name="settings_sensor_connected">接続先: %1$s</string>
|
<string name="settings_sensor_connected">接続先: %1$s</string>
|
||||||
<string name="settings_stats_coordinate">緯度/経度</string>
|
<string name="settings_stats_coordinate">緯度/経度</string>
|
||||||
<string name="settings_stats_grade_elevation">傾斜/標高</string>
|
|
||||||
<string name="settings_stats_rate_title">計測方法</string>
|
<string name="settings_stats_rate_title">計測方法</string>
|
||||||
<string name="settings_stats_units_imperial">ヤード・ポンド法(マイル、フィート)</string>
|
<string name="settings_stats_units_imperial">ヤード・ポンド法(マイル、フィート)</string>
|
||||||
<string name="settings_stats_units_metric">メートル法(km、m)</string>
|
<string name="settings_stats_units_metric">メートル法(km、m)</string>
|
||||||
|
|||||||
@@ -264,7 +264,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">블루투스 센서</string>
|
<string name="settings_sensor_bluetooth_sensor">블루투스 센서</string>
|
||||||
<string name="settings_sensor_connected">%1$s에 연결됨</string>
|
<string name="settings_sensor_connected">%1$s에 연결됨</string>
|
||||||
<string name="settings_stats_coordinate">위도/경도</string>
|
<string name="settings_stats_coordinate">위도/경도</string>
|
||||||
<string name="settings_stats_grade_elevation">등급/고도</string>
|
|
||||||
<string name="settings_stats_rate_title">선호하는 속도</string>
|
<string name="settings_stats_rate_title">선호하는 속도</string>
|
||||||
<string name="settings_stats_units_imperial">야드파운드법(마일, 피트)</string>
|
<string name="settings_stats_units_imperial">야드파운드법(마일, 피트)</string>
|
||||||
<string name="settings_stats_units_metric">미터법(km,m)</string>
|
<string name="settings_stats_units_metric">미터법(km,m)</string>
|
||||||
|
|||||||
@@ -265,7 +265,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">„Bluetooth“ jutiklis</string>
|
<string name="settings_sensor_bluetooth_sensor">„Bluetooth“ jutiklis</string>
|
||||||
<string name="settings_sensor_connected">Prijungta prie %1$s</string>
|
<string name="settings_sensor_connected">Prijungta prie %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Platuma ir ilguma</string>
|
<string name="settings_stats_coordinate">Platuma ir ilguma</string>
|
||||||
<string name="settings_stats_grade_elevation">Laipsnis / Aukštis</string>
|
|
||||||
<string name="settings_stats_rate_title">Pageidaujamas greitis</string>
|
<string name="settings_stats_rate_title">Pageidaujamas greitis</string>
|
||||||
<string name="settings_stats_units_imperial">Imperinės (myl., pėdos)</string>
|
<string name="settings_stats_units_imperial">Imperinės (myl., pėdos)</string>
|
||||||
<string name="settings_stats_units_metric">Metrinės (km, m)</string>
|
<string name="settings_stats_units_metric">Metrinės (km, m)</string>
|
||||||
|
|||||||
@@ -262,7 +262,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensors</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensors</string>
|
||||||
<string name="settings_sensor_connected">Savienots ar %1$s</string>
|
<string name="settings_sensor_connected">Savienots ar %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Platums/garums</string>
|
<string name="settings_stats_coordinate">Platums/garums</string>
|
||||||
<string name="settings_stats_grade_elevation">Slīpums/pacēlums</string>
|
|
||||||
<string name="settings_stats_rate_title">Vēlamais rādītājs</string>
|
<string name="settings_stats_rate_title">Vēlamais rādītājs</string>
|
||||||
<string name="settings_stats_units_imperial">Standarta (jūdzes, pēdas)</string>
|
<string name="settings_stats_units_imperial">Standarta (jūdzes, pēdas)</string>
|
||||||
<string name="settings_stats_units_metric">Metriskās (km, m)</string>
|
<string name="settings_stats_units_metric">Metriskās (km, m)</string>
|
||||||
|
|||||||
@@ -263,7 +263,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Conectat la %1$s</string>
|
<string name="settings_sensor_connected">Conectat la %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
|
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
|
||||||
<string name="settings_stats_grade_elevation">Înclinare/Elevație</string>
|
|
||||||
<string name="settings_stats_rate_title">Rata de preferat</string>
|
<string name="settings_stats_rate_title">Rata de preferat</string>
|
||||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</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_metric">Metric (km, m)</string>
|
||||||
|
|||||||
@@ -263,7 +263,6 @@
|
|||||||
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
|
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
|
||||||
<string name="settings_sensor_connected">Disambungkan ke %1$s</string>
|
<string name="settings_sensor_connected">Disambungkan ke %1$s</string>
|
||||||
<string name="settings_stats_coordinate">Latitud/Longitud</string>
|
<string name="settings_stats_coordinate">Latitud/Longitud</string>
|
||||||
<string name="settings_stats_grade_elevation">Cerun/Ketinggian</string>
|
|
||||||
<string name="settings_stats_rate_title">Kadar pilihan</string>
|
<string name="settings_stats_rate_title">Kadar pilihan</string>
|
||||||
<string name="settings_stats_units_imperial">Imperial (bt, kaki)</string>
|
<string name="settings_stats_units_imperial">Imperial (bt, kaki)</string>
|
||||||
<string name="settings_stats_units_metric">Metrik (km, m)</string>
|
<string name="settings_stats_units_metric">Metrik (km, m)</string>
|
||||||
|
|||||||