Compare commits

...

14 Commits

Author SHA1 Message Date
Dennis Guse 928781391d Release v3.4.0 2020-02-05 23:41:55 +01:00
Dennis Guse 05cd672464 Updated help text. 2020-02-05 23:41:50 +01:00
Dennis Guse d3109bbc37 Cleanup. 2020-02-05 23:08:17 +01:00
Dennis Guse eabdb7c721 Unify order of show on map and share menu entries. 2020-02-04 19:52:05 +01:00
Peter Storch ad990e8cd7 Forward multiple tracks to dashboard 2020-02-04 00:07:17 +01:00
Dennis Guse db3eef0104 Test: date comparison tends to be flaky. 2020-02-03 23:27:19 +01:00
Dennis Guse c4f1ba7962 Upgrade material design dependency.
Requires including androidx.documentfile directly.
2020-02-03 23:27:19 +01:00
Dennis Guse 7451fbd413 CI: enable testing via Github Actions. 2020-02-03 23:27:19 +01:00
Dennis Guse b6c55de55a CI: enable testing TravisCI on again. 2020-02-03 23:27:19 +01:00
Dennis Guse dd3ee6d99e Bugfix: match internal ContentURI correctly.
TrackPoint by TrackId was not working (and not used).
2020-02-03 21:32:06 +01:00
Dennis Guse bbb6a7c22c Bugfix: keep TrackRecordingService in foreground if paused. 2020-02-03 20:45:14 +01:00
Dennis Guse dc1b50a7bf Cleanup: move PAUSE_LATITUDE and RESUME_LATITUDE to TrackPointsColumn. 2020-02-03 20:45:14 +01:00
Dennis Guse 21bfb88e0b Bugfix: initialize fragments from bundle rather via constructor.
Introduced in 8b11e1d611
This fixes #127.
2020-02-03 19:51:04 +01:00
Dennis Guse aaab8adce9 ExportAsyncTask: always get a wakelock. 2020-02-03 19:51:04 +01:00
34 changed files with 369 additions and 308 deletions
+21
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
@@ -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);
}
}
@@ -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);
}
/**
+2 -2
View File
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
+2 -1
View File
@@ -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" />
+30 -70
View File
@@ -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>
+5 -5
View File
@@ -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"
+58 -24
View File
@@ -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>