diff --git a/app/src/androidTest/java/net/osmtracker/activity/PreferencesTest.java b/app/src/androidTest/java/net/osmtracker/activity/PreferencesTest.java
index bef2a78b..ca94dcd0 100644
--- a/app/src/androidTest/java/net/osmtracker/activity/PreferencesTest.java
+++ b/app/src/androidTest/java/net/osmtracker/activity/PreferencesTest.java
@@ -11,6 +11,8 @@
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.stringContainsInOrder;
+import android.util.Log;
+
import android.content.Context;
import android.content.SharedPreferences;
@@ -62,6 +64,7 @@ public void tearDown() {
*/
@Test
public void testStorageDirectoryValidatesNonEmpty() {
+ Log.i("More", "magic");
String keyTitle = context.getString(R.string.prefs_storage_dir);
String defaultValue = OSMTracker.Preferences.VAL_STORAGE_DIR;
diff --git a/app/src/androidTest/java/net/osmtracker/layouts/DownloadLayoutTest.java b/app/src/androidTest/java/net/osmtracker/layouts/DownloadLayoutTest.java
index 5d7c99d2..a1d372d5 100644
--- a/app/src/androidTest/java/net/osmtracker/layouts/DownloadLayoutTest.java
+++ b/app/src/androidTest/java/net/osmtracker/layouts/DownloadLayoutTest.java
@@ -153,4 +153,4 @@ private void clickButtonsToDownloadLayout(String layoutName) {
TestUtils.checkToastIsShownWith(TestUtils.getStringResource(R.string.available_layouts_successful_download));
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 071c23b8..f66ff13c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -46,6 +46,11 @@
+
+
{
@@ -36,10 +39,15 @@ public TracklistAdapter getCursorAdapter() {
}
public interface TrackListRecyclerViewAdapterListener {
- void onClick(long trackId);
+ default void onClick(long trackId) {};
void onCreateContextMenu(ContextMenu contextMenu, View view,
ContextMenu.ContextMenuInfo contextMenuInfo, long trackId);
+ default void onClick(TrackItemVH item, long trackId) {
+ onClick(trackId);
+ }
+ default void initializeItem(TrackItemVH item, long trackId) {
+ }
}
/**
@@ -104,9 +112,23 @@ public ImageView getvUploadStatus() {
@Override
public void onClick(View v) {
long trackId = Long.parseLong(getvId().getText().toString());
- mHandler.onClick(trackId);
+ mHandler.onClick(this, trackId);
}
+ public void activate(boolean state) {
+ if(state)
+ itemView.setBackgroundColor(Color.RED);
+ else {
+ TypedValue outValue = new TypedValue();
+ itemView
+ .getContext()
+ .getTheme()
+ .resolveAttribute(android.R.attr.selectableItemBackground,
+ outValue, true);
+ itemView.setBackgroundResource(outValue.resourceId);
+ }
+ }
+
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
long trackId = Long.parseLong(getvId().getText().toString());
@@ -130,8 +152,11 @@ public void onBindViewHolder(@NonNull TrackItemVH holder, int position) {
// contents of the view with that element
// Passing the binding operation to cursor loader
- cursorAdapter.getCursor().moveToPosition(position);
- cursorAdapter.bindView(holder.itemView, context, cursorAdapter.getCursor());
+ Cursor cursor = cursorAdapter.getCursor();
+ cursor.moveToPosition(position);
+ cursorAdapter.bindView(holder.itemView, context, cursor);
+ mHandler.initializeItem(holder,
+ cursor.getLong(cursor.getColumnIndex(TrackContentProvider.Schema.COL_ID)));
}
@Override
diff --git a/app/src/main/java/net/osmtracker/activity/TrackPicker.java b/app/src/main/java/net/osmtracker/activity/TrackPicker.java
new file mode 100644
index 00000000..6274a90b
--- /dev/null
+++ b/app/src/main/java/net/osmtracker/activity/TrackPicker.java
@@ -0,0 +1,151 @@
+package net.osmtracker.activity;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.View;
+
+import net.osmtracker.OSMTracker;
+import net.osmtracker.R;
+import net.osmtracker.db.TrackContentProvider;
+import net.osmtracker.activity.TrackListRVAdapter.TrackItemVH;
+
+/**
+ * Lists existing tracks. Each track is displayed using {@link RecyclerView}
+ *
+ * @author Alain Knaff
+ */
+public class TrackPicker extends AppCompatActivity
+ implements TrackListRVAdapter.TrackListRecyclerViewAdapterListener
+{
+ private static final String TAG = TrackPicker.class.getSimpleName();
+
+ /** Constant used if no track is active (-1)*/
+ private static final long TRACK_ID_NO_TRACK = -1;
+
+ // The active track being recorded, if any, or {TRACK_ID_NO_TRACK};
+ // value is updated in {@link #onResume()}
+ private long currentTrackId = TRACK_ID_NO_TRACK;
+
+ private TrackListRVAdapter recyclerViewAdapter;
+
+ private RecyclerView recyclerView;
+
+
+ // To check if the RecyclerView already has a
+ // DividerItemDecoration added
+ private boolean hasDivider;
+
+ private long overlaidTrackId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.trackpicker);
+
+ // Toolbar myToolbar = findViewById(R.id.my_toolbar);
+ // setSupportActionBar(myToolbar);
+
+ recyclerView = findViewById(R.id.recyclerview);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+
+ // Adding a horizontal divider
+ DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
+ dividerItemDecoration.setDrawable(ContextCompat.getDrawable(this, R.drawable.divider)); // Using a custom drawable
+
+ recyclerView.addItemDecoration(dividerItemDecoration);
+ }
+
+ @Override
+ protected void onResume() {
+ activatedItem = null;
+ Intent i = getIntent();
+ overlaidTrackId = i.getLongExtra(OSMTracker.INTENT_OVERLAID_TRACK_ID, 0);
+ currentTrackId = i.getLongExtra(TrackContentProvider.Schema.COL_TRACK_ID, 0);
+ setRecyclerView();
+ super.onResume();
+ }
+
+
+ /**
+ * Configures and initializes the RecyclerView for displaying the list of tracks.
+ */
+ private void setRecyclerView() {
+ RecyclerView recyclerView = findViewById(R.id.recyclerview);
+
+ LinearLayoutManager layoutManager = new LinearLayoutManager(this,
+ LinearLayoutManager.VERTICAL, false);
+ recyclerView.setLayoutManager(layoutManager);
+ // adds a divider decoration if not already present
+ if (!hasDivider) {
+ DividerItemDecoration did = new DividerItemDecoration(recyclerView.getContext(),
+ layoutManager.getOrientation());
+ recyclerView.addItemDecoration(did);
+ hasDivider = true;
+ }
+ recyclerView.setHasFixedSize(true);
+ Cursor cursor = getContentResolver().query(
+ TrackContentProvider.CONTENT_URI_TRACK, null, null, null,
+ TrackContentProvider.Schema.COL_START_DATE + " desc");
+
+ recyclerViewAdapter = new TrackListRVAdapter(this, cursor, this);
+ recyclerView.setAdapter(recyclerViewAdapter);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo, long trackId) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ }
+
+ private TrackItemVH activatedItem = null;
+
+ @Override
+ public void onClick(TrackItemVH item, long trackId) {
+ Intent i;
+
+ if(trackId == this.currentTrackId)
+ // ignore clicks on current track
+ return;
+
+ overlaidTrackId = select(item, trackId);
+ Intent data = new Intent();
+ data.putExtra(OSMTracker.INTENT_OVERLAID_TRACK_ID,
+ overlaidTrackId);
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ private long select(TrackItemVH item, long trackId) {
+ if(activatedItem != null)
+ // if a track was activated, whether same or another,
+ // deactivate
+ activatedItem.activate(false);
+
+ if(trackId == this.overlaidTrackId)
+ // if previously activated track was clicked, this
+ // means user doesn't want any track to be activated
+ return 0;
+
+ item.activate(true);
+ return trackId;
+ }
+
+ @Override
+ public void initializeItem(TrackItemVH item, long trackId) {
+ if(trackId == this.currentTrackId)
+ // prevent user from clicking on same track
+ item.itemView.setEnabled(false);
+ if(trackId == this.overlaidTrackId) {
+ item.activate(true);
+ activatedItem = item;
+ }
+ }
+}
diff --git a/app/src/main/java/net/osmtracker/db/DataHelper.java b/app/src/main/java/net/osmtracker/db/DataHelper.java
index 410e8546..79d30623 100644
--- a/app/src/main/java/net/osmtracker/db/DataHelper.java
+++ b/app/src/main/java/net/osmtracker/db/DataHelper.java
@@ -384,6 +384,46 @@ public void deleteNote(String uuid) {
}
}
+ /**
+ * Updates overlaid track of trackId
+ *
+ * @param trackId Id of the track
+ * @param overlay id of the track
+ */
+ public void updateOverlay(long trackId, long overlay) {
+ Log.v(TAG, "Changing overlay track=" + trackId +
+ ", overlay=" + overlay);
+
+ // delete old
+ contentResolver.delete(ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_OVERLAY, trackId), null, null);
+
+ // insert new
+ if(overlay != 0) {
+ ContentValues values = new ContentValues();
+ values.put(TrackContentProvider.Schema.COL_TRACK_ID, trackId);
+ values.put(TrackContentProvider.Schema.COL_OVERLAY_ID, overlay);
+ contentResolver.insert(TrackContentProvider.CONTENT_URI_OVERLAY, values);
+ }
+ }
+
+ /**
+ * Find the ID for track overlaying trackId
+ * @param trackId Id of the track
+ * @param cr {@link ContentResolver} for query
+ * @return the track ID for the overlaid track, or 0 if not found
+ */
+ public static long queryOverlay(long trackId, @NotNull ContentResolver cr) {
+ Cursor ca = cr.query(ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_OVERLAY,
+ trackId),null, null, null, null);
+
+ long overlayTrackId = 0;
+ if (ca.moveToFirst())
+ overlayTrackId = ca.getLong(ca.getColumnIndex(TrackContentProvider.Schema.COL_OVERLAY_ID));
+ ca.close();
+ return overlayTrackId;
+ }
+
+
/**
* Stop tracking by making the track inactive
* @param trackId Id of the track
diff --git a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java
index a106badd..23bbf93b 100644
--- a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java
+++ b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java
@@ -116,6 +116,16 @@ public class DatabaseHelper extends SQLiteOpenHelper {
+ TrackContentProvider.Schema.COL_OSM_UPLOAD_DATE + " long" // null indicates not yet uploaded
+ ")";
+ /**
+ * SQL for creating table OVERLAY
+ * @since 20
+ */
+ private static final String SQL_CREATE_TABLE_OVERLAY = ""
+ + "create table " + TrackContentProvider.Schema.TBL_OVERLAY + " ("
+ + TrackContentProvider.Schema.COL_TRACK_ID + " integer primary key,"
+ + TrackContentProvider.Schema.COL_OVERLAY_ID + " integer not null"
+ + ")";
+
/**
* Database name.
*/
@@ -145,7 +155,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
* v19: add TBL_TRACKPOINT.COL_SEG_ID for track segments support
*
*/
- private static final int DB_VERSION = 19;
+ private static final int DB_VERSION = 20;
private Context context;
@@ -207,6 +217,8 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_CREATE_TABLE_NOTE);
case 18:
db.execSQL("alter table "+TrackContentProvider.Schema.TBL_TRACKPOINT + " add column " + TrackContentProvider.Schema.COL_SEG_ID + " integer default 0");
+ case 19:
+ db.execSQL(SQL_CREATE_TABLE_OVERLAY);
}
}
diff --git a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java
index 31f65ab9..9f75721b 100644
--- a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java
+++ b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java
@@ -38,6 +38,8 @@ public class TrackContentProvider extends ContentProvider {
public static final Uri CONTENT_URI_NOTE = Uri.parse("content://" + AUTHORITY + "/" + Schema.TBL_NOTE);
+ public static final Uri CONTENT_URI_OVERLAY = Uri.parse("content://" + AUTHORITY + "/" + Schema.TBL_OVERLAY);
+
/**
* Uri for the active track
*/
@@ -125,6 +127,8 @@ public class TrackContentProvider extends ContentProvider {
uriMatcher.addURI(AUTHORITY, Schema.TBL_TRACKPOINT + "/#", Schema.URI_CODE_TRACKPOINT_ID);
uriMatcher.addURI(AUTHORITY, Schema.TBL_NOTE + "/#", Schema.URI_CODE_NOTE_ID);
uriMatcher.addURI(AUTHORITY, Schema.TBL_NOTE + "/uuid/*", Schema.URI_CODE_NOTE_UUID);
+ uriMatcher.addURI(AUTHORITY, Schema.TBL_OVERLAY, Schema.URI_CODE_OVERLAYS);
+ uriMatcher.addURI(AUTHORITY, Schema.TBL_OVERLAY + "/#", Schema.URI_CODE_OVERLAY_ID);
}
/**
@@ -227,6 +231,8 @@ public int delete(Uri uri, String selection, String[] selectionArgs) {
String trackId = Long.toString(ContentUris.parseId(uri));
dbHelper.getWritableDatabase().delete(Schema.TBL_WAYPOINT, Schema.COL_TRACK_ID + " = ?", new String[] {trackId});
dbHelper.getWritableDatabase().delete(Schema.TBL_TRACKPOINT, Schema.COL_TRACK_ID + " = ?", new String[] {trackId});
+ dbHelper.getWritableDatabase().delete(Schema.TBL_OVERLAY, Schema.COL_TRACK_ID + " = ?", new String[] {trackId});
+ dbHelper.getWritableDatabase().delete(Schema.TBL_OVERLAY, Schema.COL_OVERLAY_ID + " = ?", new String[] {trackId});
count = dbHelper.getWritableDatabase().delete(Schema.TBL_TRACK, Schema.COL_ID + " = ?", new String[] {trackId});
break;
case Schema.URI_CODE_WAYPOINT_UUID:
@@ -245,6 +251,10 @@ public int delete(Uri uri, String selection, String[] selectionArgs) {
count = 0;
}
break;
+ case Schema.URI_CODE_OVERLAY_ID:
+ String oTrackId = Long.toString(ContentUris.parseId(uri));
+ count = dbHelper.getWritableDatabase().delete(Schema.TBL_OVERLAY, Schema.COL_TRACK_ID + " = ?", new String[] {oTrackId});
+ break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
@@ -345,6 +355,19 @@ public Uri insert(Uri uri, ContentValues values) {
throw new IllegalArgumentException("values should provide " + Schema.COL_START_DATE);
}
break;
+ case Schema.URI_CODE_OVERLAYS:
+ if (values.containsKey(Schema.COL_TRACK_ID) &&
+ values.containsKey(Schema.COL_OVERLAY_ID)) {
+ long rowId = dbHelper.getWritableDatabase().insert(Schema.TBL_OVERLAY, null, values);
+ if (rowId > 0) {
+ Uri overlayUri = ContentUris.withAppendedId(CONTENT_URI_OVERLAY, rowId);
+ getContext().getContentResolver().notifyChange(overlayUri, null);
+ return overlayUri;
+ }
+ } else {
+ throw new IllegalArgumentException("values should provide " + Schema.COL_TRACK_ID+ " and "+Schema.COL_OVERLAY_ID);
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
@@ -490,6 +513,16 @@ public Cursor query(Uri uri, String[] projection, String selectionIn, String[] s
selection = Schema.TBL_TRACKPOINT + "." + Schema.COL_ID + " = ?";
selectionArgs = new String[] {trackPointId};
break;
+ case Schema.URI_CODE_OVERLAY_ID:
+ if (selectionIn != null || selectionArgsIn != null) {
+ // Any selection/selectionArgs will be ignored
+ throw new UnsupportedOperationException();
+ }
+ trackId = uri.getLastPathSegment();
+ qb.setTables(Schema.TBL_OVERLAY);
+ selection = Schema.TBL_OVERLAY + "." + Schema.COL_TRACK_ID + " = ?";
+ selectionArgs = new String[] {trackId};
+ break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
@@ -574,6 +607,7 @@ public static final class Schema {
public static final String TBL_WAYPOINT = "waypoint";
public static final String TBL_NOTE = "note";
public static final String TBL_TRACK = "track";
+ public static final String TBL_OVERLAY = "overlay";
public static final String COL_ID = "_id";
public static final String COL_TRACK_ID = "track_id";
public static final String COL_UUID = "uuid";
@@ -599,6 +633,7 @@ public static final class Schema {
public static final String COL_COMPASS_ACCURACY = "compass_accuracy";
public static final String COL_ATMOSPHERIC_PRESSURE = "atmospheric_pressure";
public static final String COL_SEG_ID = "segment_id";
+ public static final String COL_OVERLAY_ID = "overlay_id";
// virtual colums that are used in some sqls but dont exist in database
public static final String COL_TRACKPOINT_COUNT = "tp_count";
@@ -620,6 +655,8 @@ public static final class Schema {
public static final int URI_CODE_TRACK_NOTES = 13;
public static final int URI_CODE_NOTE_ID = 14;
public static final int URI_CODE_NOTE_UUID = 15;
+ public static final int URI_CODE_OVERLAYS = 16;
+ public static final int URI_CODE_OVERLAY_ID = 17;
public static final int VAL_TRACK_ACTIVE = 1;
diff --git a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java
index 0d20d0b6..2b92d82d 100644
--- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java
+++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java
@@ -351,7 +351,6 @@ protected void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean f
int prevSegId=-1;
for(c.moveToFirst(); !c.isAfterLast(); c.moveToNext(),i++) {
StringBuffer out = new StringBuffer();
-
int segId = c.getInt(c.getColumnIndex(TrackContentProvider.Schema.COL_SEG_ID));
if(prevSegId != -1 && segId != prevSegId) {
fw.write("\t\t" + "" + "\n");
@@ -635,4 +634,4 @@ public String sanitizeTrackName(String trackName){
public String getErrorMsg() {
return errorMsg;
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java
index 901cc5e6..f57b4132 100644
--- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java
+++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java
@@ -6,6 +6,8 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -24,11 +26,14 @@
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
+import android.database.Cursor;
+
import net.osmtracker.OSMTracker;
import net.osmtracker.R;
import net.osmtracker.activity.TrackLogger;
import net.osmtracker.db.DataHelper;
import net.osmtracker.db.TrackContentProvider;
+import net.osmtracker.db.model.Track;
import net.osmtracker.listener.PressureListener;
import net.osmtracker.listener.SensorListener;
@@ -109,6 +114,8 @@ public class GPSLogger extends Service implements LocationListener {
*/
private PressureListener pressureListener = new PressureListener();
+ private boolean newSeg = false;
+
/**
* Receives Intent for way point and notes tracking, and stop/start logging.
*/
@@ -191,6 +198,7 @@ public void onReceive(Context context, Intent intent) {
dataHelper.updateNote(trackId, uuid, name);
}
} else if (OSMTracker.INTENT_START_TRACKING.equals(intent.getAction())) {
+ newSeg = true;
Bundle extras = intent.getExtras();
if (extras != null) {
Long trackId = extras.getLong(TrackContentProvider.Schema.COL_TRACK_ID);
@@ -318,6 +326,23 @@ public void onDestroy() {
super.onDestroy();
}
+ private long getSegIdFor(long trackId) {
+ ContentResolver cr = getContentResolver();
+ try(Cursor cursor =
+ cr.query(ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId),
+ null, null, null, null)) {
+
+ if (! cursor.moveToFirst()) {
+ Log.v(TAG, "Track "+trackId+" not found");
+ return 0; // <--- Early return ---
+ }
+
+ return Track
+ .build(trackId, cursor, cr, true)
+ .getMaxSegId();
+ }
+ }
+
/**
* Start GPS tracking.
*/
diff --git a/app/src/main/res/layout/trackpicker.xml b/app/src/main/res/layout/trackpicker.xml
new file mode 100644
index 00000000..108127f6
--- /dev/null
+++ b/app/src/main/res/layout/trackpicker.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/displaytrackmap_menu.xml b/app/src/main/res/menu/displaytrackmap_menu.xml
index 8ee7003a..eb00f088 100644
--- a/app/src/main/res/menu/displaytrackmap_menu.xml
+++ b/app/src/main/res/menu/displaytrackmap_menu.xml
@@ -5,6 +5,9 @@
android:id="@+id/displaytrackmap_menu_center_to_gps"
android:icon="@android:drawable/ic_menu_mylocation"
android:title="@string/menu_center_to_gps" />
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 277fe80e..2db92cc4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -52,6 +52,8 @@
Unable to export track: {0}
All tracks will be exported, which could take a long time. Are you sure?
Unable to process the track: {0}
+
+ List of tracks to overlay:
Track Details
Start time:
@@ -133,6 +135,7 @@
Export as GPX
OpenStreetMap upload
Center to GPS
+ Overlay other
Export all as GPX
Unable to write to external storage.