Compare commits

...

8 Commits

Author SHA1 Message Date
Dennis Guse bbb2f874ee Release v3.3.1 2020-01-03 18:11:57 +01:00
Dennis Guse 9af3759b07 Bugfix: app name (release) not shown.
Introduced in 6cc7271a41.
2020-01-03 17:54:15 +01:00
Dennis Guse 1f6fe6bf0c Android 10: on uninstall ask the user if the app data should be kept. 2020-01-03 17:50:07 +01:00
Dennis Guse 7b291e85d7 Renamed DeleteActivity to TrackDeleteActivity. 2020-01-03 17:04:51 +01:00
Dennis Guse 0f72066bc0 Add IntentChooser for dashboards. 2020-01-02 19:56:24 +01:00
Dennis Guse 70e0e7b216 Add README for data API. 2020-01-02 19:54:19 +01:00
Peter Storch 7e50bb1b6d Merge pull request #114 from pstorch/79_osm_plugin
integrate dashboard plugin
2020-01-02 17:56:07 +01:00
Peter Storch a3eb107068 integrate dashboard plugin mechanism
To be able to integrate the OSMDashboard https://github.com/OpenTracksApp/OSMDashboard,
which addresses #79. Also other Dashboards will be possible.
2020-01-02 17:45:01 +01:00
16 changed files with 139 additions and 25 deletions
+5 -2
View File
@@ -30,10 +30,10 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
## Features:
* __Tracking:__ track your sport and outdoor activities
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
* __Heart rate:__ supports bluetooth LE sensors
* __Heart rate:__ supports Bluetooth LE sensors
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
* __No Internet access:__ Internet is not used
* __Voice annoucements__
* __Voice announcements__
* __No advertising__
* __Privacy:__
* _does not contain any in-app analytics_
@@ -47,6 +47,9 @@ _Please note:_
_Tracks_ does not contain a _map_ (as this would required Internet access).
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
## Custom Dashboards
As of v3.3.1 OpenTracks enables to use custom dashboards.
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
## Screenshots
<div>
+30
View File
@@ -0,0 +1,30 @@
# OpenTracks Data API
## Reason
OpenTracks by itself only provides basic features for data visualization and also lacks functionality for analytics.
By itself OpenTracks provides a basic dashboard that can show summary of a track (while recording as well as afterwards).
Although this might be (mostly) sufficient, it has some limitations.
This include limited configurability (e.g., ordering of shown elements) and the lack of Internet access.
In fact, the later is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
_NOTE:_ for showing a track on a map a workaround was implemented in OpenTracks.
Data could be exported as KMZ and presented in 3rd-party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)).
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
## Implementation
OpenTracks by default does not expose any data to 3rd-party applications.
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary) as well as the `TrackPoint`s.
For each, one URI is provided.
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
Limitations:
* only tracks selected by the user is exposed,
* `Waypoints` are not exposed,
* access is only granted temporarily and automatically revoked (see `FLAG_GRANT_READ_URI_PERMISSION`),
* no write access is impossible, and
* neither track recordings can be started, paused, or stopped.
All required information can be found in `IntentDashboardUtils.java`.
A _reference implementation_ is the [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard).
+1 -1
View File
@@ -56,7 +56,7 @@ android {
applicationId "de.dennisguse.opentracks"
manifestPlaceholders = [
applicationLogo: "@drawable/ic_logo_color_24dp",
applicationName: "@strings/app_name"
applicationName: "@string/app_name"
]
//Useful for debugging.
@@ -0,0 +1,12 @@
v3.3.1: OpenTracks
Changes:
- Android 10: on uninstall allow to keep app data
- Introduced Dashboard API: map icon can be used to open current track in external app
(requires installation of another app like OSMDashboard)
Bugfix:
- App label was broken (introduced in v3.3.0)
Developer:
- Documentation for Dashboard API can be found in README_DEVELOPER.md
+4 -3
View File
@@ -16,12 +16,13 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks"
android:installLocation="auto"
android:versionCode="3243"
android:versionName="v3.3.0"> <!-- For FDroid. -->
android:versionCode="3251"
android:versionName="v3.3.1"> <!-- For FDroid. -->
<!-- Permission to initialize services -->
<application
android:name=".Startup"
android:allowBackup="false"
android:hasFragileUserData="true"
android:hardwareAccelerated="true"
android:icon="${applicationLogo}"
android:label="${applicationName}"
@@ -30,7 +31,7 @@ limitations under the License.
<!-- Activities -->
<activity android:name=".AboutActivity" />
<activity android:name=".DeleteActivity" />
<activity android:name=".TrackDeleteActivity" />
<activity
android:name=".HelpActivity"
@@ -76,8 +76,8 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
if (stopRecording) {
getTrackRecordingServiceConnection().stopRecording(this, false);
}
Intent intent = IntentUtils.newIntent(this, DeleteActivity.class);
intent.putExtra(DeleteActivity.EXTRA_TRACK_IDS, trackIds);
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
startActivityForResult(intent, DELETE_REQUEST_CODE);
}
@@ -88,7 +88,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection();
/**
* Called after {@link DeleteActivity} returns its result.
* Called after {@link TrackDeleteActivity} returns its result.
*/
abstract protected void onDeleted();
}
@@ -30,7 +30,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private final long[] trackIds;
private final Context context;
private DeleteActivity deleteActivity;
private TrackDeleteActivity deleteActivity;
// true if the AsyncTask has completed
private boolean completed;
@@ -41,7 +41,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* @param trackIds the track ids to delete. To delete all, set to size 1 with
* trackIds[0] == -1L
*/
public DeleteAsyncTask(DeleteActivity deleteActivity, long[] trackIds) {
public DeleteAsyncTask(TrackDeleteActivity deleteActivity, long[] trackIds) {
this.deleteActivity = deleteActivity;
this.trackIds = trackIds;
context = deleteActivity.getApplicationContext();
@@ -53,7 +53,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
*
* @param deleteActivity the current activity, can be null
*/
public void setActivity(DeleteActivity deleteActivity) {
public void setActivity(TrackDeleteActivity deleteActivity) {
this.deleteActivity = deleteActivity;
if (completed && deleteActivity != null) {
deleteActivity.onAsyncTaskCompleted();
@@ -23,14 +23,13 @@ import android.content.Intent;
import android.os.Bundle;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.R;
/**
* An activity for delete tracks.
*
* @author Jimmy Shih
*/
public class DeleteActivity extends Activity {
public class TrackDeleteActivity extends Activity {
public static final String EXTRA_TRACK_IDS = "track_ids";
@@ -1086,4 +1086,4 @@ public class ContentProviderUtils {
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
}
}
}
}
@@ -61,6 +61,7 @@ public abstract class CustomContentProvider extends ContentProvider {
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());
@@ -134,6 +135,8 @@ public abstract class CustomContentProvider extends ContentProvider {
return TrackPointsColumns.CONTENT_TYPE;
case TRACKPOINTS_BY_ID:
return TrackPointsColumns.CONTENT_ITEMTYPE;
case TRACKPOINTS_BY_TRACKID:
return TrackPointsColumns.CONTENT_ITEMTYPE;
case TRACKS:
return TracksColumns.CONTENT_TYPE;
case TRACKS_BY_ID:
@@ -198,7 +201,11 @@ public abstract class CustomContentProvider extends ContentProvider {
break;
case TRACKPOINTS_BY_ID:
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + url.getPathSegments().get(1));
break;
case TRACKPOINTS_BY_TRACKID:
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + "=" + url.getPathSegments().get(1));
break;
case TRACKS:
queryBuilder.setTables(TracksColumns.TABLE_NAME);
@@ -206,7 +213,7 @@ public abstract class CustomContentProvider extends ContentProvider {
break;
case TRACKS_BY_ID:
queryBuilder.setTables(TracksColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
queryBuilder.appendWhere(TracksColumns._ID + "=" + url.getPathSegments().get(1));
break;
case WAYPOINTS:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
@@ -214,7 +221,7 @@ public abstract class CustomContentProvider extends ContentProvider {
break;
case WAYPOINTS_BY_ID:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + url.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown url " + url);
@@ -226,6 +233,7 @@ public abstract class CustomContentProvider extends ContentProvider {
@Override
public int update(@NonNull Uri url, ContentValues values, String where, String[] selectionArgs) {
// TODO Use SQLiteQueryBuilder
String table;
String whereClause;
switch (getUrlType(url)) {
@@ -367,7 +375,9 @@ public abstract class CustomContentProvider extends ContentProvider {
enum UrlType {
TRACKPOINTS,
TRACKPOINTS_BY_ID,
TRACKS, TRACKS_BY_ID,
TRACKPOINTS_BY_TRACKID,
TRACKS,
TRACKS_BY_ID,
WAYPOINTS,
WAYPOINTS_BY_ID
}
@@ -81,7 +81,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
}
builder.deleteCharAt(builder.lastIndexOf(TRACKID_DELIMITER));
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TracksColumns.TABLE_NAME + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
String mime = getTypeMime(uri);
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
@@ -29,7 +29,7 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
public interface TrackPointsColumns extends BaseColumns {
String TABLE_NAME = "trackpoints";
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/trackpoints");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
String DEFAULT_SORT_ORDER = "_id";
@@ -29,7 +29,7 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
public interface TracksColumns extends BaseColumns {
String TABLE_NAME = "tracks";
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/tracks");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
String DEFAULT_SORT_ORDER = "_id";
@@ -29,7 +29,7 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
public interface WaypointsColumns extends BaseColumns {
String TABLE_NAME = "waypoints";
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/waypoints");
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
String DEFAULT_SORT_ORDER = "_id";
@@ -0,0 +1,50 @@
package de.dennisguse.opentracks.util;
import android.content.ClipData;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import java.util.ArrayList;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
/**
* Create an {@link Intent} to request showing a Dashboard.
* The receiving {@link android.app.Activity} gets temporary access to the {@link TracksColumns} and the {@link TrackPointsColumns} (incl. update).
*/
public class IntentDashboardUtils {
public static final String ACTION_DASHBOARD = "Intent.OpenTracks-Dashboard";
public static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
private IntentDashboardUtils() {
}
public static boolean startDashboardPlugin(Context context, long trackId) {
ArrayList<Uri> uris = new ArrayList<>();
uris.add(0, ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId));
uris.add(1, ContentUris.withAppendedId(TrackPointsColumns.CONTENT_URI, trackId));
Intent intent = new Intent(ACTION_DASHBOARD);
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ClipData clipData = ClipData.newRawUri(null, uris.get(0));
clipData.addItem(new ClipData.Item(uris.get(1)));
intent.setClipData(clipData);
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(Intent.createChooser(intent, null));
return true;
}
return false;
}
}
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.ShareContentProvider;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -126,6 +127,8 @@ public class IntentUtils {
}
}
/**
* Send intent to show tracks on a map (needs an another app).
*
@@ -137,10 +140,16 @@ public class IntentUtils {
return;
}
if (IntentDashboardUtils.startDashboardPlugin(context, trackIds[0])) {
return;
}
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_WITH_TRACKDETAIL);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);