diff --git a/README_DEVELOPER.md b/README_DEVELOPER.md index ef90b04b3..545364a24 100644 --- a/README_DEVELOPER.md +++ b/README_DEVELOPER.md @@ -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`. diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java index a79f334f9..d0ca34d98 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java @@ -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})); + } + } diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index c7ffa3f25..99910fae1 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -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; @@ -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); + } + } diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java index 198fa1f1a..5e0fc8b3e 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java @@ -53,6 +53,8 @@ public abstract class CustomContentProvider extends ContentProvider { private static final String TAG = CustomContentProvider.class.getSimpleName(); + private static final String SQL_LIST_DELIMITER = ","; + private final UriMatcher uriMatcher; private SQLiteDatabase db; @@ -61,10 +63,10 @@ public abstract class CustomContentProvider extends ContentProvider { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 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, 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, 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()); @@ -202,12 +204,11 @@ public abstract class CustomContentProvider extends ContentProvider { break; case TRACKPOINTS_BY_ID: queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); - queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url)); break; case TRACKPOINTS_BY_TRACKID: queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); - queryBuilder.appendWhere(TrackPointsColumns.TRACKID + "=" + ContentUris.parseId(url)); + queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")"); break; case TRACKS: queryBuilder.setTables(TracksColumns.TABLE_NAME); @@ -215,7 +216,7 @@ public abstract class CustomContentProvider extends ContentProvider { break; case TRACKS_BY_ID: queryBuilder.setTables(TracksColumns.TABLE_NAME); - queryBuilder.appendWhere(TracksColumns._ID + "=" + ContentUris.parseId(url)); + queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")"); break; case WAYPOINTS: queryBuilder.setTables(WaypointsColumns.TABLE_NAME); @@ -417,4 +418,4 @@ public abstract class CustomContentProvider extends ContentProvider { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } -} \ No newline at end of file +} diff --git a/src/main/java/de/dennisguse/opentracks/util/IntentDashboardUtils.java b/src/main/java/de/dennisguse/opentracks/util/IntentDashboardUtils.java index ef247cef9..b9b98a4ad 100644 --- a/src/main/java/de/dennisguse/opentracks/util/IntentDashboardUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/IntentDashboardUtils.java @@ -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 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_BY_TRACKID, 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; } + } diff --git a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java index 0f7f9e23e..d1d1eb673 100644 --- a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java @@ -140,7 +140,7 @@ public class IntentUtils { return; } - if (IntentDashboardUtils.startDashboard(context, trackIds[0])) { + if (IntentDashboardUtils.startDashboard(context, trackIds)) { return; }