forked from upstream-mirrors/OpenTracks
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
||||
+10
-10
@@ -22,18 +22,18 @@ android:
|
||||
- android-29
|
||||
|
||||
# Emulator Management: Create, Start and Wait
|
||||
#install:
|
||||
# - echo y | sdkmanager "platform-tools"
|
||||
# - echo y | sdkmanager --channel=4 "emulator"
|
||||
# - echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
# - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
# - emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
# - android-wait-for-emulator
|
||||
# - adb shell input keyevent 82 &
|
||||
install:
|
||||
- echo y | sdkmanager "platform-tools"
|
||||
- echo y | sdkmanager --channel=4 "emulator"
|
||||
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
- android-wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
|
||||
script:
|
||||
- ./gradlew clean build
|
||||
# - ./gradlew connectedCheck
|
||||
- ./gradlew build
|
||||
- ./gradlew connectedCheck
|
||||
|
||||
sbefore_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
|
||||
+3
-3
@@ -14,15 +14,15 @@ For the time being, the OpenTracks Data API can be used to implement mainly cust
|
||||
|
||||
## Implementation
|
||||
OpenTracks by default does not expose any data to 3rd-party applications.
|
||||
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary) as well as the `TrackPoint`s.
|
||||
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.
|
||||
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
|
||||
|
||||
Limitations:
|
||||
* only tracks selected by the user is exposed,
|
||||
* only tracks selected by the user are exposed,
|
||||
* `Waypoints` are not exposed,
|
||||
* access is only granted temporarily and automatically revoked (see `FLAG_GRANT_READ_URI_PERMISSION`),
|
||||
* no write access is impossible, and
|
||||
* no write access is possible, and
|
||||
* neither track recordings can be started, paused, or stopped.
|
||||
|
||||
All required information can be found in `IntentDashboardUtils.java`.
|
||||
|
||||
+2
-1
@@ -98,6 +98,7 @@ android {
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat: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.espresso:espresso-core:3.2.0'
|
||||
@@ -109,5 +110,5 @@ dependencies {
|
||||
|
||||
testImplementation 'androidx.test:core:1.2.0'
|
||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
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)
|
||||
+11
-6
@@ -16,6 +16,7 @@
|
||||
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
@@ -29,7 +30,6 @@ import org.junit.Test;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider.DatabaseHelper;
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider}.
|
||||
@@ -47,13 +47,13 @@ public class CustomContentProviderTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
db = (new DatabaseHelper(context, DATABASE_NAME)).getWritableDatabase();
|
||||
db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase();
|
||||
customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider.DatabaseHelper#onCreate(SQLiteDatabase)}.
|
||||
* Tests {@link CustomSQLiteOpenHelper#onCreate(SQLiteDatabase)}.
|
||||
*/
|
||||
@Test
|
||||
public void testDatabaseHelper_OnCreate() {
|
||||
@@ -76,8 +76,13 @@ public class CustomContentProviderTest {
|
||||
@Test
|
||||
public void testGetType() {
|
||||
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
|
||||
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI));
|
||||
Assert.assertEquals(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_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +147,7 @@ public class CustomContentProviderTest {
|
||||
createTable(TrackPointsColumns.TABLE_NAME);
|
||||
createTable(WaypointsColumns.TABLE_NAME);
|
||||
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
databaseHelper.onUpgrade(db, oldVersion, CustomContentProvider.DATABASE_VERSION);
|
||||
CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(context);
|
||||
databaseHelper.onUpgrade(db, oldVersion, CustomSQLiteOpenHelper.DATABASE_VERSION);
|
||||
}
|
||||
}
|
||||
+12
-4
@@ -230,7 +230,7 @@ public class CustomContentProviderUtilsTest {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(1, tracksCursor.getCount());
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(10, tracksPointsCursor.getCount());
|
||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(1, waypointCursor.getCount());
|
||||
@@ -239,7 +239,7 @@ public class CustomContentProviderUtilsTest {
|
||||
// Check whether all have been deleted.
|
||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(0, tracksCursor.getCount());
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(0, tracksPointsCursor.getCount());
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(0, waypointCursor.getCount());
|
||||
@@ -265,7 +265,7 @@ public class CustomContentProviderUtilsTest {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(3, tracksCursor.getCount());
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, null, TrackPointsColumns._ID);
|
||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(1, waypointCursor.getCount());
|
||||
@@ -274,7 +274,7 @@ public class CustomContentProviderUtilsTest {
|
||||
// Check whether all data of a track has been deleted.
|
||||
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||
Assert.assertEquals(2, tracksCursor.getCount());
|
||||
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());
|
||||
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||
Assert.assertEquals(0, waypointCursor.getCount());
|
||||
@@ -766,4 +766,12 @@ public class CustomContentProviderUtilsTest {
|
||||
contentProviderUtils.insertTrack(track);
|
||||
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}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,8 +61,7 @@ public class ChartFragmentTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
boolean chartByDistance = false;
|
||||
chartFragment = new ChartFragment(chartByDistance) {
|
||||
};
|
||||
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
|
||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,10 @@ public class StringUtilsTest {
|
||||
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
calendar.set(year, month - 1, day, hour, minute, second);
|
||||
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"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="3272"
|
||||
android:versionName="v3.3.2"> <!-- For FDroid. -->
|
||||
android:versionCode="3287"
|
||||
android:versionName="v3.4.0"> <!-- For FDroid. -->
|
||||
<!-- Permission to initialize services -->
|
||||
<application
|
||||
android:name=".Startup"
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
@@ -46,8 +45,7 @@ import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChartTimeFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||
@@ -206,17 +204,15 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return new StatsFragment();
|
||||
case 1:
|
||||
return new ChartTimeFragment();
|
||||
return ChartFragment.newInstance(false);
|
||||
case 2:
|
||||
return new ChartDistanceFragment();
|
||||
return ChartFragment.newInstance(true);
|
||||
default: //0
|
||||
return new StatsFragment();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
|
||||
@@ -111,8 +111,7 @@ class DataSourceManager {
|
||||
dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
|
||||
break;
|
||||
case SAMPLED_IN_TRACK_POINTS_TABLE:
|
||||
dataSource.registerContentObserver(
|
||||
TrackPointsColumns.CONTENT_URI, trackPointsTableObserver);
|
||||
dataSource.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, trackPointsTableObserver);
|
||||
break;
|
||||
case SAMPLED_OUT_TRACK_POINTS_TABLE:
|
||||
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
|
||||
|
||||
@@ -29,15 +29,20 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
public interface TrackPointsColumns extends BaseColumns {
|
||||
|
||||
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_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
|
||||
String DEFAULT_SORT_ORDER = "_id";
|
||||
|
||||
// Columns
|
||||
String TRACKID = "trackid";
|
||||
|
||||
String LONGITUDE = "longitude";
|
||||
String LATITUDE = "latitude";
|
||||
double PAUSE_LATITUDE = 100.0;
|
||||
double RESUME_LATITUDE = 200.0;
|
||||
|
||||
String TIME = "time";
|
||||
String ALTITUDE = "elevation";
|
||||
String ACCURACY = "accuracy";
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
@@ -68,6 +69,7 @@ public class ContentProviderUtils {
|
||||
* Maximum number of waypoints that will be loaded at one time.
|
||||
*/
|
||||
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
||||
public static final String ID_SEPARATOR = ",";
|
||||
|
||||
private final IContentResolver contentResolver;
|
||||
private int defaultCursorBatchSize = 2000;
|
||||
@@ -181,13 +183,13 @@ public class ContentProviderUtils {
|
||||
* Deletes all tracks (including waypoints and track points).
|
||||
*/
|
||||
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);
|
||||
// Delete tracks last since it triggers a database vaccum call
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||
|
||||
File dir = FileUtils.getPhotoDir(context);
|
||||
deleteDirectoryRecurse(context, dir);
|
||||
deleteDirectoryRecurse(dir);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,11 +214,11 @@ public class ContentProviderUtils {
|
||||
private void deleteTrackPointsAndWaypoints(Context context, long trackId) {
|
||||
String where = TrackPointsColumns.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 + "=?",
|
||||
new String[]{Long.toString(trackId)});
|
||||
deleteDirectoryRecurse(context, FileUtils.getPhotoDir(context, trackId));
|
||||
deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,10 +226,10 @@ public class ContentProviderUtils {
|
||||
*
|
||||
* @param dir the directory
|
||||
*/
|
||||
private void deleteDirectoryRecurse(Context context, File dir) {
|
||||
if (dir.exists() && dir.isDirectory()) {
|
||||
private void deleteDirectoryRecurse(File dir) {
|
||||
if (dir != null && dir.exists() && dir.isDirectory()) {
|
||||
for (File child : dir.listFiles()) {
|
||||
deleteDirectoryRecurse(context, child);
|
||||
deleteDirectoryRecurse(child);
|
||||
}
|
||||
dir.delete();
|
||||
}
|
||||
@@ -659,7 +661,7 @@ public class ContentProviderUtils {
|
||||
for (int i = 0; i < length; i++) {
|
||||
values[i] = createContentValues(locations[i], trackId);
|
||||
}
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -846,7 +848,7 @@ public class ContentProviderUtils {
|
||||
* @return the content provider URI of the inserted track point
|
||||
*/
|
||||
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 +928,7 @@ public class ContentProviderUtils {
|
||||
* @param sortOrder the sort order
|
||||
*/
|
||||
private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return contentResolver.query(TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
||||
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
int getDefaultCursorBatchSize() {
|
||||
@@ -942,4 +944,26 @@ public class ContentProviderUtils {
|
||||
void setDefaultCursorBatchSize(int 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
-52
@@ -25,7 +25,6 @@ import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
@@ -45,27 +44,25 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
*/
|
||||
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 SQL_LIST_DELIMITER = ",";
|
||||
|
||||
private final UriMatcher uriMatcher;
|
||||
|
||||
private SQLiteDatabase db;
|
||||
|
||||
public CustomContentProvider() {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_BY_TRACKID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.TRACKPOINTS_BY_TRACKID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_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
|
||||
@@ -81,7 +78,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean onCreate(Context context) {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(context);
|
||||
try {
|
||||
db = databaseHelper.getWritableDatabase();
|
||||
} catch (SQLiteException e) {
|
||||
@@ -200,11 +197,11 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
break;
|
||||
case TRACKPOINTS_BY_ID:
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + url.getPathSegments().get(1));
|
||||
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||
break;
|
||||
case TRACKPOINTS_BY_TRACKID:
|
||||
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;
|
||||
case TRACKS:
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
@@ -212,7 +209,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
break;
|
||||
case TRACKS_BY_ID:
|
||||
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;
|
||||
case WAYPOINTS:
|
||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||
@@ -220,7 +217,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
break;
|
||||
case WAYPOINTS_BY_ID:
|
||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + url.getPathSegments().get(1));
|
||||
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown url " + url);
|
||||
@@ -242,7 +239,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
break;
|
||||
case TRACKPOINTS_BY_ID:
|
||||
table = TrackPointsColumns.TABLE_NAME;
|
||||
whereClause = TrackPointsColumns._ID + "=" + url.getPathSegments().get(1);
|
||||
whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url);
|
||||
if (!TextUtils.isEmpty(where)) {
|
||||
whereClause += " AND (" + where + ")";
|
||||
}
|
||||
@@ -253,7 +250,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
break;
|
||||
case TRACKS_BY_ID:
|
||||
table = TracksColumns.TABLE_NAME;
|
||||
whereClause = TracksColumns._ID + "=" + url.getPathSegments().get(1);
|
||||
whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url);
|
||||
if (!TextUtils.isEmpty(where)) {
|
||||
whereClause += " AND (" + where + ")";
|
||||
}
|
||||
@@ -264,7 +261,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
break;
|
||||
case WAYPOINTS_BY_ID:
|
||||
table = WaypointsColumns.TABLE_NAME;
|
||||
whereClause = WaypointsColumns._ID + "=" + url.getPathSegments().get(1);
|
||||
whereClause = WaypointsColumns._ID + "=" + ContentUris.parseId(url);
|
||||
if (!TextUtils.isEmpty(where)) {
|
||||
whereClause += " AND (" + where + ")";
|
||||
}
|
||||
@@ -289,8 +286,15 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
*
|
||||
* @param url the url
|
||||
*/
|
||||
@NonNull
|
||||
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);
|
||||
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);
|
||||
}
|
||||
@@ -380,31 +384,4 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
WAYPOINTS,
|
||||
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,44 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
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 = 23;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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) {
|
||||
}
|
||||
}
|
||||
@@ -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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -56,9 +55,18 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
* @author Sandor Dornbush
|
||||
* @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<>();
|
||||
|
||||
@@ -139,14 +147,13 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
||||
}
|
||||
};
|
||||
|
||||
public ChartFragment(boolean chartByDistance) {
|
||||
this.chartByDistance = chartByDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
chartByDistance = getArguments().getBoolean(KEY_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
|
||||
|
||||
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.
|
||||
@@ -190,20 +197,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
||||
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
|
||||
public void onTrackUpdated(Track track) {
|
||||
if (isResumed()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
private static final String CONFIRM_DELETE_DIALOG_TAG = "confirmDeleteDialog";
|
||||
private ConfirmDeleteCaller caller;
|
||||
private final long[] trackIds;
|
||||
private static final String KEY_TRACK_IDS = "trackIds";
|
||||
|
||||
public ConfirmDeleteDialogFragment(long[] trackIds) {
|
||||
this.trackIds = trackIds;
|
||||
}
|
||||
private ConfirmDeleteCaller caller;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
@@ -65,6 +67,8 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
|
||||
|
||||
int titleId;
|
||||
int messageId;
|
||||
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 {
|
||||
|
||||
private static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog";
|
||||
private final long[] markerIds;
|
||||
private static final String KEY_MARKER_IDS = "markerIds";
|
||||
|
||||
private DeleteMarkerCaller caller;
|
||||
|
||||
public DeleteMarkerDialogFragment(long[] markerIds) {
|
||||
this.markerIds = 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
|
||||
@@ -63,6 +64,8 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS);
|
||||
|
||||
final FragmentActivity fragmentActivity = getActivity();
|
||||
int titleId;
|
||||
int messageId;
|
||||
|
||||
@@ -40,18 +40,19 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
public class FileTypeDialogFragment extends DialogFragment {
|
||||
|
||||
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 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) {
|
||||
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
|
||||
@@ -67,6 +68,9 @@ public class FileTypeDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
@NonNull
|
||||
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};
|
||||
String[] choices = new String[trackFileFormats.length];
|
||||
for (int i = 0; i < choices.length; i++) {
|
||||
|
||||
@@ -57,7 +57,9 @@ public class MarkerDetailFragment extends Fragment {
|
||||
|
||||
private static final String TAG = MarkerDetailFragment.class.getSimpleName();
|
||||
private static final String KEY_MARKER_ID = "markerId";
|
||||
|
||||
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND_MS;
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private Handler handler;
|
||||
private ImageView photoView;
|
||||
|
||||
@@ -232,7 +232,9 @@ public enum TrackFileFormat {
|
||||
/**
|
||||
* Returns whether the format supports photos.
|
||||
*/
|
||||
public boolean includesPhotos() { return false; };
|
||||
public boolean includesPhotos() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
/**
|
||||
@@ -51,10 +50,10 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
private ExportActivity exportActivity;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
private boolean completed;
|
||||
private boolean completed = false;
|
||||
|
||||
private int processedTrackCount;
|
||||
private int totalTrackCount;
|
||||
private int processedTrackCount = 0;
|
||||
private int totalTrackCount = 0;
|
||||
|
||||
/**
|
||||
* Creates an AsyncTask.
|
||||
@@ -69,10 +68,6 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
this.directory = directory;
|
||||
context = exportActivity.getApplicationContext();
|
||||
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) {
|
||||
try {
|
||||
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();
|
||||
} finally {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -39,11 +39,11 @@ import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
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.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
@@ -274,7 +274,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 (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;
|
||||
}
|
||||
@@ -344,8 +344,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
|
||||
// If not the first segment, add a resume separator before adding the first location.
|
||||
insertLocation(
|
||||
createLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
|
||||
insertLocation(createLocation(TrackPointsColumns.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
|
||||
}
|
||||
trackData.lastLocationInCurrentSegment = location;
|
||||
return location;
|
||||
|
||||
@@ -43,6 +43,7 @@ import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
@@ -70,12 +71,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
*/
|
||||
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.
|
||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||
|
||||
@@ -386,7 +381,7 @@ public class TrackRecordingService extends Service {
|
||||
if (track != null) {
|
||||
Location resume = new Location(LocationManager.GPS_PROVIDER);
|
||||
resume.setLongitude(0);
|
||||
resume.setLatitude(RESUME_LATITUDE);
|
||||
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
|
||||
resume.setTime(System.currentTimeMillis());
|
||||
insertLocation(track, resume, null);
|
||||
}
|
||||
@@ -473,7 +468,7 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
||||
pause.setLongitude(0);
|
||||
pause.setLatitude(PAUSE_LATITUDE);
|
||||
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||
pause.setTime(System.currentTimeMillis());
|
||||
insertLocation(track, pause, null);
|
||||
}
|
||||
@@ -610,7 +605,7 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
||||
pause.setLongitude(0);
|
||||
pause.setLatitude(PAUSE_LATITUDE);
|
||||
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||
pause.setTime(lastLocation.getTime());
|
||||
insertLocation(track, pause, null);
|
||||
|
||||
@@ -713,17 +708,7 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
|
||||
private void showNotification(boolean isGpsStarted) {
|
||||
// Dijkstra If
|
||||
if (isRecording() && isPaused()) {
|
||||
stopForeground(true);
|
||||
notificationManager.cancelNotification();
|
||||
|
||||
}
|
||||
if (!isRecording() && !isGpsStarted) {
|
||||
stopForeground(true);
|
||||
notificationManager.cancelNotification();
|
||||
}
|
||||
if (isRecording() && !isPaused()) {
|
||||
if (isRecording()) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||
@@ -745,5 +730,9 @@ public class TrackRecordingService extends Service {
|
||||
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.getNotification());
|
||||
}
|
||||
if (!isRecording() && !isGpsStarted) {
|
||||
stopForeground(true);
|
||||
notificationManager.cancelNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
|
||||
import static de.dennisguse.opentracks.services.TrackRecordingService.MAX_NO_MOVEMENT_SPEED;
|
||||
import static de.dennisguse.opentracks.services.TrackRecordingService.PAUSE_LATITUDE;
|
||||
|
||||
/**
|
||||
* Updater for {@link TripStatistics}.
|
||||
@@ -123,7 +123,7 @@ public class TripStatisticsUpdater {
|
||||
updateTime(location.getTime());
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
// Either pause or resume marker
|
||||
if (location.getLatitude() == PAUSE_LATITUDE) {
|
||||
if (location.getLatitude() == TrackPointsColumns.PAUSE_LATITUDE) {
|
||||
if (lastLocation != null && lastMovingLocation != null && lastLocation != lastMovingLocation) {
|
||||
currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@@ -10,6 +9,7 @@ import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
/**
|
||||
* Create an {@link Intent} to request showing a Dashboard.
|
||||
@@ -24,11 +24,12 @@ public class IntentDashboardUtils {
|
||||
private IntentDashboardUtils() {
|
||||
}
|
||||
|
||||
public static boolean startDashboard(Context context, long trackId) {
|
||||
public static boolean startDashboard(Context context, long[] trackIds) {
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
||||
|
||||
uris.add(0, ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId));
|
||||
uris.add(1, ContentUris.withAppendedId(TrackPointsColumns.CONTENT_URI, trackId));
|
||||
uris.add(0, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
||||
uris.add(1, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
|
||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||
@@ -47,4 +48,5 @@ public class IntentDashboardUtils {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public class IntentUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IntentDashboardUtils.startDashboard(context, trackIds[0])) {
|
||||
if (IntentDashboardUtils.startDashboard(context, trackIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="239dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_logo_color_24dp" />
|
||||
|
||||
<TextView
|
||||
@@ -80,6 +80,7 @@
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:gravity="center"
|
||||
android:text="@string/third_party_libraries" />
|
||||
|
||||
|
||||
@@ -44,20 +44,6 @@
|
||||
android:linksClickable="true"
|
||||
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
|
||||
style="@style/TextMedium"
|
||||
android:layout_width="match_parent"
|
||||
@@ -72,34 +58,6 @@
|
||||
android:layout_height="wrap_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
|
||||
style="@style/TextMedium"
|
||||
android:layout_width="match_parent"
|
||||
@@ -128,20 +86,6 @@
|
||||
android:layout_height="wrap_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
|
||||
style="@style/TextMedium"
|
||||
android:layout_width="match_parent"
|
||||
@@ -155,20 +99,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
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
|
||||
style="@style/TextMedium"
|
||||
android:layout_width="match_parent"
|
||||
@@ -182,6 +112,36 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_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>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
@@ -15,16 +15,16 @@ limitations under the License.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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
|
||||
android:id="@+id/list_context_menu_show_on_map"
|
||||
android:icon="@drawable/ic_map_24dp"
|
||||
android:title="@string/menu_show_on_map"
|
||||
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
|
||||
android:id="@+id/list_context_menu_edit"
|
||||
android:icon="@drawable/ic_edit_24dp"
|
||||
|
||||
@@ -24,43 +24,77 @@ limitations under the License.
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks has the following features:</string>
|
||||
<string name="help_feature_content">
|
||||
* Create markers (incl. pictures)\n
|
||||
* Heart rate sensor (Bluetooth LE)\n
|
||||
* Voice announcements by time or distance\n
|
||||
* While recording, show on lock screen\n
|
||||
* Share tracks
|
||||
* create GPS tracks\n
|
||||
* use a heart rate sensor (Bluetooth LE only)\n
|
||||
* voice announcements (by time or distance)\n
|
||||
* create markers (incl. pictures)\n
|
||||
* works completely offline\n
|
||||
* show tracks on a map (see below)
|
||||
</string>
|
||||
<string name="help_data_statistics_customized_title">Can the statistics view be customized?</string>
|
||||
<string name="help_data_statistics_customized_content">Yes, it can be customized in the Settings.</string>
|
||||
<string name="help_recording_title">How does OpenTracks record data?</string>
|
||||
<string name="help_recording_content">OpenTracks uses the GPS sensor of the device to record the location continuously.</string>
|
||||
<string name="help_gps_imprecise_title">Statistics (e.g., speed) reported by OpenTracks are imprecise?</string>
|
||||
<string name="help_gps_imprecise_content">OpenTracks directly uses the data provided by the GPS of the mobile device. If the GPS device reports inaccurate locations, OpenTracks cannot do anything about it.</string>
|
||||
<string name="help_heart_rate_sensors_title">Which heart rate sensors does OpenTracks support?</string>
|
||||
<string name="help_heart_rate_sensors_content">OpenTracks should work with any Bluetooth LE heart sensor that support streaming the data in real-time. Smartwatches that store the heart rate data are not supported.</string>
|
||||
|
||||
<string name="help_track_map_title">How to show a track on a map?</string>
|
||||
<string name="help_track_map_content">
|
||||
This requires a 3rd party application to be installed that can consume KMZ-encoded geo data.
|
||||
This works well with <a href="http://osmand.net/">OsmAnd~</a> and <a href="https://maps.me/">Maps.ME</a>.
|
||||
In fact, an App needs to support <i>ACTION_VIEW</i> with MIME <i>application/vnd.google-earth.kmz</i>.
|
||||
At the moment, the recorded locations as well as the timestamps are send to an App while pictures and sensor data are not shared.</string>
|
||||
Use the small map icon.\n
|
||||
a)
|
||||
(live updates) Install <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>.
|
||||
It provides online or offline maps via <a href="https://www.openstreetmap.org">OpenStreetmap</a>.\n
|
||||
Actually, OSMDashboard is a separate app that only can access (temporarily) access the data of track(s) that are going to be shown.
|
||||
|
||||
\n
|
||||
b)
|
||||
Via a "normal" map application (e.g., <a href="http://osmand.net/">OsmAnd</a> and <a href="https://maps.me/">MAPS.ME</a>).
|
||||
Those app need to support the KMZ file format (only locations and timestamps).
|
||||
In fact, an app needs to support <i>ACTION_VIEW</i> with MIME <i>application/vnd.google-earth.kmz</i>.
|
||||
</string>
|
||||
|
||||
<string name="help_storage_pictures_title">How does OpenTracks store pictures that were taken as markers?</string>
|
||||
<string name="help_storage_pictures_content">These are stored directly on the file system.</string>
|
||||
<string name="help_storage_pictures_content">These are stored in the app internal storage (i.e., only accessible within OpenTracks).</string>
|
||||
|
||||
<string name="help_track_export_title">How can I export/share recorded tracks?</string>
|
||||
<string name="help_track_export_content">
|
||||
Tracks can be exported from the Settings as KMZ, KML, or GPX.
|
||||
KMZ contains all data incl. images, while KML contains only the sensor data, and GPX only the location.
|
||||
Tracks shared via the <i>share icon</i> are shared as KMZ and any App can be used to send the data.
|
||||
KMZ/KML all data; GPX only locations.
|
||||
Tracks shared via the <i>share icon</i> are shared as KMZ.
|
||||
</string>
|
||||
<string name="help_data_upload_title">Can OpenTracks upload the data automatically?</string>
|
||||
|
||||
<string name="help_data_google_play_services_title">Does OpenTracks require/use Google Play Service?</string>
|
||||
<string name="help_data_online_title">Does OpenTracks use online services?</string>
|
||||
|
||||
<string name="help_recording_title">How does OpenTracks record data?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks uses the GPS sensor of the device to record the location continuously.
|
||||
If the GPS device reports inaccurate data (e.g., location, speed, elevation), OpenTracks cannot do anything about it.
|
||||
</string>
|
||||
|
||||
<string name="help_data_storage_title">How does OpenTracks store sensor data?</string>
|
||||
<string name="help_data_storage_content">
|
||||
OpenTracks stores sensor data per recorded location.
|
||||
In terms of a heart rate sensor this means that only <i>one</i> heart rate measurement is stored per location.
|
||||
Sensor data is stored per recorded location.
|
||||
In terms of a heart rate sensor, this means that only <i>one</i> heart rate measurement is stored per location.
|
||||
If you are standing still or are indoors, no sensor data will be recorded (but it will be shown).
|
||||
</string>
|
||||
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks is not recording locations?</string>
|
||||
<string name="help_gps_troubleshooting_content">
|
||||
What you can do:\n
|
||||
a) check if GPS is enabled,\n
|
||||
b) improve GPS reception (e.g., go outdoors),\n
|
||||
c) check if with <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, and\n
|
||||
d) disable power saving.\n
|
||||
\n
|
||||
If everything does not help please report a bug.
|
||||
</string>
|
||||
|
||||
<string name="help_support_title">How can I support OpenTracks?</string>
|
||||
<string name="help_support_content">
|
||||
You can:\n
|
||||
a) spread the word about OpenTracks,\n
|
||||
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">propose features</a>,\n
|
||||
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">report bugs</a>,\n
|
||||
d) help in software development,\n
|
||||
e) buy in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n
|
||||
f) donate, and\n
|
||||
g) do whatever you think helps OpenTracks.
|
||||
</string>
|
||||
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">airplane</string>
|
||||
<string name="activity_type_atv">ATV</string>
|
||||
|
||||
Reference in New Issue
Block a user